Binding (final PsiType x, final PsiType y, final Balancer balancer, final Set<? super Constraint> constraints) { final int indicator = (x instanceof PsiTypeVariable ? 1 : 0) + (y instanceof PsiTypeVariable ? 2 : 0); switch (indicator) { case 0 -> { if (x instanceof PsiWildcardType || y instanceof PsiWildcardType) { final PsiType xType = x instanceof PsiWildcardType ? ((PsiWildcardType)x).getBound() : x; final PsiType yType = y instanceof PsiWildcardType ? ((PsiWildcardType)y).getBound() : y; switch ((x instanceof PsiWildcardType ? 1 : 0) + (y instanceof PsiWildcardType ? 2 : 0)) { case 1 -> { if (((PsiWildcardType)x).isExtends()) { /* ? extends T1, T2 */ return null; } else { /* ? super T1, T2 */ if (xType != null && !CommonClassNames.JAVA_LANG_OBJECT.equals(xType.getCanonicalText())) { return null; } return create(); } } case 2 -> { if (((PsiWildcardType)y).isExtends()) { /* T1, ? extends T2 */ if (yType instanceof PsiTypeVariable) { final PsiTypeVariable beta = myFactory.create(); if (constraints != null) { constraints.add(new Subtype(beta, yType)); if (x != null) { constraints.add(new Subtype(x, yType)); } } return create(); } else { if (constraints != null && xType != null && yType != null) { constraints.add(new Subtype(xType, yType)); } return balance(xType, yType, balancer, constraints); } } else {/* T1, ? super T2 */ if (yType instanceof PsiTypeVariable) { final PsiTypeVariable beta = myFactory.create(); if (constraints != null) { if (x != null) constraints.add(new Subtype(x, beta)); constraints.add(new Subtype(yType, beta)); } return create(); } else { if (constraints != null && yType != null && xType != null) { constraints.add(new Subtype(yType, xType)); } return balance(xType, yType, balancer, constraints); } } } case 3 -> { switch ((((PsiWildcardType)x).isExtends() ? 0 : 1) + (((PsiWildcardType)y).isExtends() ? 0 : 2)) { case 0 -> { /* ? super T1, ? super T2 */ if (constraints != null && xType != null && yType != null) { constraints.add(new Subtype(yType, xType)); } return balance(xType, yType, balancer, constraints); } case 1 -> { /* ? extends T1, ? super T2 */ if (constraints != null && xType != null && yType != null) { constraints.add(new Subtype(xType, yType)); } return balance(xType, yType, balancer, constraints); } case 2 -> { /* ? super T1, ? extends T2*/ return null; } case 3 -> { /* ? extends T1, ? extends T2*/ if (constraints != null && xType != null && yType != null) { constraints.add(new Subtype(xType, yType)); } return balance(xType, yType, balancer, constraints); } } } } return create(); } else if (x instanceof PsiArrayType || y instanceof PsiArrayType) { final PsiType xType = x instanceof PsiArrayType ? ((PsiArrayType)x).getComponentType() : x; final PsiType yType = y instanceof PsiArrayType ? ((PsiArrayType)y).getComponentType() : y; return balance(xType, yType, balancer, constraints); } else if (x instanceof PsiClassType && y instanceof PsiClassType) { final PsiClassType.ClassResolveResult resultX = Util.resolveType(x); final PsiClassType.ClassResolveResult resultY = Util.resolveType(y); final PsiClass xClass = resultX.getElement(); final PsiClass yClass = resultY.getElement(); if (xClass != null && yClass != null) { final PsiSubstitutor ySubst = resultY.getSubstitutor(); PsiSubstitutor xSubst = TypeConversionUtil.getClassSubstitutor(yClass, xClass, resultX.getSubstitutor()); if (xSubst == null) return null; Binding b = create(); for (final PsiTypeParameter aParm : xSubst.getSubstitutionMap().keySet()) { final PsiType xType = xSubst.substitute(aParm); final PsiType yType = ySubst.substitute(aParm); final Binding b1 = unify(xType, yType, new Unifier() { @Override public Binding unify(final PsiType x, final PsiType y) { return balance(x, y, balancer, constraints); } }); if (b1 == null) { return null; } b = b.compose(b1); } return b; } } else if (y instanceof Bottom) { return create(); } else { return null; } } case 1 -> { return balancer.varType((PsiTypeVariable)x, y); } case 2 -> { return balancer.typeVar(x, (PsiTypeVariable)y); } case 3 -> { return balancer.varVar((PsiTypeVariable)x, (PsiTypeVariable)y); } } return null; }