@Nullable
  public static PyType getType(@NotNull PsiElement resolved, @NotNull List<PyType> elementTypes) {
    final String qualifiedName = getQualifiedName(resolved);

    final List<Integer> paramListTypePositions = new ArrayList<>();
    final List<Integer> ellipsisTypePositions = new ArrayList<>();
    for (int i = 0; i < elementTypes.size(); i++) {
      final PyType type = elementTypes.get(i);
      if (type instanceof PyTypeParser.ParameterListType) {
        paramListTypePositions.add(i);
      } else if (type instanceof PyTypeParser.EllipsisType) {
        ellipsisTypePositions.add(i);
      }
    }

    if (!paramListTypePositions.isEmpty()) {
      if (!("typing.Callable".equals(qualifiedName) && paramListTypePositions.equals(list(0)))) {
        return null;
      }
    }
    if (!ellipsisTypePositions.isEmpty()) {
      if (!("typing.Callable".equals(qualifiedName) && ellipsisTypePositions.equals(list(0))
          || "typing.Tuple".equals(qualifiedName)
              && ellipsisTypePositions.equals(list(1))
              && elementTypes.size() == 2)) {
        return null;
      }
    }

    if ("typing.Union".equals(qualifiedName)) {
      return PyUnionType.union(elementTypes);
    }
    if ("typing.Optional".equals(qualifiedName) && elementTypes.size() == 1) {
      return PyUnionType.union(elementTypes.get(0), PyNoneType.INSTANCE);
    }
    if ("typing.Callable".equals(qualifiedName) && elementTypes.size() == 2) {
      final PyTypeParser.ParameterListType paramList =
          as(elementTypes.get(0), PyTypeParser.ParameterListType.class);
      if (paramList != null) {
        return new PyCallableTypeImpl(paramList.getCallableParameters(), elementTypes.get(1));
      }
      if (elementTypes.get(0) instanceof PyTypeParser.EllipsisType) {
        return new PyCallableTypeImpl(null, elementTypes.get(1));
      }
    }
    if ("typing.Tuple".equals(qualifiedName)) {
      if (elementTypes.size() > 1 && elementTypes.get(1) instanceof PyTypeParser.EllipsisType) {
        return PyTupleType.createHomogeneous(resolved, elementTypes.get(0));
      }
      return PyTupleType.create(resolved, elementTypes);
    }
    final PyType builtinCollection = getBuiltinCollection(resolved);
    if (builtinCollection instanceof PyClassType) {
      final PyClassType classType = (PyClassType) builtinCollection;
      return new PyCollectionTypeImpl(classType.getPyClass(), false, elementTypes);
    }
    return null;
  }
 @Nullable
 private Ref<PyType> getTypeOfProperty(
     @Nullable PyType qualifierType, @NotNull String name, @NotNull TypeEvalContext context) {
   if (qualifierType instanceof PyClassType) {
     final PyClassType classType = (PyClassType) qualifierType;
     PyClass pyClass = classType.getPyClass();
     Property property = pyClass.findProperty(name, true);
     if (property != null) {
       if (classType.isDefinition()) {
         return Ref.<PyType>create(
             PyBuiltinCache.getInstance(pyClass).getObjectType(PyNames.PROPERTY));
       }
       if (AccessDirection.of(this) == AccessDirection.READ) {
         final PyType type = property.getType(context);
         if (type != null) {
           return Ref.create(type);
         }
       }
       return Ref.create();
     }
   } else if (qualifierType instanceof PyUnionType) {
     final PyUnionType unionType = (PyUnionType) qualifierType;
     for (PyType type : unionType.getMembers()) {
       final Ref<PyType> result = getTypeOfProperty(type, name, context);
       if (result != null) {
         return result;
       }
     }
   }
   return null;
 }
 @Nullable
 private PyType replaceSelf(
     @Nullable PyType returnType,
     @Nullable PyExpression receiver,
     @NotNull TypeEvalContext context) {
   if (receiver != null) {
     // TODO: Currently we substitute only simple subclass types, but we could handle union and
     // collection types as well
     if (returnType instanceof PyClassType) {
       final PyClassType returnClassType = (PyClassType) returnType;
       if (returnClassType.getPyClass() == getContainingClass()) {
         final PyType receiverType = context.getType(receiver);
         if (receiverType instanceof PyClassType
             && PyTypeChecker.match(returnType, receiverType, context)) {
           return returnClassType.isDefinition()
               ? receiverType
               : ((PyClassType) receiverType).toInstance();
         }
       }
     }
   }
   return returnType;
 }
  @NotNull
  public static ParseResult parsePep484FunctionTypeComment(
      @NotNull PsiElement anchor, @NotNull String text) {
    final ForwardDeclaration<ParseResult, PyElementType> typeExpr = ForwardDeclaration.create();

    final Function<Pair<ParseResult, List<ParseResult>>, ParseResult> toParamTypeList =
        pair -> {
          if (pair != null) {
            final ParseResult first = pair.getFirst();
            final List<ParseResult> second = pair.getSecond();
            final List<PyType> itemTypes = new ArrayList<>();
            ParseResult result = first;
            itemTypes.add(result.getType());
            for (ParseResult r : second) {
              result = result.merge(r);
              itemTypes.add(r.getType());
            }
            return result.withType(ParameterListType.fromParameterTypes(itemTypes));
          }
          return EMPTY_RESULT.withType(new ParameterListType(Collections.emptyList()));
        };

    final FunctionalParser<ParseResult, PyElementType> ellipsis =
        op("...").map(token -> EMPTY_RESULT.withType(EllipsisType.INSTANCE)).named("ellipsis");

    final FunctionalParser<ParseResult, PyElementType> classType =
        token(IDENTIFIER)
            .then(many(op(".").skipThen(token(IDENTIFIER))))
            .map(new MakeSimpleType(anchor))
            .cached()
            .named("class-type");

    final FunctionalParser<ParseResult, PyElementType> typeParamList =
        op("[")
            .skipThen(maybe(typeExpr.then(many(op(",").skipThen(typeExpr)))))
            .thenSkip(op("]"))
            .map(toParamTypeList)
            .named("type-param-list");

    final FunctionalParser<ParseResult, PyElementType> typeParam =
        typeExpr.or(typeParamList).or(ellipsis).named("type-param");

    final FunctionalParser<ParseResult, PyElementType> genericType =
        classType
            .thenSkip(op("["))
            .then(typeParam)
            .then(many(op(",").skipThen(typeParam)))
            .thenSkip(op("]"))
            .map(
                value -> {
                  final Pair<ParseResult, ParseResult> firstPair = value.getFirst();
                  final ParseResult first = firstPair.getFirst();
                  final ParseResult second = firstPair.getSecond();
                  final List<ParseResult> third = value.getSecond();
                  final List<PyType> typesInBrackets = new ArrayList<>();
                  typesInBrackets.add(second.getType());
                  ParseResult result = first;
                  result = result.merge(second);
                  for (ParseResult r : third) {
                    typesInBrackets.add(r.getType());
                    result = result.merge(r);
                  }
                  final List<PyType> elementTypes =
                      third.isEmpty()
                          ? Collections.singletonList(second.getType())
                          : typesInBrackets;
                  final PsiElement resolved = first.getElement();
                  if (resolved != null) {
                    final PyType typingType = PyTypingTypeProvider.getType(resolved, elementTypes);
                    if (typingType != null) {
                      return result.withType(typingType);
                    }
                  }
                  return EMPTY_RESULT;
                })
            .named("generic-type");

    typeExpr.define(genericType.or(classType)).named("type-expr");

    final FunctionalParser<ParseResult, PyElementType> paramType =
        maybe(op("*"))
            .then(maybe(op("*")))
            .then(typeExpr)
            .map(
                pair -> {
                  final ParseResult paramResult = pair.getSecond();
                  final PyType type = paramResult.getType();
                  int starCount = 0;
                  if (pair.getFirst().getFirst() != null) {
                    starCount++;
                  }
                  if (pair.getFirst().getSecond() != null) {
                    starCount++;
                  }
                  if (starCount == 0) {
                    return paramResult;
                  } else if (starCount == 1) {
                    final PyClassType tupleType = PyTupleType.createHomogeneous(anchor, type);
                    if (tupleType != null) {
                      return paramResult.withType(tupleType);
                    }
                    return EMPTY_RESULT;
                  } else if (starCount == 2) {
                    final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(anchor);
                    final PyClassType dictType = builtinCache.getDictType();
                    if (dictType != null) {
                      final PyClass pyClass = dictType.getPyClass();
                      return paramResult.withType(
                          new PyCollectionTypeImpl(
                              pyClass, false, Arrays.asList(builtinCache.getStrType(), type)));
                    }
                    return EMPTY_RESULT;
                  }
                  return EMPTY_RESULT;
                })
            .named("param-type");

    final FunctionalParser<ParseResult, PyElementType> paramTypes =
        paramType.then(many(op(",").skipThen(paramType))).map(toParamTypeList).named("param-types");

    final FunctionalParser<ParseResult, PyElementType> funcType =
        op("(")
            .skipThen(maybe(paramTypes.or(ellipsis)))
            .thenSkip(op(")"))
            .thenSkip(op("->"))
            .then(typeExpr)
            .map(
                value -> {
                  final ParseResult paramsResult = value.getFirst();
                  final ParseResult returnResult = value.getSecond();
                  final List<PyCallableParameter> parameters;
                  ParseResult result = returnResult;
                  if (paramsResult != null) {
                    result = result.merge(paramsResult);
                    final ParameterListType paramTypesList =
                        as(paramsResult.getType(), ParameterListType.class);
                    if (paramTypesList != null) {
                      parameters = paramTypesList.getCallableParameters();
                    }
                    // ellipsis
                    else {
                      parameters = null;
                    }
                  } else {
                    parameters = Collections.emptyList();
                    result = returnResult;
                  }
                  return result.withType(
                      new PyCallableTypeImpl(parameters, returnResult.getType()));
                })
            .named("func-type");

    final FunctionalParser<ParseResult, PyElementType> typeFile =
        funcType.endOfInput().named("function-type-comment");

    try {
      return typeFile.parse(tokenize(text));
    } catch (ParserException e) {
      return EMPTY_RESULT;
    }
  }