IDEADEV-41448
authorMaxim Medvedev <maxim.medvedev@jetbrains.com>
Wed, 18 Nov 2009 17:04:12 +0000 (20:04 +0300)
committerMaxim Medvedev <maxim.medvedev@jetbrains.com>
Wed, 18 Nov 2009 17:04:12 +0000 (20:04 +0300)
plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/PsiUtil.java
plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.java
plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.java
plugins/groovy/testdata/highlighting/MethodCallWithDefaultParameters.groovy [new file with mode: 0644]
plugins/groovy/testdata/resolve/method/optionalParameters1/A.groovy [new file with mode: 0644]
plugins/groovy/testdata/resolve/method/optionalParameters2/A.groovy [new file with mode: 0644]
plugins/groovy/testdata/resolve/method/optionalParameters3/A.groovy [new file with mode: 0644]
plugins/groovy/testdata/resolve/method/optionalParameters4/A.groovy [new file with mode: 0644]

index 10e86375ff713c5e324fd049c6271f419a81cc50..f1d1324e13946081791011beaee06bcb537ec773 100644 (file)
@@ -130,57 +130,87 @@ public class PsiUtil {
       parameters = ArrayUtil.remove(parameters, 0);
     }
 
-    PsiType[] parameterTypes = skipOptionalParametersAndSubstitute(argumentTypes.length, parameters, substitutor);
-    if (parameterTypes.length - 1 > argumentTypes.length) return false; //one Map type might represent named arguments
-    if (parameterTypes.length == 0 && argumentTypes.length > 0) return false;
-
-    final GlobalSearchScope scope = method.getResolveScope();
-
-    if (parameterTypes.length - 1 == argumentTypes.length) {
-      final PsiType firstType = parameterTypes[0];
-      final PsiClassType mapType = createMapType(method.getManager(), scope);
-      if (mapType.isAssignableFrom(firstType)) {
-        final PsiType[] trimmed = new PsiType[parameterTypes.length - 1];
-        System.arraycopy(parameterTypes, 1, trimmed, 0, trimmed.length);
-        parameterTypes = trimmed;
-      } else if (!method.isVarArgs()) return false;
-    }
-
-    for (int i = 0; i < argumentTypes.length; i++) {
-      PsiType argType = argumentTypes[i];
-      PsiType parameterTypeToCheck;
-      if (i < parameterTypes.length - 1) {
-        parameterTypeToCheck = parameterTypes[i];
-      } else {
-        PsiType lastParameterType = parameterTypes[parameterTypes.length - 1];
-        if (lastParameterType instanceof PsiArrayType && !(argType instanceof PsiArrayType)) {
-          parameterTypeToCheck = ((PsiArrayType)lastParameterType).getComponentType();
-        } else if (parameterTypes.length == argumentTypes.length) {
-          parameterTypeToCheck = lastParameterType;
-        } else {
-          return false;
-        }
+    if (parameters.length == 0) return argumentTypes.length == 0;
+
+    final PsiParameter lastParameter = parameters[parameters.length - 1];
+    boolean hasVarArgs = lastParameter.isVarArgs() || lastParameter.getType() instanceof PsiArrayType;
+    if (!hasVarArgs && argumentTypes.length > parameters.length) return false;
+
+    int allOptionalParameterCount = 0;
+    for (PsiParameter parameter : parameters) {
+      if (isOptionalParameter(parameter)) {
+        allOptionalParameterCount++;
       }
+    }
 
-      if (!TypesUtil.isAssignableByMethodCallConversion(parameterTypeToCheck, argType, method.getManager(), scope)) return false;
+    int optionalParameterCount;
+    if (hasVarArgs) {
+      optionalParameterCount = allOptionalParameterCount - (parameters.length - 1) + argumentTypes.length;
+      if (optionalParameterCount < 0) return false;
+      for (int i = optionalParameterCount; i >= 0; i--) {
+        if (checkMethodApplicability(parameters, argumentTypes, i, hasVarArgs, substitutor, method.getResolveScope(), method.getManager())) {
+          return true;
+        }
+      }
+      return false;
     }
+    else {
+      optionalParameterCount = allOptionalParameterCount - parameters.length + argumentTypes.length;
+      if (optionalParameterCount < 0) return false;
+      return checkMethodApplicability(parameters, argumentTypes, optionalParameterCount, hasVarArgs, substitutor, method.getResolveScope(),
+                                      method.getManager());
 
-    return true;
+    }
   }
 
-  public static PsiType[] skipOptionalParametersAndSubstitute(int argNum, PsiParameter[] parameters, PsiSubstitutor substitutor) {
-    int diff = parameters.length - argNum;
-    List<PsiType> result = new ArrayList<PsiType>(argNum);
-    for (PsiParameter parameter : parameters) {
-      if (diff > 0 && parameter instanceof GrParameter && ((GrParameter)parameter).isOptional()) {
-        diff--;
-        continue;
+  private static boolean checkMethodApplicability(PsiParameter[] parameters,
+                                                  PsiType[] argumentTypes,
+                                                  int optionalParametersCount,
+                                                  boolean hasVarArg,
+                                                  PsiSubstitutor substitutor,
+                                                  GlobalSearchScope scope,
+                                                  PsiManager manager) {
+
+    int argIndex = 0;
+    for (int i = 0; i < parameters.length - 1; i++) {
+      if (isOptionalParameter(parameters[i])) {
+        if (optionalParametersCount == 0) continue;
+        optionalParametersCount--;
       }
 
-      result.add(substitutor.substitute(parameter.getType()));
+      final PsiType parameterType = substitutor.substitute(parameters[i].getType());
+      if (argIndex >= argumentTypes.length) return false;
+      final PsiType argType = argumentTypes[argIndex++];
+      if (!TypesUtil.isAssignableByMethodCallConversion(parameterType, argType, manager, scope)) {
+        return false;
+      }
     }
 
-    return result.toArray(new PsiType[result.size()]);
+    final PsiParameter lastParameter = parameters[parameters.length - 1];
+    final PsiType lastParameterType = substitutor.substitute(lastParameter.getType());
+
+    if (hasVarArg) {
+      if (argIndex == argumentTypes.length - 1 &&
+          TypesUtil.isAssignableByMethodCallConversion(lastParameterType, argumentTypes[argIndex], manager, scope)) {
+        return true;
+      }
+      final PsiType arrayType = ((PsiArrayType)lastParameterType).getComponentType();
+      for (; argIndex < argumentTypes.length; argIndex++) {
+        if (!TypesUtil.isAssignableByMethodCallConversion(arrayType, argumentTypes[argIndex], manager, scope)) return false;
+      }
+    }
+    else {
+      if (!isOptionalParameter(lastParameter) || optionalParametersCount > 0) {
+        if (argIndex >= argumentTypes.length) return false;
+        final PsiType argType = argumentTypes[argIndex++];
+        if (!TypesUtil.isAssignableByMethodCallConversion(lastParameterType, argType, manager, scope)) return false;
+      }
+    }
+    return argIndex == argumentTypes.length;
+  }
+
+  private static boolean isOptionalParameter(PsiParameter parameter) {
+    return parameter instanceof GrParameter && ((GrParameter)parameter).isOptional();
   }
 
   public static PsiClassType createMapType(PsiManager manager, GlobalSearchScope scope) {
@@ -792,6 +822,7 @@ public class PsiUtil {
 
   @Nullable
   public static PsiElement skipWhitespaces(@Nullable PsiElement elem, boolean forward) {
+    //noinspection ConstantConditions
     while (elem != null &&
            elem.getNode() != null &&
            GroovyElementTypes.WHITE_SPACES_OR_COMMENTS.contains(elem.getNode().getElementType())) {
index 1dc403f003da884a932f85803dd04a463b1f3798..4f478a88e23b8ab9ee880655d40476b3ade2390b 100644 (file)
@@ -191,4 +191,6 @@ public class GroovyHighlightingTest extends LightCodeInsightFixtureTestCase {
   public void testGstringAssignableToString() throws Exception{doTest();} 
   public void testGstringAssignableToStringInClosureParameter() throws Exception{doTest();}
   public void testEverythingAssignableToString() throws Exception {doTest(new GroovyAssignabilityCheckInspection());}
+
+  public void testMethodCallWithDefaultParameters() throws Exception {doTest();}
 }
\ No newline at end of file
index d566a056d4ad0e233f7bce9d9cb9a859bd4c9387..51a7f135a4724de61859e9821b5b92fdce46f57b 100644 (file)
@@ -241,11 +241,7 @@ public class ResolveMethodTest extends GroovyResolveTestCase {
   public void testConstructor2() throws Exception {
     PsiReference ref = configureByFile("constructor2/A.groovy");
     PsiMethod method = ((GrNewExpression) ref.getElement().getParent()).resolveConstructor();
-    assertNotNull(method);
-    assertTrue(method.isConstructor());
-    final PsiParameter[] parameters = method.getParameterList().getParameters();
-    assertEquals(1, parameters.length);
-    assertTrue(parameters[0].getType().equalsToText("java.util.Map"));
+    assertNull(method);
   }
 
   //grvy-101
@@ -520,4 +516,28 @@ public class ResolveMethodTest extends GroovyResolveTestCase {
     assertInstanceOf(resolved, PsiMethod.class);
     assertEquals("A", ((PsiMethod)resolved).getContainingClass().getName());
   }
+
+  public void testOptionalParameters1() throws Exception {
+    PsiReference ref = configureByFile("optionalParameters1/A.groovy");
+    final PsiElement resolved = ref.resolve();
+    assertInstanceOf(resolved, PsiMethod.class);
+  }
+
+  public void testOptionalParameters2() throws Exception {
+    PsiReference ref = configureByFile("optionalParameters2/A.groovy");
+    final PsiElement resolved = ref.resolve();
+    assertInstanceOf(resolved, PsiMethod.class);
+  }
+
+  public void testOptionalParameters3() throws Exception {
+    PsiReference ref = configureByFile("optionalParameters3/A.groovy");
+    final PsiElement resolved = ref.resolve();
+    assertInstanceOf(resolved, PsiMethod.class);
+  }
+
+  public void testOptionalParameters4() throws Exception {
+    PsiReference ref = configureByFile("optionalParameters4/A.groovy");
+    final PsiElement resolved = ref.resolve();
+    assertInstanceOf(resolved, PsiMethod.class);
+  }
 }
diff --git a/plugins/groovy/testdata/highlighting/MethodCallWithDefaultParameters.groovy b/plugins/groovy/testdata/highlighting/MethodCallWithDefaultParameters.groovy
new file mode 100644 (file)
index 0000000..64b572f
--- /dev/null
@@ -0,0 +1,56 @@
+public def createRegisteredPerson(String username,
+                                  String password,
+                                  String email,
+                                  String ipAddress,
+                                  String roleName = null,
+                                  String firstName = null,
+                                  String lastName = null,
+                                  Date birthday = null,
+                                  String bio = null,
+                                  String homepage = null,
+                                  Date timeZone = null,
+                                  String country = null,
+                                  String city = null,
+                                  String jabber = null,
+                                  String site = null,
+                                  String sex = null) {
+}
+
+createRegisteredPerson('name', 'pswd', 'email', 'ip', 'role', 'firstName', 'lastName', null, 'bio', 'page')
+
+def foo(String a, Date b = null, int i = -1, String c, String d = 'd', String e, String f) {}
+
+
+foo('aa', 'cc', 'dd', 'ee')
+foo('a', null, 'c', 'e')
+foo('a', null, 'd', 'c', 'e')
+
+  foo<warning descr="'foo' in 'MethodCallWithDefaultParameters' cannot be applied to '(java.lang.String, null, java.lang.Integer, java.lang.String, java.lang.String)'">('a', null, 1, 'c', 'e')</warning>
+  foo<warning descr="'foo' in 'MethodCallWithDefaultParameters' cannot be applied to '(java.lang.String, java.lang.String, java.lang.String)'">("aa", 'cc', 'ee')</warning>
+
+foo('aa', null, 'cc', 'dd', 'ee')
+  foo<warning descr="'foo' in 'MethodCallWithDefaultParameters' cannot be applied to '(java.lang.String, null, java.lang.Integer, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)'">('aa', null, 1, 'cc', 'dd', 'ee', 'f', 'g')</warning>
+
+foo('a', 'cc', 'dd', 'ee')
+  foo<warning descr="'foo' in 'MethodCallWithDefaultParameters' cannot be applied to '(java.lang.String, java.lang.Integer, java.lang.String, java.lang.String, java.lang.String)'">('a', -1, 'cc', 'dd', 'ee')</warning>
+
+
+def bar(String a, Date b = null, int i = -1, String c, String d = 'd', String e, String... f) {}
+
+
+bar('aa', 'cc', 'dd', 'ee')
+bar('a', null, 'c', 'e')
+bar('a', null, 'd', 'c', 'e')
+
+bar('a', null, 1, 'c', 'e')
+bar("aa", 'cc', 'ee')
+
+bar('aa', null, 'cc', 'dd', 'ee')
+bar('aa', null, 1, 'cc', 'dd', 'ee', 'f', 'g')
+
+bar('a', 'cc', 'dd', 'ee')
+  bar<warning descr="'bar' in 'MethodCallWithDefaultParameters' cannot be applied to '(java.lang.String, java.lang.Integer, java.lang.String, java.lang.String, java.lang.String)'">('a', -1, 'cc', 'dd', 'ee')</warning>
+
+def go(String a, String b = 'b', String c, int ... i) {}
+
+go('a', 'c', 1, 2, 3);
\ No newline at end of file
diff --git a/plugins/groovy/testdata/resolve/method/optionalParameters1/A.groovy b/plugins/groovy/testdata/resolve/method/optionalParameters1/A.groovy
new file mode 100644 (file)
index 0000000..0f42ce9
--- /dev/null
@@ -0,0 +1,22 @@
+class RegisteredPerson{}
+public RegisteredPerson createRegisteredPerson(String username,
+                                               String password,
+                                               String email,
+                                               String ipAddress,
+                                               String roleName = null,
+                                               String firstName = null,
+                                               String lastName = null,
+                                               Date birthday = null,
+                                               String bio = null,
+                                               String homepage = null,
+                                               TimeZone timeZone = null,
+                                               String country = null,
+                                               String city = null,
+                                               String jabber = null,
+                                               String site = null,
+                                               String sex = null)
+{
+    return null;
+}
+
+createRe<ref>gisteredPerson('username', 'pswd', 'email', 'ip', 'role', 'name', 'family', null, 'bio', 'page')
\ No newline at end of file
diff --git a/plugins/groovy/testdata/resolve/method/optionalParameters2/A.groovy b/plugins/groovy/testdata/resolve/method/optionalParameters2/A.groovy
new file mode 100644 (file)
index 0000000..e065941
--- /dev/null
@@ -0,0 +1,3 @@
+def foo(String a, String b = 'b', String c = 'c', int i = 1, String d) {}
+
+f<ref>oo('a', 'b', 2, 'd')
\ No newline at end of file
diff --git a/plugins/groovy/testdata/resolve/method/optionalParameters3/A.groovy b/plugins/groovy/testdata/resolve/method/optionalParameters3/A.groovy
new file mode 100644 (file)
index 0000000..47a611c
--- /dev/null
@@ -0,0 +1,3 @@
+def foo(String a, String b = 'b', String c = 'c', int i = 1, String d) {}
+
+f<ref>oo('a', 'b', 'd')
\ No newline at end of file
diff --git a/plugins/groovy/testdata/resolve/method/optionalParameters4/A.groovy b/plugins/groovy/testdata/resolve/method/optionalParameters4/A.groovy
new file mode 100644 (file)
index 0000000..dc5ecfd
--- /dev/null
@@ -0,0 +1,3 @@
+def foo(String a, String b = 'b', int i = 1, String d, String c = 'c') {}
+
+f<ref>oo('a', 'bb', 'd', 'cc')
\ No newline at end of file