IDEA-51893: Quick Fix "​Create Field" when using Groovy named parameters
[fedora-idea.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / lang / psi / impl / statements / expressions / TypesUtil.java
blobe3049b8a6498c17c3e9c61f47c422867e163d6e6
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions;
18 import com.intellij.pom.java.LanguageLevel;
19 import com.intellij.psi.*;
20 import com.intellij.psi.search.GlobalSearchScope;
21 import com.intellij.psi.tree.IElementType;
22 import com.intellij.psi.util.InheritanceUtil;
23 import com.intellij.psi.util.TypeConversionUtil;
24 import com.intellij.util.containers.HashMap;
25 import gnu.trove.TIntObjectHashMap;
26 import gnu.trove.TObjectIntHashMap;
27 import org.jetbrains.annotations.NonNls;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
31 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
32 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrBinaryExpression;
33 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
34 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrUnaryExpression;
35 import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType;
36 import org.jetbrains.plugins.groovy.lang.psi.impl.GrTupleType;
37 import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil;
38 import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
39 import org.jetbrains.plugins.groovy.lang.resolve.processors.MethodResolverProcessor;
41 import java.util.Map;
43 import static com.intellij.psi.CommonClassNames.*;
44 import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.*;
46 /**
47 * @author ven
49 public class TypesUtil {
50 @NonNls
51 public static final Map<String, PsiType> ourQNameToUnboxed = new HashMap<String, PsiType>();
53 private TypesUtil() {
56 @Nullable
57 public static PsiType getNumericResultType(GrBinaryExpression binaryExpression, PsiType lType) {
58 final GrExpression rop = binaryExpression.getRightOperand();
59 PsiType rType = rop == null ? null : rop.getType();
60 if (lType == null || rType == null) return null;
61 String lCanonical = lType.getCanonicalText();
62 String rCanonical = rType.getCanonicalText();
63 if (TYPE_TO_RANK.containsKey(lCanonical) && TYPE_TO_RANK.containsKey(rCanonical)) {
64 int lRank = TYPE_TO_RANK.get(lCanonical);
65 int rRank = TYPE_TO_RANK.get(rCanonical);
66 int resultRank = Math.max(lRank, rRank);
67 String qName = RANK_TO_TYPE.get(resultRank);
68 GlobalSearchScope scope = binaryExpression.getResolveScope();
69 if (qName == null) return null;
70 return JavaPsiFacade.getInstance(binaryExpression.getProject()).getElementFactory().createTypeByFQClassName(qName, scope);
73 final PsiType type = getOverloadedOperatorType(lType, binaryExpression.getOperationTokenType(), binaryExpression, new PsiType[]{rType});
74 if (type != null) {
75 return type;
78 if (rType.equalsToText(GrStringUtil.GROOVY_LANG_GSTRING)) {
79 PsiType gstringType = JavaPsiFacade.getInstance(binaryExpression.getProject()).getElementFactory()
80 .createTypeByFQClassName(GrStringUtil.GROOVY_LANG_GSTRING, binaryExpression.getResolveScope());
81 return getOverloadedOperatorType(lType, binaryExpression.getOperationTokenType(), binaryExpression, new PsiType[]{gstringType});
83 return null;
86 @Nullable
87 public static PsiType getOverloadedOperatorType(PsiType thisType,
88 IElementType tokenType,
89 GroovyPsiElement place,
90 PsiType[] argumentTypes) {
91 return getOverloadedOperatorType(thisType, ourOperationsToOperatorNames.get(tokenType), place, argumentTypes);
94 @Nullable
95 public static PsiType getOverloadedOperatorType(PsiType thisType, String operatorName, GroovyPsiElement place, PsiType[] argumentTypes) {
96 if (operatorName != null) {
97 MethodResolverProcessor processor =
98 new MethodResolverProcessor(operatorName, place, false, thisType, argumentTypes, PsiType.EMPTY_ARRAY);
99 if (thisType instanceof PsiClassType) {
100 final PsiClassType classtype = (PsiClassType)thisType;
101 final PsiClassType.ClassResolveResult resolveResult = classtype.resolveGenerics();
102 final PsiClass lClass = resolveResult.getElement();
103 if (lClass != null) {
104 lClass.processDeclarations(processor, ResolveState.initial().put(PsiSubstitutor.KEY, resolveResult.getSubstitutor()), null, place);
108 ResolveUtil.processNonCodeMethods(thisType, processor, place.getProject(), place, false);
109 final GroovyResolveResult[] candidates = processor.getCandidates();
110 if (candidates.length == 1) {
111 final PsiElement element = candidates[0].getElement();
112 if (element instanceof PsiMethod) {
113 return candidates[0].getSubstitutor().substitute(((PsiMethod)element).getReturnType());
117 return null;
119 private static final Map<IElementType, String> ourPrimitiveTypesToClassNames = new HashMap<IElementType, String>();
120 private static final String NULL = "null";
121 private static final String JAVA_MATH_BIG_DECIMAL = "java.math.BigDecimal";
122 private static final String JAVA_MATH_BIG_INTEGER = "java.math.BigInteger";
124 static {
125 ourPrimitiveTypesToClassNames.put(mSTRING_LITERAL, JAVA_LANG_STRING);
126 ourPrimitiveTypesToClassNames.put(mGSTRING_LITERAL, JAVA_LANG_STRING);
127 ourPrimitiveTypesToClassNames.put(mREGEX_LITERAL, JAVA_LANG_STRING);
128 ourPrimitiveTypesToClassNames.put(mNUM_INT, JAVA_LANG_INTEGER);
129 ourPrimitiveTypesToClassNames.put(mNUM_LONG, JAVA_LANG_LONG);
130 ourPrimitiveTypesToClassNames.put(mNUM_FLOAT, JAVA_LANG_FLOAT);
131 ourPrimitiveTypesToClassNames.put(mNUM_DOUBLE, JAVA_LANG_DOUBLE);
132 ourPrimitiveTypesToClassNames.put(mNUM_BIG_INT, JAVA_MATH_BIG_INTEGER);
133 ourPrimitiveTypesToClassNames.put(mNUM_BIG_DECIMAL, JAVA_MATH_BIG_DECIMAL);
134 ourPrimitiveTypesToClassNames.put(kFALSE, JAVA_LANG_BOOLEAN);
135 ourPrimitiveTypesToClassNames.put(kTRUE, JAVA_LANG_BOOLEAN);
136 ourPrimitiveTypesToClassNames.put(kNULL, NULL);
139 private static final Map<IElementType, String> ourOperationsToOperatorNames = new HashMap<IElementType, String>();
141 static {
142 ourOperationsToOperatorNames.put(mPLUS, "plus");
143 ourOperationsToOperatorNames.put(mMINUS, "minus");
144 ourOperationsToOperatorNames.put(mBAND, "and");
145 ourOperationsToOperatorNames.put(mBOR, "or");
146 ourOperationsToOperatorNames.put(mBXOR, "xor");
147 ourOperationsToOperatorNames.put(mDIV, "div");
148 ourOperationsToOperatorNames.put(mMOD, "mod");
149 ourOperationsToOperatorNames.put(mSTAR, "multiply");
150 ourOperationsToOperatorNames.put(mDEC, "previous");
151 ourOperationsToOperatorNames.put(mINC, "next");
154 private static final TObjectIntHashMap<String> TYPE_TO_RANK = new TObjectIntHashMap<String>();
156 static {
157 TYPE_TO_RANK.put(JAVA_LANG_BYTE, 1);
158 TYPE_TO_RANK.put(JAVA_LANG_SHORT, 2);
159 TYPE_TO_RANK.put(JAVA_LANG_CHARACTER, 2);
160 TYPE_TO_RANK.put(JAVA_LANG_INTEGER, 3);
161 TYPE_TO_RANK.put(JAVA_LANG_LONG, 4);
162 TYPE_TO_RANK.put(JAVA_MATH_BIG_INTEGER, 5);
163 TYPE_TO_RANK.put(JAVA_MATH_BIG_DECIMAL, 6);
164 TYPE_TO_RANK.put(JAVA_LANG_FLOAT, 7);
165 TYPE_TO_RANK.put(JAVA_LANG_DOUBLE, 8);
166 TYPE_TO_RANK.put(JAVA_LANG_NUMBER, 9);
169 static {
170 ourQNameToUnboxed.put(JAVA_LANG_BOOLEAN, PsiType.BOOLEAN);
171 ourQNameToUnboxed.put(JAVA_LANG_BYTE, PsiType.BYTE);
172 ourQNameToUnboxed.put(JAVA_LANG_CHARACTER, PsiType.CHAR);
173 ourQNameToUnboxed.put(JAVA_LANG_SHORT, PsiType.SHORT);
174 ourQNameToUnboxed.put(JAVA_LANG_INTEGER, PsiType.INT);
175 ourQNameToUnboxed.put(JAVA_LANG_LONG, PsiType.LONG);
176 ourQNameToUnboxed.put(JAVA_LANG_FLOAT, PsiType.FLOAT);
177 ourQNameToUnboxed.put(JAVA_LANG_DOUBLE, PsiType.DOUBLE);
181 private static final TIntObjectHashMap<String> RANK_TO_TYPE = new TIntObjectHashMap<String>();
183 static {
184 RANK_TO_TYPE.put(1, JAVA_LANG_INTEGER);
185 RANK_TO_TYPE.put(2, JAVA_LANG_INTEGER);
186 RANK_TO_TYPE.put(3, JAVA_LANG_INTEGER);
187 RANK_TO_TYPE.put(4, JAVA_LANG_LONG);
188 RANK_TO_TYPE.put(5, JAVA_MATH_BIG_INTEGER);
189 RANK_TO_TYPE.put(6, JAVA_MATH_BIG_DECIMAL);
190 RANK_TO_TYPE.put(7, JAVA_LANG_DOUBLE);
191 RANK_TO_TYPE.put(8, JAVA_LANG_DOUBLE);
192 RANK_TO_TYPE.put(9, JAVA_LANG_NUMBER);
195 public static boolean isAssignable(PsiType lType, PsiType rType, PsiManager manager, GlobalSearchScope scope) {
196 //all numeric types are assignable
197 if (isNumericType(lType)) {
198 return isNumericType(rType) || rType.equals(PsiType.NULL);
200 if (rType instanceof GrTupleType) {
201 final GrTupleType tuple = (GrTupleType)rType;
202 if (tuple.getComponentTypes().length == 0) {
203 if (lType instanceof PsiArrayType ||
204 InheritanceUtil.isInheritor(lType, JAVA_UTIL_LIST) ||
205 InheritanceUtil.isInheritor(lType, JAVA_UTIL_SET)) {
206 return true;
211 if (lType.equalsToText(JAVA_LANG_STRING)) return true;
213 rType = boxPrimitiveType(rType, manager, scope);
214 lType = boxPrimitiveType(lType, manager, scope);
216 return lType.isAssignableFrom(rType);
219 public static boolean isAssignableByMethodCallConversion(PsiType lType, PsiType rType, PsiManager manager, GlobalSearchScope scope) {
220 if (lType == null || rType == null) return false;
222 if (rType instanceof GrTupleType) {
223 final GrTupleType tuple = (GrTupleType)rType;
224 if (tuple.getComponentTypes().length == 0) {
225 if (lType instanceof PsiArrayType ||
226 InheritanceUtil.isInheritor(lType, JAVA_UTIL_LIST) ||
227 InheritanceUtil.isInheritor(lType, JAVA_UTIL_SET)) {
228 return true;
233 if (rType.equalsToText(GrStringUtil.GROOVY_LANG_GSTRING)) {
234 final PsiClass javaLangString = JavaPsiFacade.getInstance(manager.getProject()).findClass(JAVA_LANG_STRING, scope);
235 if (javaLangString != null &&
236 isAssignable(lType, JavaPsiFacade.getElementFactory(manager.getProject()).createType(javaLangString), manager, scope)) {
237 return true;
241 if (isNumericType(lType) && isNumericType(rType)) {
242 lType = unboxPrimitiveTypeWrapper(lType);
243 if (lType.equalsToText(JAVA_MATH_BIG_DECIMAL)) lType = PsiType.DOUBLE;
244 rType = unboxPrimitiveTypeWrapper(rType);
245 if (rType.equalsToText(JAVA_MATH_BIG_DECIMAL)) rType = PsiType.DOUBLE;
247 else {
248 rType = boxPrimitiveType(rType, manager, scope);
249 lType = boxPrimitiveType(lType, manager, scope);
252 return TypeConversionUtil.isAssignable(lType, rType);
256 public static boolean isNumericType(PsiType type) {
257 if (type instanceof PsiClassType) {
258 return TYPE_TO_RANK.contains(type.getCanonicalText());
261 return type instanceof PsiPrimitiveType && TypeConversionUtil.isNumericType(type);
264 public static PsiType unboxPrimitiveTypeWraperAndEraseGenerics(PsiType result) {
265 return TypeConversionUtil.erasure(unboxPrimitiveTypeWrapper(result));
268 public static PsiType unboxPrimitiveTypeWrapper(PsiType type) {
269 if (type instanceof PsiClassType) {
270 PsiType unboxed = ourQNameToUnboxed.get(type.getCanonicalText());
271 if (unboxed != null) type = unboxed;
273 return type;
276 public static PsiType boxPrimitiveType(PsiType result, PsiManager manager, GlobalSearchScope resolveScope) {
277 if (result instanceof PsiPrimitiveType && result != PsiType.VOID) {
278 PsiPrimitiveType primitive = (PsiPrimitiveType)result;
279 String boxedTypeName = primitive.getBoxedTypeName();
280 if (boxedTypeName != null) {
281 return JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createTypeByFQClassName(boxedTypeName, resolveScope);
285 return result;
288 @Nullable
289 public static PsiType getTypeForIncOrDecExpression(GrUnaryExpression expr) {
290 final GrExpression op = expr.getOperand();
291 if (op != null) {
292 final PsiType opType = op.getType();
293 if (opType != null) {
294 final PsiType overloaded = getOverloadedOperatorType(opType, expr.getOperationTokenType(), expr, PsiType.EMPTY_ARRAY);
295 if (overloaded != null) {
296 return overloaded;
298 if (isNumericType(opType)) {
299 return opType;
304 return null;
307 public static PsiClassType createType(String fqName, PsiElement context) {
308 JavaPsiFacade facade = JavaPsiFacade.getInstance(context.getProject());
309 return facade.getElementFactory().createTypeByFQClassName(fqName, context.getResolveScope());
312 public static PsiClassType getJavaLangObject(PsiElement context) {
313 return PsiType.getJavaLangObject(context.getManager(), context.getResolveScope());
316 @Nullable
317 public static PsiType getLeastUpperBound(@NotNull PsiType type1, @NotNull PsiType type2, PsiManager manager) {
318 if (type1 instanceof GrTupleType && type2 instanceof GrTupleType) {
319 GrTupleType tuple1 = (GrTupleType)type1;
320 GrTupleType tuple2 = (GrTupleType)type2;
321 PsiType[] components1 = tuple1.getComponentTypes();
322 PsiType[] components2 = tuple2.getComponentTypes();
323 PsiType[] components3 = new PsiType[Math.min(components1.length, components2.length)];
324 for (int i = 0; i < components3.length; i++) {
325 PsiType c1 = components1[i];
326 PsiType c2 = components2[i];
327 if (c1 == null || c2 == null) {
328 components3[i] = null;
330 else {
331 components3[i] = getLeastUpperBound(c1, c2, manager);
334 return new GrTupleType(components3, JavaPsiFacade.getInstance(manager.getProject()),
335 tuple1.getScope().intersectWith(tuple2.getResolveScope()));
337 else if (type1 instanceof GrClosureType && type2 instanceof GrClosureType) {
338 GrClosureType clType1 = (GrClosureType)type1;
339 GrClosureType clType2 = (GrClosureType)type2;
340 PsiType[] parameterTypes1 = clType1.getClosureParameterTypes();
341 PsiType[] parameterTypes2 = clType2.getClosureParameterTypes();
342 if (parameterTypes1.length == parameterTypes2.length) {
343 PsiType[] paramTypes = new PsiType[parameterTypes1.length];
344 boolean[] opts = new boolean[parameterTypes1.length];
345 for (int i = 0; i < paramTypes.length; i++) {
346 paramTypes[i] = GenericsUtil.getGreatestLowerBound(parameterTypes1[i], parameterTypes2[i]);
347 opts[i] = clType1.isOptionalParameter(i) && clType2.isOptionalParameter(i);
349 final PsiType ret1 = clType1.getClosureReturnType();
350 final PsiType ret2 = clType2.getClosureReturnType();
351 PsiType returnType = ret1 == null ? ret2 : ret2 == null ? ret1 : getLeastUpperBound(ret1, ret2, manager);
352 GlobalSearchScope scope = clType1.getResolveScope().intersectWith(clType2.getResolveScope());
353 return GrClosureType.create(returnType, paramTypes, opts, manager, scope, LanguageLevel.JDK_1_5);
356 else if (GrStringUtil.GROOVY_LANG_GSTRING.equals(type1.getCanonicalText()) &&
357 CommonClassNames.JAVA_LANG_STRING.equals(type2.getInternalCanonicalText())) {
358 return type2;
360 else if (GrStringUtil.GROOVY_LANG_GSTRING.equals(type2.getCanonicalText()) &&
361 CommonClassNames.JAVA_LANG_STRING.equals(type1.getInternalCanonicalText())) {
362 return type1;
365 return GenericsUtil.getLeastUpperBound(type1, type2, manager);
368 @Nullable
369 public static PsiType getPsiType(PsiElement context, IElementType elemType) {
370 if (elemType == kNULL) {
371 return PsiType.NULL;
373 final String typeName = getPsiTypeName(elemType);
374 if (typeName != null) {
375 return JavaPsiFacade.getElementFactory(context.getProject()).createTypeByFQClassName(typeName, context.getResolveScope());
377 return null;
380 @Nullable
381 private static String getPsiTypeName(IElementType elemType) {
382 return ourPrimitiveTypesToClassNames.get(elemType);