Skip to content

Commit

Permalink
Pull out the logic to box an iterable/async iterable and get its temp…
Browse files Browse the repository at this point in the history
…late into a stand a lone function.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=231271817
  • Loading branch information
johnplaisted authored and tjgq committed Jan 29, 2019
1 parent 3197554 commit f0ae19c
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 28 deletions.
95 changes: 94 additions & 1 deletion src/com/google/javascript/jscomp/JsIterables.java
Expand Up @@ -15,12 +15,16 @@
*/
package com.google.javascript.jscomp;

import static com.google.javascript.rhino.jstype.JSTypeNative.ASYNC_ITERABLE_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.ITERABLE_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.UNKNOWN_TYPE;

import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.TemplateTypeMap;
import java.util.ArrayList;
import java.util.List;

/**
* Models type transformations of JavaScript `Iterable` and `Iterator` types.
Expand Down Expand Up @@ -66,7 +70,96 @@ static final JSType getElementType(JSType iterableOrIterator, JSTypeRegistry typ
*/
static final JSType createIterableTypeOf(JSType elementType, JSTypeRegistry typeRegistry) {
return typeRegistry.createTemplatizedType(
typeRegistry.getNativeObjectType(JSTypeNative.ITERABLE_TYPE), elementType);
typeRegistry.getNativeObjectType(ITERABLE_TYPE), elementType);
}

/**
* The template of an Iterable|AsyncIterable, or a mismatch if subtype of Iterable|AsyncIterable.
*/
static final class MaybeBoxedIterableOrAsyncIterable {
private final JSType templatedType;
private final JSType mismatchType;

/**
* @param templatedType the unwrapped type that is being yieled by the iterable / async
* iterable. e.g. in {@code Iterable<number>|AsyncIterable<Qux>|string}, this should be
* {@code number|Qux|string}. Null if a mismatch.
* @param mismatchType the type that caused the mismatch. This can be the entire type (e.g.
* {@code number} is not iterable or async iterable) or a piece of a union that caused the
* mismatch (e.g. {@code number} in {@code number|Iterable<Qux>}). Null if a match.
*/
private MaybeBoxedIterableOrAsyncIterable(JSType templatedType, JSType mismatchType) {
this.templatedType = templatedType;
this.mismatchType = mismatchType;
}

/**
* @return the template of the iterable / async iterable
* @throws if not an iterable / async iterable
*/
JSType getTemplatedType() {
if (!isMatch()) {
throw new IllegalStateException("Type was not boxable to iterable or async iterable!");
}
return templatedType;
}

/** @return the type that caused the mismatch, if any */
JSType getMismatchType() {
return mismatchType;
}

JSType orElse(JSType type) {
if (isMatch()) {
return templatedType;
}

return type;
}

boolean isMatch() {
return templatedType != null;
}
}

/**
* Given a type, if it is an iterable or async iterable, will return its template. If not a
* subtype of Iterable|AsyncIterable, returns an object that has no match, and will indicate the
* mismatch. e.g. both {@code number} and {@code number|Iterable} are not subtypes of
* Iterable|AsyncIterable.
*/
static final MaybeBoxedIterableOrAsyncIterable maybeBoxIterableOrAsyncIterable(
JSType type, JSTypeRegistry typeRegistry) {
List<JSType> templatedTypes = new ArrayList<>();

// Note: we don't just use JSType.autobox() here because that removes null and undefined.
// We want to keep null and undefined around because they should cause a mismatch.
if (type.isUnionType()) {
for (JSType alt : type.toMaybeUnionType().getAlternatesWithoutStructuralTyping()) {
alt = alt.isBoxableScalar() ? alt.autoboxesTo() : alt;
boolean isIterable = alt.isSubtypeOf(typeRegistry.getNativeType(ITERABLE_TYPE));
boolean isAsyncIterable = alt.isSubtypeOf(typeRegistry.getNativeType(ASYNC_ITERABLE_TYPE));
if (!isIterable && !isAsyncIterable) {
return new MaybeBoxedIterableOrAsyncIterable(null, alt);
}
JSTypeNative iterableType = isAsyncIterable ? ASYNC_ITERABLE_TYPE : ITERABLE_TYPE;
templatedTypes.add(
alt.getInstantiatedTypeArgument(typeRegistry.getNativeType(iterableType)));
}
} else {
JSType autoboxedType = type.isBoxableScalar() ? type.autoboxesTo() : type;
boolean isIterable = autoboxedType.isSubtypeOf(typeRegistry.getNativeType(ITERABLE_TYPE));
boolean isAsyncIterable =
autoboxedType.isSubtypeOf(typeRegistry.getNativeType(ASYNC_ITERABLE_TYPE));
if (!isIterable && !isAsyncIterable) {
return new MaybeBoxedIterableOrAsyncIterable(null, autoboxedType);
}
JSTypeNative iterableType = isAsyncIterable ? ASYNC_ITERABLE_TYPE : ITERABLE_TYPE;
templatedTypes.add(
autoboxedType.getInstantiatedTypeArgument(typeRegistry.getNativeType(iterableType)));
}
return new MaybeBoxedIterableOrAsyncIterable(
typeRegistry.createUnionType(templatedTypes), null);
}

