@NotNull
  public TypeInfoForCall getQualifiedExpressionExtendedTypeInfo(
      @NotNull JetQualifiedExpression expression,
      @NotNull ResolutionContext context,
      @NotNull ResolveMode resolveMode) {
    // TODO : functions as values
    JetExpression selectorExpression = expression.getSelectorExpression();
    JetExpression receiverExpression = expression.getReceiverExpression();
    JetTypeInfo receiverTypeInfo =
        expressionTypingServices.getTypeInfoWithNamespaces(
            receiverExpression,
            context.scope,
            NO_EXPECTED_TYPE,
            context.dataFlowInfo,
            context.trace);
    JetType receiverType = receiverTypeInfo.getType();
    if (selectorExpression == null) return TypeInfoForCall.create(null, context.dataFlowInfo);
    if (receiverType == null)
      receiverType = ErrorUtils.createErrorType("Type for " + expression.getText());

    context = context.replaceDataFlowInfo(receiverTypeInfo.getDataFlowInfo());

    if (selectorExpression instanceof JetSimpleNameExpression) {
      ConstantUtils.propagateConstantValues(
          expression, context.trace, (JetSimpleNameExpression) selectorExpression);
    }

    TypeInfoForCall selectorReturnTypeInfo =
        getSelectorReturnTypeInfo(
            new ExpressionReceiver(receiverExpression, receiverType),
            expression.getOperationTokenNode(),
            selectorExpression,
            context,
            resolveMode);
    JetType selectorReturnType = selectorReturnTypeInfo.getType();

    // TODO move further
    if (!(receiverType instanceof NamespaceType)
        && expression.getOperationSign() == JetTokens.SAFE_ACCESS) {
      if (selectorReturnType != null
          && !selectorReturnType.isNullable()
          && !KotlinBuiltIns.getInstance().isUnit(selectorReturnType)) {
        if (receiverType.isNullable()) {
          selectorReturnType = TypeUtils.makeNullable(selectorReturnType);
        }
      }
    }

    // TODO : this is suspicious: remove this code?
    if (selectorReturnType != null) {
      context.trace.record(BindingContext.EXPRESSION_TYPE, selectorExpression, selectorReturnType);
    }
    JetTypeInfo typeInfo =
        JetTypeInfo.create(selectorReturnType, selectorReturnTypeInfo.getDataFlowInfo());
    if (resolveMode == ResolveMode.TOP_LEVEL_CALL) {
      DataFlowUtils.checkType(typeInfo.getType(), expression, context, typeInfo.getDataFlowInfo());
    }
    return TypeInfoForCall.create(typeInfo, selectorReturnTypeInfo);
  }
  private boolean typeMustBeNullable(
      @NotNull JetType autoType,
      @NotNull List<TypeAndVariance> typesFromSuper,
      @NotNull TypeUsage howThisTypeIsUsed) {
    boolean someSupersNotCovariantNullable = false;
    boolean someSupersCovariantNullable = false;
    boolean someSupersNotNull = false;
    for (TypeAndVariance typeFromSuper : typesFromSuper) {
      if (!typeFromSuper.type.isNullable()) {
        someSupersNotNull = true;
      } else {
        if (typeFromSuper.varianceOfPosition == Variance.OUT_VARIANCE) {
          someSupersCovariantNullable = true;
        } else {
          someSupersNotCovariantNullable = true;
        }
      }
    }

    if (someSupersNotNull && someSupersNotCovariantNullable) {
      reportError("Incompatible types in superclasses: " + typesFromSuper);
      return autoType.isNullable();
    } else if (someSupersNotNull) {
      return false;
    } else if (someSupersNotCovariantNullable || someSupersCovariantNullable) {
      boolean annotatedAsNotNull = howThisTypeIsUsed != TYPE_ARGUMENT && !autoType.isNullable();

      if (annotatedAsNotNull && someSupersNotCovariantNullable) {
        reportError(
            "In superclass type is nullable: "
                + typesFromSuper
                + ", in subclass it is not: "
                + autoType);
        return true;
      }

      return !annotatedAsNotNull;
    }
    return autoType.isNullable();
  }
  @NotNull
  private String renderDefaultType(@NotNull JetType type) {
    StringBuilder sb = new StringBuilder();

    sb.append(renderTypeName(type.getConstructor()));
    if (!type.getArguments().isEmpty()) {
      sb.append("<");
      appendTypeProjections(type.getArguments(), sb);
      sb.append(">");
    }
    if (type.isNullable()) {
      sb.append("?");
    }
    return sb.toString();
  }
  @NotNull
  private String renderFunctionType(@NotNull JetType type) {
    StringBuilder sb = new StringBuilder();

    JetType receiverType = KotlinBuiltIns.getInstance().getReceiverType(type);
    if (receiverType != null) {
      sb.append(renderType(receiverType));
      sb.append(".");
    }

    sb.append("(");
    appendTypeProjections(
        KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(type), sb);
    sb.append(") " + arrow() + " ");
    sb.append(renderType(KotlinBuiltIns.getInstance().getReturnTypeFromFunctionType(type)));

    if (type.isNullable()) {
      return "(" + sb + ")?";
    }
    return sb.toString();
  }