function getElementTypeOfIterable(type, errorNode) {
            if (isTypeAny(type)) {
                return undefined;
            }
            var typeAsIterable = type;
            if (!typeAsIterable.iterableElementType) {
                // As an optimization, if the type is instantiated directly using the globalIterableType (Iterable<number>),
                // then just grab its type argument.
                if ((type.flags & 4096 /* Reference */) && type.target === globalIterableType) {
                    typeAsIterable.iterableElementType = type.typeArguments[0];
                }
                else {
                    var iteratorFunction = getTypeOfPropertyOfType(type, ts.getPropertyNameForKnownSymbolName("iterator"));
                    if (isTypeAny(iteratorFunction)) {
                        return undefined;
                    }
                    var iteratorFunctionSignatures = iteratorFunction ? getSignaturesOfType(iteratorFunction, 0 /* Call */) : emptyArray;
                    if (iteratorFunctionSignatures.length === 0) {
                        if (errorNode) {
                            error(errorNode, ts.Diagnostics.Type_must_have_a_Symbol_iterator_method_that_returns_an_iterator);
                        }
                        return undefined;
                    }
                    typeAsIterable.iterableElementType = getElementTypeOfIterator(getUnionType(ts.map(iteratorFunctionSignatures, getReturnTypeOfSignature)), errorNode);
                }
            }
            return typeAsIterable.iterableElementType;
        }
We want to treat type as an iterable, and get the type it is an iterable of. The iterable
must have the following structure (annotated with the names of the variables below):

{ // iterable
    [Symbol.iterator]: { // iteratorFunction
        (): Iterator<T>
    }
}

T is the type we are after. At every level that involves analyzing return types
of signatures, we union the return types of all the signatures.

Another thing to note is that at any step of this process, we could run into a dead end,
meaning either the property is missing, or we run into the anyType. If either of these things
happens, we return undefined to signal that we could not find the iterated type. If a property
is missing, and the previous step did not result in 'any', then we also give an error if the
caller requested it. Then the caller can decide what to do in the case where there is no iterated
type. This is different from returning anyType, because that would signify that we have matched the
whole pattern and that T (above) is 'any'.