private JsIterables() {}
Expand Down
36 changes: 9 additions & 27 deletions src/com/google/javascript/jscomp/TypeValidator.java
Expand Up @@ -22,7 +22,6 @@
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.javascript.rhino.jstype.JSTypeNative.ARRAY_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.ASYNC_GENERATOR_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.ASYNC_ITERABLE_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.BOOLEAN_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.GENERATOR_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.ITERABLE_TYPE;
Expand All @@ -40,6 +39,7 @@
import static com.google.javascript.rhino.jstype.JSTypeNative.VOID_TYPE;

import com.google.common.base.Joiner;
import com.google.javascript.jscomp.JsIterables.MaybeBoxedIterableOrAsyncIterable;
import com.google.javascript.jscomp.parsing.parser.util.format.SimpleFormat;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
Expand Down Expand Up @@ -317,34 +317,16 @@ boolean expectAutoboxesToIterable(NodeTraversal t, Node n, JSType type, String m
*/
Optional<JSType> expectAutoboxesToIterableOrAsyncIterable(
NodeTraversal t, Node n, JSType type, String msg) {
List<JSType> templatedTypes = new ArrayList<>();
MaybeBoxedIterableOrAsyncIterable maybeBoxed =
JsIterables.maybeBoxIterableOrAsyncIterable(type, typeRegistry);

// Note: we don't just use JSType.autobox() here because that removes null and undefined.
// We want to keep null and undefined around.
if (type.isUnionType()) {
for (JSType alt : type.toMaybeUnionType().getAlternatesWithoutStructuralTyping()) {
alt = alt.isBoxableScalar() ? alt.autoboxesTo() : alt;
boolean isIterable = alt.isSubtypeOf(getNativeType(ITERABLE_TYPE));
boolean isAsyncIterable = alt.isSubtypeOf(getNativeType(ASYNC_ITERABLE_TYPE));
if (!isIterable && !isAsyncIterable) {
mismatch(t, n, msg, type, iterableOrAsyncIterable);
return Optional.empty();
}
JSTypeNative iterableType = isAsyncIterable ? ASYNC_ITERABLE_TYPE : ITERABLE_TYPE;
templatedTypes.add(alt.getInstantiatedTypeArgument(getNativeType(iterableType)));
}
} else {
JSType autoboxedType = type.isBoxableScalar() ? type.autoboxesTo() : type;
boolean isIterable = autoboxedType.isSubtypeOf(getNativeType(ITERABLE_TYPE));
boolean isAsyncIterable = autoboxedType.isSubtypeOf(getNativeType(ASYNC_ITERABLE_TYPE));
if (!isIterable && !isAsyncIterable) {
mismatch(t, n, msg, type, iterableOrAsyncIterable);
return Optional.empty();
}
JSTypeNative iterableType = isAsyncIterable ? ASYNC_ITERABLE_TYPE : ITERABLE_TYPE;
templatedTypes.add(autoboxedType.getInstantiatedTypeArgument(getNativeType(iterableType)));
if (maybeBoxed.isMatch()) {
return Optional.of(maybeBoxed.getTemplatedType());
}
return Optional.of(typeRegistry.createUnionType(templatedTypes));

mismatch(t, n, msg, type, iterableOrAsyncIterable);

return Optional.empty();
}

/** Expect the type to be a Generator or supertype of Generator. */
Expand Down

0 comments on commit f0ae19c

Please sign in to comment.