Substitution (@Nullable PsiType expected, @Nullable PsiType actual, @Nullable PsiType methodType, int level) { if (expected == null || actual == null || methodType == null) return Substitution.BOTTOM; if (expected instanceof PsiArrayType && actual instanceof PsiArrayType && methodType instanceof PsiArrayType) { return findDesiredSubstitution(((PsiArrayType)expected).getComponentType(), ((PsiArrayType)actual).getComponentType(), ((PsiArrayType)methodType).getComponentType(), level); } if (expected.equals(actual)) return Substitution.TOP; if (!(methodType instanceof PsiClassType)) return Substitution.BOTTOM; PsiClass methodClass = ((PsiClassType)methodType).resolve(); if (methodClass == null) return Substitution.BOTTOM; if (methodClass instanceof PsiTypeParameter) { if (!(expected instanceof PsiPrimitiveType)) { for (PsiClassType superType : methodClass.getSuperTypes()) { PsiSubstitutor substitutor = PsiSubstitutor.EMPTY.put((PsiTypeParameter)methodClass, expected); if (!substitutor.substitute(superType).isAssignableFrom(expected)) return Substitution.BOTTOM; } return new Substitution.TypeParameterSubstitution((PsiTypeParameter)methodClass, actual, expected); } return Substitution.BOTTOM; } if (!(expected instanceof PsiClassType) || !(actual instanceof PsiClassType)) return Substitution.BOTTOM; PsiClass expectedClass = ((PsiClassType)expected).resolve(); PsiClass actualClass = ((PsiClassType)actual).resolve(); if (expectedClass == null || actualClass == null || !actualClass.isEquivalentTo(methodClass)) return Substitution.BOTTOM; if (!expectedClass.isEquivalentTo(actualClass)) { if (level > 0) return Substitution.BOTTOM; methodType = trySubstitute(methodType, expectedClass); actual = trySubstitute(actual, expectedClass); if (methodType == null || actual == null) return Substitution.BOTTOM; } PsiType[] methodTypeParameters = ((PsiClassType)methodType).getParameters(); PsiType[] expectedTypeParameters = ((PsiClassType)expected).getParameters(); PsiType[] actualTypeParameters = ((PsiClassType)actual).getParameters(); if (methodTypeParameters.length != expectedTypeParameters.length || methodTypeParameters.length != actualTypeParameters.length) { return Substitution.BOTTOM; } Substitution existing = Substitution.TOP; for (int i = 0; i < methodTypeParameters.length; i++) { Substitution substitution = findDesiredSubstitution(expectedTypeParameters[i], actualTypeParameters[i], methodTypeParameters[i], level+1); if (existing == Substitution.TOP) { existing = substitution; } else if (substitution != Substitution.TOP && !existing.equals(substitution)) { return Substitution.BOTTOM; } } return existing; }