PsiType evaluateType(final PsiExpression expr, final ReductionSystem system) {
    if (expr instanceof PsiArrayAccessExpression && !mySettings.preserveRawArrays()) {
      final PsiType at = evaluateType(((PsiArrayAccessExpression)expr).getArrayExpression(), system);

      if (at instanceof PsiArrayType) {
        return ((PsiArrayType)at).getComponentType();
      }
    }
    else if (expr instanceof PsiAssignmentExpression) {
      return evaluateType(((PsiAssignmentExpression)expr).getLExpression(), system);
    }
    else if (expr instanceof PsiCallExpression call) {
      final PsiMethod method = call.resolveMethod();

      if (method != null) {
        final PsiClass aClass = method.getContainingClass();
        final PsiTypeParameter[] methodTypeParameters = method.getTypeParameters();
        final PsiParameter[] parameters = method.getParameterList().getParameters();
        final PsiExpression[] arguments = call.getArgumentList().getExpressions();
        final PsiExpression qualifier =
          expr instanceof PsiMethodCallExpression ? ((PsiMethodCallExpression)expr).getMethodExpression().getQualifierExpression() : null;

        final Set<PsiTypeParameter> typeParameters = Set.of(methodTypeParameters);

        PsiSubstitutor qualifierSubstitutor = PsiSubstitutor.EMPTY;
        PsiSubstitutor supertypeSubstitutor = PsiSubstitutor.EMPTY;

        PsiType aType;

        if (method.isConstructor()) {
          if (expr instanceof PsiNewExpression) {
            aType = isCooked(expr) ? getType(expr) : expr.getType();
            qualifierSubstitutor = Util.resolveType(aType).getSubstitutor();
          }
          else {
            LOG.assertTrue(expr instanceof PsiMethodCallExpression); //either this(); or super();
            final PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)expr).getMethodExpression();
            if (PsiKeyword.THIS.equals(methodExpression.getText())) {
              aType = JavaPsiFacade.getElementFactory(myManager.getProject()).createType(aClass);
            }
            else {
              LOG.assertTrue(PsiKeyword.SUPER.equals(methodExpression.getText()));
              PsiClass placeClass = PsiTreeUtil.getParentOfType(expr, PsiClass.class);
              qualifierSubstitutor = TypeConversionUtil.getClassSubstitutor(aClass, placeClass, PsiSubstitutor.EMPTY);
              aType = JavaPsiFacade.getElementFactory(myManager.getProject()).createType(aClass, qualifierSubstitutor);
            }
          }
        }
        else {
          aType = getType(method);
        }

        if (qualifier != null) {
          final PsiType qualifierType = evaluateType(qualifier, system);
          final PsiClassType.ClassResolveResult result = Util.resolveType(qualifierType);

          if (result.getElement() != null) {
            final PsiClass qualifierClass = result.getElement();

            qualifierSubstitutor = TypeConversionUtil.getClassSubstitutor(aClass, qualifierClass, result.getSubstitutor());

            if (qualifierSubstitutor != null) {
              aType = qualifierSubstitutor.substitute(aType);
            }
          }
        }

        final Map<PsiTypeParameter, PsiType> mapping = new HashMap<>();

        for (int i = 0; i < Math.min(parameters.length, arguments.length); i++) {
          final PsiType argumentType = evaluateType(arguments[i], system);

          PsiType paramType;

          if (isCooked(parameters[i])) {
            paramType = getType(parameters[i]);
            system.addSubtypeConstraint(argumentType, paramType);
          }
          else {
            paramType = parameters[i].getType();
            if (qualifierSubstitutor != null) {
              paramType = qualifierSubstitutor.substitute(paramType);
            }

            if (!Util.bindsTypeVariables(paramType) && !Util.bindsTypeParameters(paramType, typeParameters)) {
              paramType = Util.banalize(paramType);
            }

            final PsiType theType = new Object() {
              PsiType introduceAdditionalTypeVariables(final PsiType type, final PsiSubstitutor qualifier, final PsiSubstitutor supertype) {
                final int level = type.getArrayDimensions();
                final PsiClassType.ClassResolveResult result = Util.resolveType(type);
                final PsiClass aClass = result.getElement();

                if (aClass != null) {
                  if (aClass instanceof PsiTypeParameter tp) {
                    final PsiClassType[] exTypes = tp.getExtendsListTypes();

                    PsiType pv = mapping.get(tp);

                    if (pv == null) {
                      pv = myTypeVariableFactory.create();
                      mapping.put(tp, pv);
                    }

                    for (final PsiClassType ext : exTypes) {
                      final PsiType exType = qualifier.substitute(new Object() {
                        public PsiType substitute(final PsiType ext) {
                          final PsiClassType.ClassResolveResult result = Util.resolveType(ext);
                          final PsiClass aClass = result.getElement();

                          if (aClass != null) {
                            if (aClass instanceof PsiTypeParameter) {
                              final PsiType type = mapping.get(aClass);

                              if (type != null) {
                                return type;
                              }

                              return ext;
                            }

                            final PsiSubstitutor aSubst = result.getSubstitutor();
                            PsiSubstitutor theSubst = PsiSubstitutor.EMPTY;

                            for (final PsiTypeParameter param : aSubst.getSubstitutionMap().keySet()) {
                              PsiType type = aSubst.substitute(param);

                              if (type != null) {
                                if (type instanceof PsiWildcardType wildcard) {
                                  final PsiType bound = wildcard.getBound();
                                  if (bound != null) {
                                    final PsiManager manager = param.getManager();
                                    type = wildcard.isExtends()
                                           ? PsiWildcardType.createExtends(manager, substitute(bound))
                                           : PsiWildcardType.createSuper(manager, substitute(bound));
                                  }
                                }
                                else {
                                  type = substitute(type);
                                }
                              }

                              theSubst = theSubst.put(param, type);
                            }

                            return JavaPsiFacade.getElementFactory(aClass.getProject())
                              .createType(aClass, theSubst);
                          }

                          return ext;
                        }
                      }.substitute(ext));
                      system.addSubtypeConstraint(pv, exType);
                    }

                    return PsiTypesUtil.createArrayType(pv, level);
                  }

                  final Map<PsiTypeParameter, PsiType> substitutionMap = result.getSubstitutor().getSubstitutionMap();

                  PsiSubstitutor theSubst = PsiSubstitutor.EMPTY;

                  for (final PsiTypeParameter p : substitutionMap.keySet()) {
                    final PsiType pType = substitutionMap.get(p);

                    if (pType instanceof PsiWildcardType wildcard) {
                      final PsiType theBound = wildcard.getBound();

                      if (theBound != null) {
                        final PsiType bound = qualifier.substitute(supertype.substitute(theBound));

                        if (Util.bindsTypeVariables(bound)) {
                          final PsiType var = myTypeVariableFactory.create();

                          if (wildcard.isExtends()) {
                            system.addSubtypeConstraint(var, bound);
                          }
                          else {
                            system.addSubtypeConstraint(bound, var);
                          }

                          theSubst = theSubst.put(p, var);
                        }
                        else if (Util.bindsTypeParameters(bound, typeParameters)) {
                          final PsiType var = myTypeVariableFactory.create();
                          PsiSubstitutor subst = PsiSubstitutor.EMPTY;

                          for (final PsiTypeParameter typeParam : methodTypeParameters) {
                            PsiType paramVar = mapping.get(typeParam);

                            if (paramVar == null) {
                              paramVar = myTypeVariableFactory.create();
                              mapping.put(typeParam, paramVar);
                            }

                            subst = subst.put(typeParam, paramVar);
                          }

                          final PsiType bnd = subst.substitute(bound);

                          if (wildcard.isExtends()) {
                            system.addSubtypeConstraint(bnd, var);
                          }
                          else {
                            system.addSubtypeConstraint(var, bnd);
                          }

                          theSubst = theSubst.put(p, var);
                        }
                        else {
                          theSubst = theSubst.put(p, pType);
                        }
                      }
                      else {
                        theSubst = theSubst.put(p, pType);
                      }
                    }
                    else if (pType != null) {
                      theSubst = theSubst.put(p, introduceAdditionalTypeVariables(pType, qualifier, supertype));
                    }
                  }

                  return PsiTypesUtil.createArrayType(JavaPsiFacade.getElementFactory(aClass.getProject()).createType(aClass, theSubst), level);
                }

                return PsiTypesUtil.createArrayType(type, level);
              }
            }.introduceAdditionalTypeVariables(paramType, qualifierSubstitutor, supertypeSubstitutor);

            system.addSubtypeConstraint(argumentType, theType);
          }
        }

        PsiSubstitutor theSubst = PsiSubstitutor.EMPTY;

        for (final PsiTypeParameter param : mapping.keySet()) {
          final PsiType type = mapping.get(param);

          theSubst = theSubst.put(param, type);
        }

        for (PsiTypeParameter typeParam : methodTypeParameters) {
          final PsiType inferred = inferTypeForMethodTypeParameter(typeParam, parameters, arguments, theSubst, expr, system);
          theSubst = theSubst.put(typeParam, inferred);
        }

        return theSubst.substitute(aType);
      }
    }
    else if (expr instanceof PsiParenthesizedExpression) {
      return evaluateType(((PsiParenthesizedExpression)expr).getExpression(), system);
    }
    else if (expr instanceof PsiConditionalExpression) {
      return evaluateType(((PsiConditionalExpression)expr).getThenExpression(), system);
    }
    else if (expr instanceof PsiReferenceExpression ref) {
      final PsiExpression qualifier = ref.getQualifierExpression();

      if (qualifier == null) {
        return getType(ref.resolve());
      }
      else {
        final PsiType qualifierType = evaluateType(qualifier, system);
        final PsiElement element = ref.resolve();

        final PsiClassType.ClassResolveResult result = Util.resolveType(qualifierType);

        if (result.getElement() != null) {
          final PsiClass aClass = result.getElement();
          final PsiSubstitutor aSubst = result.getSubstitutor();

          if (element instanceof PsiField field) {
            final PsiType fieldType = getType(field);
            final PsiClass superClass = field.getContainingClass();

            PsiType aType = fieldType;

            if (!aClass.equals(superClass) && field.isPhysical()) {
              aType = TypeConversionUtil.getSuperClassSubstitutor(superClass, aClass, PsiSubstitutor.EMPTY).substitute(aType);
            }

            return aSubst.substitute(aType);
          }
        }
        else if (element != null) {
          return getType(element);
        }
      }
    }

    return getType(expr);
  }
PsiType evaluateType(final PsiExpression expr, final ReductionSystem system)