@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;
  }
 @NotNull
 public QualifiedResolveResult followAssignmentsChain(PyResolveContext resolveContext) {
   PyReferenceExpression seeker = this;
   QualifiedResolveResult ret = null;
   List<PyExpression> qualifiers = new ArrayList<PyExpression>();
   PyExpression qualifier = seeker.getQualifier();
   if (qualifier != null) {
     qualifiers.add(qualifier);
   }
   Set<PsiElement> visited = new HashSet<PsiElement>();
   visited.add(this);
   SEARCH:
   while (ret == null) {
     ResolveResult[] targets = seeker.getReference(resolveContext).multiResolve(false);
     for (ResolveResult target : targets) {
       PsiElement elt = target.getElement();
       if (elt instanceof PyTargetExpression) {
         PsiElement assigned_from = null;
         final PyTargetExpression expr = (PyTargetExpression) elt;
         final TypeEvalContext context = resolveContext.getTypeEvalContext();
         if (context.maySwitchToAST(expr) || expr.getStub() == null) {
           assigned_from = expr.findAssignedValue();
         }
         // TODO: Maybe findAssignedValueByStub() should become a part of the PyTargetExpression
         // interface
         else if (elt instanceof PyTargetExpressionImpl) {
           assigned_from = ((PyTargetExpressionImpl) elt).findAssignedValueByStub(context);
         }
         if (assigned_from instanceof PyReferenceExpression) {
           if (visited.contains(assigned_from)) {
             break;
           }
           visited.add(assigned_from);
           seeker = (PyReferenceExpression) assigned_from;
           if (seeker.getQualifier() != null) {
             qualifiers.add(seeker.getQualifier());
           }
           continue SEARCH;
         } else if (assigned_from != null)
           ret = new QualifiedResolveResultImpl(assigned_from, qualifiers, false);
       } else if (ret == null && elt instanceof PyElement && target.isValidResult()) {
         // remember this result, but a further reference may be the next resolve result
         ret =
             new QualifiedResolveResultImpl(
                 elt, qualifiers, target instanceof ImplicitResolveResult);
       }
     }
     // all resolve results checked, reassignment not detected, nothing more to do
     break;
   }
   if (ret == null) ret = EMPTY_RESULT;
   return ret;
 }
 @NotNull
 private static List<PyType> getIndexTypes(
     @NotNull PySubscriptionExpression expression, @NotNull Context context) {
   final List<PyType> types = new ArrayList<>();
   final PyExpression indexExpr = expression.getIndexExpression();
   if (indexExpr instanceof PyTupleExpression) {
     final PyTupleExpression tupleExpr = (PyTupleExpression) indexExpr;
     for (PyExpression expr : tupleExpr.getElements()) {
       types.add(getType(expr, context));
     }
   } else if (indexExpr != null) {
     types.add(getType(indexExpr, context));
   }
   return types;
 }
 @Nullable
 private static PyType getCallableType(@NotNull PsiElement resolved, @NotNull Context context) {
   if (resolved instanceof PySubscriptionExpression) {
     final PySubscriptionExpression subscriptionExpr = (PySubscriptionExpression) resolved;
     final PyExpression operand = subscriptionExpr.getOperand();
     final Collection<String> operandNames =
         resolveToQualifiedNames(operand, context.getTypeContext());
     if (operandNames.contains("typing.Callable")) {
       final PyExpression indexExpr = subscriptionExpr.getIndexExpression();
       if (indexExpr instanceof PyTupleExpression) {
         final PyTupleExpression tupleExpr = (PyTupleExpression) indexExpr;
         final PyExpression[] elements = tupleExpr.getElements();
         if (elements.length == 2) {
           final PyExpression parametersExpr = elements[0];
           final PyExpression returnTypeExpr = elements[1];
           if (parametersExpr instanceof PyListLiteralExpression) {
             final List<PyCallableParameter> parameters = new ArrayList<>();
             final PyListLiteralExpression listExpr = (PyListLiteralExpression) parametersExpr;
             for (PyExpression argExpr : listExpr.getElements()) {
               parameters.add(new PyCallableParameterImpl(null, getType(argExpr, context)));
             }
             final PyType returnType = getType(returnTypeExpr, context);
             return new PyCallableTypeImpl(parameters, returnType);
           }
           if (isEllipsis(parametersExpr)) {
             return new PyCallableTypeImpl(null, getType(returnTypeExpr, context));
           }
         }
       }
     }
   }
   return null;
 }
 @Nullable
 private PyType resolveQualifierType(
     @NotNull List<Token<PyElementType>> tokens,
     @NotNull PyFile file,
     @NotNull TypeEvalContext context,
     @NotNull Map<TextRange, PyType> types,
     @NotNull Map<PyType, TextRange> fullRanges,
     @NotNull Map<PyType, PyImportElement> imports) {
   if (tokens.isEmpty()) {
     return null;
   }
   final Token<PyElementType> firstToken = tokens.get(0);
   final String firstText = firstToken.getText().toString();
   final TextRange firstRange = firstToken.getRange();
   final List<RatedResolveResult> resolveResults = file.multiResolveName(firstText);
   if (resolveResults.isEmpty()) {
     return getImplicitlyResolvedType(tokens, context, types, fullRanges, firstRange);
   }
   final List<PyType> members = Lists.newArrayList();
   for (RatedResolveResult result : resolveResults) {
     final PsiElement resolved = result.getElement();
     PyType type = null;
     if (resolved instanceof PyTargetExpression) {
       type =
           PyTypingTypeProvider.getTypeFromTargetExpression(
               (PyTargetExpression) resolved, context);
     }
     if (type == null && resolved instanceof PyTypedElement) {
       type = context.getType((PyTypedElement) resolved);
     }
     if (type != null) {
       if (!allowResolveToType(type)) {
         continue;
       }
       if (type instanceof PyClassLikeType) {
         type = ((PyClassLikeType) type).toInstance();
       }
       types.put(firstRange, type);
       fullRanges.put(type, firstRange);
       for (PyFromImportStatement fromImportStatement : file.getFromImports()) {
         for (PyImportElement importElement : fromImportStatement.getImportElements()) {
           if (firstText.equals(importElement.getVisibleName())) {
             imports.put(type, importElement);
           }
         }
       }
       for (PyImportElement importElement : file.getImportTargets()) {
         if (firstText.equals(importElement.getVisibleName())) {
           imports.put(type, importElement);
         }
       }
     }
     members.add(type);
   }
   if (!members.isEmpty()) {
     tokens.remove(0);
   }
   return PyUnionType.union(members);
 }
 @Nullable
 private static PyType getType(@NotNull PyExpression expression, @NotNull Context context) {
   final List<PyType> members = Lists.newArrayList();
   for (PsiElement resolved : tryResolving(expression, context.getTypeContext())) {
     members.add(getTypeForResolvedElement(resolved, context));
   }
   return PyUnionType.union(members);
 }
 @Nullable
 private static PyType getGenericTypeBound(
     @NotNull PyExpression[] typeVarArguments, @NotNull Context context) {
   final List<PyType> types = new ArrayList<>();
   for (int i = 1; i < typeVarArguments.length; i++) {
     types.add(getType(typeVarArguments[i], context));
   }
   return PyUnionType.union(types);
 }
  public PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) {
    if (!TypeEvalStack.mayEvaluate(this)) {
      return null;
    }
    try {
      final boolean qualified = isQualified();
      if (!qualified) {
        String name = getReferencedName();
        if (PyNames.NONE.equals(name)) {
          return PyNoneType.INSTANCE;
        }
      }
      PyType type = getTypeFromProviders(context);
      if (type != null) {
        return type;
      }
      if (qualified) {
        PyType maybe_type = PyUtil.getSpecialAttributeType(this, context);
        if (maybe_type != null) return maybe_type;
        Ref<PyType> typeOfProperty = getTypeOfProperty(context);
        if (typeOfProperty != null) {
          return typeOfProperty.get();
        }
      }
      final PsiPolyVariantReference reference =
          getReference(PyResolveContext.noImplicits().withTypeEvalContext(context));
      final List<PsiElement> targets = PyUtil.multiResolveTopPriority(reference);
      if (targets.isEmpty()) {
        return getQualifiedReferenceTypeByControlFlow(context);
      }

      final List<PyType> members = new ArrayList<PyType>();
      for (PsiElement target : targets) {
        if (target == this || target == null) {
          continue;
        }
        if (!target.isValid()) {
          LOG.error(
              "Reference "
                  + this
                  + " resolved to invalid element "
                  + target
                  + " (text="
                  + target.getText()
                  + ")");
          continue;
        }
        members.add(getTypeFromTarget(target, context, this));
      }

      return PyUnionType.union(members);
    } finally {
      TypeEvalStack.evaluated(this);
    }
  }
 @NotNull
 private static List<PsiElement> tryResolving(
     @NotNull PyExpression expression, @NotNull TypeEvalContext context) {
   final List<PsiElement> elements = Lists.newArrayList();
   if (expression instanceof PyReferenceExpression) {
     final PyReferenceExpression referenceExpr = (PyReferenceExpression) expression;
     final PyResolveContext resolveContext =
         PyResolveContext.noImplicits().withTypeEvalContext(context);
     final PsiPolyVariantReference reference = referenceExpr.getReference(resolveContext);
     final List<PsiElement> resolved = PyUtil.multiResolveTopPriority(reference);
     for (PsiElement element : resolved) {
       if (element instanceof PyFunction) {
         final PyFunction function = (PyFunction) element;
         if (PyUtil.isInit(function)) {
           final PyClass cls = function.getContainingClass();
           if (cls != null) {
             elements.add(cls);
             continue;
           }
         }
       } else if (element instanceof PyTargetExpression) {
         final PyTargetExpression targetExpr = (PyTargetExpression) element;
         // XXX: Requires switching from stub to AST
         final PyExpression assignedValue = targetExpr.findAssignedValue();
         if (assignedValue != null) {
           elements.add(assignedValue);
           continue;
         }
       }
       if (element != null) {
         elements.add(element);
       }
     }
   }
   return !elements.isEmpty() ? elements : Collections.singletonList(expression);
 }
  /**
   * Moves methods (as opposite to {@link #makeMethodsAbstract(java.util.Collection,
   * com.jetbrains.python.psi.PyClass...)})
   *
   * @param from source
   * @param methodsToMove what to move
   * @param to where
   * @return newly added methods
   */
  private static List<PyElement> moveMethods(
      final PyClass from, final Collection<PyFunction> methodsToMove, final PyClass... to) {
    final List<PyElement> result = new ArrayList<PyElement>();
    for (final PyClass destClass : to) {
      // We move copies here because there may be several destinations
      final List<PyFunction> copies = new ArrayList<PyFunction>(methodsToMove.size());
      for (final PyFunction element : methodsToMove) {
        final PyFunction newMethod = (PyFunction) element.copy();
        copies.add(newMethod);
      }

      result.addAll(PyClassRefactoringUtil.copyMethods(copies, destClass));
    }
    deleteElements(methodsToMove);

    PyClassRefactoringUtil.insertPassIfNeeded(from);
    return result;
  }
 private boolean smartIntroduce(final IntroduceOperation operation) {
   final Editor editor = operation.getEditor();
   final PsiFile file = operation.getFile();
   int offset = editor.getCaretModel().getOffset();
   PsiElement elementAtCaret = file.findElementAt(offset);
   if (!checkIntroduceContext(file, editor, elementAtCaret)) return true;
   final List<PyExpression> expressions = new ArrayList<PyExpression>();
   while (elementAtCaret != null) {
     if (elementAtCaret instanceof PyStatement || elementAtCaret instanceof PyFile) {
       break;
     }
     if (elementAtCaret instanceof PyExpression && isValidIntroduceVariant(elementAtCaret)) {
       expressions.add((PyExpression) elementAtCaret);
     }
     elementAtCaret = elementAtCaret.getParent();
   }
   if (expressions.size() == 1 || ApplicationManager.getApplication().isUnitTestMode()) {
     operation.setElement(expressions.get(0));
     performActionOnElement(operation);
     return true;
   } else if (expressions.size() > 1) {
     IntroduceTargetChooser.showChooser(
         editor,
         expressions,
         new Pass<PyExpression>() {
           @Override
           public void pass(PyExpression pyExpression) {
             operation.setElement(pyExpression);
             performActionOnElement(operation);
           }
         },
         new Function<PyExpression, String>() {
           public String fun(PyExpression pyExpression) {
             return pyExpression.getText();
           }
         });
     return true;
   }
   return false;
 }
 /** @param self should be this */
 @NotNull
 private static List<PyAssignmentStatement> findAttributesStatic(@NotNull final PsiElement self) {
   final List<PyAssignmentStatement> result = new ArrayList<>();
   for (final PyAssignmentStatement statement :
       new PsiQuery(self).siblings(PyAssignmentStatement.class).getElements()) {
     for (final PyQualifiedExpression targetExpression :
         new PsiQuery(statement.getTargets()).filter(PyQualifiedExpression.class).getElements()) {
       final PyExpression qualifier = targetExpression.getQualifier();
       if (qualifier == null) {
         continue;
       }
       final PsiReference qualifierReference = qualifier.getReference();
       if (qualifierReference == null) {
         continue;
       }
       if (qualifierReference.isReferenceTo(self)) {
         result.add(statement);
       }
     }
   }
   return result;
 }
  @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;
    }
  }
  /** @param anchor should never be null or null will be returned */
  @NotNull
  public static ParseResult parse(@Nullable final PsiElement anchor, @NotNull String type) {
    PyPsiUtils.assertValid(anchor);
    if (anchor == null) {
      return EMPTY_RESULT;
    }

    final ForwardDeclaration<ParseResult, PyElementType> typeExpr = ForwardDeclaration.create();

    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> tupleType =
        op("(")
            .skipThen(typeExpr)
            .then(many(op(",").skipThen(typeExpr)))
            .thenSkip(op(")"))
            .map(
                value -> {
                  ParseResult result = value.getFirst();
                  final List<ParseResult> rest = value.getSecond();
                  if (rest.isEmpty()) {
                    return result;
                  }
                  final List<PyType> types = new ArrayList<>();
                  types.add(result.getType());
                  for (ParseResult r : rest) {
                    result = result.merge(r);
                    types.add(r.getType());
                  }
                  return result.withType(PyTupleType.create(anchor, types));
                })
            .named("tuple-type");

    final FunctionalParser<ParseResult, PyElementType> typeParameter =
        token(PARAMETER)
            .then(maybe(op("<=").skipThen(typeExpr)))
            .map(
                value -> {
                  final Token<PyElementType> token = value.getFirst();
                  final String name = token.getText().toString();
                  final TextRange range = token.getRange();
                  final ParseResult boundResult = value.getSecond();
                  if (boundResult != null) {
                    final PyGenericType type1 = new PyGenericType(name, boundResult.getType());
                    final ParseResult result = new ParseResult(null, type1, range);
                    return result.merge(boundResult).withType(type1);
                  }
                  return new ParseResult(null, new PyGenericType(name, null), range);
                })
            .named("type-parameter");

    final FunctionalParser<ParseResult, PyElementType> simpleExpr =
        classType.or(tupleType).or(typeParameter).named("simple-expr");

    final FunctionalParser<ParseResult, PyElementType> paramExpr =
        classType
            .thenSkip(op("["))
            .then(typeExpr)
            .then(many(op(",").skipThen(typeExpr)))
            .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 PyType firstType = first.getType();
                  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);
                    }
                  }
                  if (firstType instanceof PyClassType) {
                    final PyType type1 =
                        new PyCollectionTypeImpl(
                            ((PyClassType) firstType).getPyClass(), false, elementTypes);
                    return result.withType(type1);
                  }
                  return EMPTY_RESULT;
                })
            .or(
                classType
                    .thenSkip(op("of"))
                    .then(simpleExpr)
                    .map(
                        value -> {
                          final ParseResult firstResult = value.getFirst();
                          final ParseResult secondResult = value.getSecond();
                          final ParseResult result = firstResult.merge(secondResult);
                          final PyType firstType = firstResult.getType();
                          final PyType secondType = secondResult.getType();
                          if (firstType != null) {
                            if (firstType instanceof PyClassType && secondType != null) {
                              return result.withType(
                                  new PyCollectionTypeImpl(
                                      ((PyClassType) firstType).getPyClass(),
                                      false,
                                      Collections.singletonList(secondType)));
                            }
                            return result.withType(firstType);
                          }
                          return EMPTY_RESULT;
                        }))
            .or(
                classType
                    .thenSkip(op("from"))
                    .then(simpleExpr)
                    .thenSkip(op("to"))
                    .then(simpleExpr)
                    .map(
                        value -> {
                          final Pair<ParseResult, ParseResult> firstPair = value.getFirst();
                          final ParseResult first = firstPair.getFirst();
                          final ParseResult second = firstPair.getSecond();
                          final ParseResult third = value.getSecond();
                          final PyType firstType = first.getType();
                          if (firstType instanceof PyClassType) {
                            final List<PyType> elementTypes =
                                Arrays.asList(second.getType(), third.getType());
                            final PyCollectionTypeImpl type1 =
                                new PyCollectionTypeImpl(
                                    ((PyClassType) firstType).getPyClass(), false, elementTypes);
                            return first.merge(second).merge(third).withType(type1);
                          }
                          return EMPTY_RESULT;
                        }))
            .named("param-expr");

    final FunctionalParser<ParseResult, PyElementType> callableExpr =
        op("(")
            .skipThen(maybe(typeExpr.then(many(op(",").skipThen(typeExpr)))))
            .thenSkip(op(")"))
            .thenSkip(op("->"))
            .then(typeExpr)
            .map(
                value -> {
                  final List<PyCallableParameter> parameters = new ArrayList<>();
                  final ParseResult returnResult = value.getSecond();
                  ParseResult result;
                  final Pair<ParseResult, List<ParseResult>> firstPair = value.getFirst();
                  if (firstPair != null) {
                    final ParseResult first = firstPair.getFirst();
                    final List<ParseResult> second = firstPair.getSecond();
                    result = first;
                    parameters.add(new PyCallableParameterImpl(null, first.getType()));
                    for (ParseResult r : second) {
                      result = result.merge(r);
                      parameters.add(new PyCallableParameterImpl(null, r.getType()));
                    }
                    result = result.merge(returnResult);
                  } else {
                    result = returnResult;
                  }
                  return result.withType(
                      new PyCallableTypeImpl(parameters, returnResult.getType()));
                })
            .named("callable-expr");

    final FunctionalParser<ParseResult, PyElementType> singleExpr =
        paramExpr.or(callableExpr).or(simpleExpr).named("single-expr");

    final FunctionalParser<ParseResult, PyElementType> unionExpr =
        singleExpr
            .then(many(op("or").or(op("|")).skipThen(singleExpr)))
            .map(
                value -> {
                  final ParseResult first = value.getFirst();
                  final List<ParseResult> rest = value.getSecond();
                  if (rest.isEmpty()) {
                    return first;
                  }
                  final List<PyType> types = new ArrayList<>();
                  types.add(first.getType());
                  ParseResult result = first;
                  for (ParseResult r : rest) {
                    types.add(r.getType());
                    result = result.merge(r);
                  }
                  return result.withType(PyUnionType.union(types));
                })
            .named("union-expr");

    typeExpr.define(unionExpr).named("type-expr");

    final FunctionalParser<ParseResult, PyElementType> typeFile =
        typeExpr.endOfInput().named("type-file");

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