diff --git a/src/com/google/javascript/jscomp/AstFactory.java b/src/com/google/javascript/jscomp/AstFactory.java index 3927fa8fd64..c98d6215ac2 100644 --- a/src/com/google/javascript/jscomp/AstFactory.java +++ b/src/com/google/javascript/jscomp/AstFactory.java @@ -803,12 +803,7 @@ Node createJSCompMakeIteratorCall(Node iterable, Scope scope) { // function(Iterable): Iterator // with // function(Iterable): Iterator - TemplateTypeMap typeMap = - registry.createTemplateTypeMap( - makeIteratorType.getTemplateTypeMap().getTemplateKeys(), - ImmutableList.of(iterableType)); - TemplateTypeReplacer replacer = TemplateTypeReplacer.forPartialReplacement(registry, typeMap); - makeIteratorName.setJSType(makeIteratorType.visit(replacer)); + makeIteratorName.setJSType(replaceTemplate(makeIteratorType, ImmutableList.of(iterableType))); } return createCall(makeIteratorName, iterable); } @@ -832,12 +827,7 @@ Node createJscompArrayFromIteratorCall(Node iterator, Scope scope) { // function(Iterator): Array // with // function(Iterator): Array - TemplateTypeMap typeMap = - registry.createTemplateTypeMap( - makeIteratorType.getTemplateTypeMap().getTemplateKeys(), - ImmutableList.of(iterableType)); - TemplateTypeReplacer replacer = TemplateTypeReplacer.forPartialReplacement(registry, typeMap); - makeIteratorName.setJSType(makeIteratorType.visit(replacer)); + makeIteratorName.setJSType(replaceTemplate(makeIteratorType, ImmutableList.of(iterableType))); } return createCall(makeIteratorName, iterator); } @@ -872,21 +862,17 @@ Node createJSCompMakeAsyncIteratorCall(Node iterable, Scope scope) { // function(AsyncIterable): AsyncIterator // with // function(AsyncIterable): AsyncIterator - TemplateTypeMap typeMap = - registry.createTemplateTypeMap( - makeAsyncIteratorType.getTemplateTypeMap().getTemplateKeys(), - ImmutableList.of(asyncIterableType)); - TemplateTypeReplacer replacer = TemplateTypeReplacer.forPartialReplacement(registry, typeMap); - makeIteratorAsyncName.setJSType(makeAsyncIteratorType.visit(replacer)); + makeIteratorAsyncName.setJSType( + replaceTemplate(makeAsyncIteratorType, ImmutableList.of(asyncIterableType))); } return createCall(makeIteratorAsyncName, iterable); } - private JSType replaceTemplate(JSType templatedType, JSType... templateTypes) { + private JSType replaceTemplate(JSType templatedType, ImmutableList templateTypes) { TemplateTypeMap typeMap = - registry.createTemplateTypeMap( - templatedType.getTemplateTypeMap().getTemplateKeys(), - ImmutableList.copyOf(templateTypes)); + registry + .getEmptyTemplateTypeMap() + .copyWithExtension(templatedType.getTemplateTypeMap().getTemplateKeys(), templateTypes); TemplateTypeReplacer replacer = TemplateTypeReplacer.forPartialReplacement(registry, typeMap); return templatedType.visit(replacer); } @@ -915,7 +901,7 @@ Node createAsyncGeneratorWrapperReference(JSType originalFunctionType, Scope sco // AsyncGeneratorWrapper // with // AsyncGeneratorWrapper - ctor.setJSType(replaceTemplate(ctor.getJSType(), yieldedType)); + ctor.setJSType(replaceTemplate(ctor.getJSType(), ImmutableList.of(yieldedType))); } return ctor; @@ -937,7 +923,8 @@ Node createEmptyAsyncGeneratorWrapperArgument(JSType asyncGeneratorWrapperType) // Not injecting libraries? generatorType = registry.createFunctionType( - replaceTemplate(getNativeType(JSTypeNative.GENERATOR_TYPE), unknownType)); + replaceTemplate( + getNativeType(JSTypeNative.GENERATOR_TYPE), ImmutableList.of(unknownType))); } else { // Generator<$jscomp.AsyncGeneratorWrapper$ActionRecord> JSType innerFunctionReturnType = diff --git a/src/com/google/javascript/jscomp/FunctionTypeBuilder.java b/src/com/google/javascript/jscomp/FunctionTypeBuilder.java index e7e978bd594..4792c9c24fa 100644 --- a/src/com/google/javascript/jscomp/FunctionTypeBuilder.java +++ b/src/com/google/javascript/jscomp/FunctionTypeBuilder.java @@ -975,10 +975,12 @@ FunctionType buildAndRegister() { } private void maybeSetBaseType(FunctionType fnType) { - if (fnType.hasInstanceType() && baseType != null) { - fnType.setPrototypeBasedOn(baseType); - fnType.extendTemplateTypeMapBasedOn(baseType); + if (!fnType.hasInstanceType() || baseType == null) { + return; } + + fnType.setPrototypeBasedOn(baseType); + fnType.getInstanceType().prependTemplateTypeMap(baseType.getTemplateTypeMap()); } /** diff --git a/src/com/google/javascript/rhino/jstype/FunctionType.java b/src/com/google/javascript/rhino/jstype/FunctionType.java index 28c36dd067f..221209b86e6 100644 --- a/src/com/google/javascript/rhino/jstype/FunctionType.java +++ b/src/com/google/javascript/rhino/jstype/FunctionType.java @@ -493,15 +493,6 @@ final void setPrototypeBasedOn(ObjectType baseType, Node propertyNode) { setPrototype(baseType, propertyNode); } - /** - * Extends the TemplateTypeMap of the function's this type, based on the specified type. - * - * @param type - */ - public final void extendTemplateTypeMapBasedOn(ObjectType type) { - typeOfThis.extendTemplateTypeMap(type.getTemplateTypeMap()); - } - /** * Sets the prototype. * @@ -620,15 +611,12 @@ public final ImmutableList getOwnImplementedInterfaces() { } public final void setImplementedInterfaces(List implementedInterfaces) { - if (isConstructor()) { - // Records this type for each implemented interface. - for (ObjectType type : implementedInterfaces) { - registry.registerTypeImplementingInterface(this, type); - typeOfThis.extendTemplateTypeMap(type.getTemplateTypeMap()); - } - this.implementedInterfaces = ImmutableList.copyOf(implementedInterfaces); - } else { - throw new UnsupportedOperationException("An interface cannot implement other inferfaces"); + checkState(isConstructor()); + + this.implementedInterfaces = ImmutableList.copyOf(implementedInterfaces); + for (ObjectType type : implementedInterfaces) { + registry.registerTypeImplementingInterface(this, type); + typeOfThis.prependTemplateTypeMap(type.getTemplateTypeMap()); } } @@ -643,13 +631,11 @@ public final int getExtendedInterfacesCount() { } public final void setExtendedInterfaces(List extendedInterfaces) { - if (isInterface()) { - this.extendedInterfaces = ImmutableList.copyOf(extendedInterfaces); - for (ObjectType extendedInterface : this.extendedInterfaces) { - typeOfThis.extendTemplateTypeMap(extendedInterface.getTemplateTypeMap()); - } - } else { - throw new UnsupportedOperationException(); + checkState(isInterface()); + + this.extendedInterfaces = ImmutableList.copyOf(extendedInterfaces); + for (ObjectType extendedInterface : extendedInterfaces) { + typeOfThis.prependTemplateTypeMap(extendedInterface.getTemplateTypeMap()); } } @@ -1655,22 +1641,14 @@ public Builder withTypeOfThis(JSType typeOfThis) { /** Set the template name. */ public Builder withTemplateKeys(ImmutableList templateKeys) { - this.templateTypeMap = registry.createTemplateTypeMap(templateKeys, null); + this.templateTypeMap = + registry.getEmptyTemplateTypeMap().copyWithExtension(templateKeys, ImmutableList.of()); return this; } /** Set the template name. */ public Builder withTemplateKeys(TemplateType... templateKeys) { - this.templateTypeMap = - registry.createTemplateTypeMap(ImmutableList.copyOf(templateKeys), null); - return this; - } - - Builder withExtendedTemplate(TemplateType key, JSType value) { - this.templateTypeMap = - templateTypeMap.extend( - registry.createTemplateTypeMap(ImmutableList.of(key), ImmutableList.of(value))); - return this; + return withTemplateKeys(ImmutableList.copyOf(templateKeys)); } Builder withTemplateTypeMap(TemplateTypeMap templateTypeMap) { @@ -1790,7 +1768,7 @@ public FunctionType build() { if (hasConstructorOnlyKeys) { ft.setInstanceType( new InstanceObjectType( - registry, ft, isNative, templateTypeMap.remove(constructorOnlyKeys))); + registry, ft, isNative, templateTypeMap.copyWithoutKeys(constructorOnlyKeys))); } return ft; } diff --git a/src/com/google/javascript/rhino/jstype/JSType.java b/src/com/google/javascript/rhino/jstype/JSType.java index 3234fdbd574..3519963ca55 100644 --- a/src/com/google/javascript/rhino/jstype/JSType.java +++ b/src/com/google/javascript/rhino/jstype/JSType.java @@ -103,8 +103,8 @@ static final boolean areIdentical(JSType a, JSType b) { JSType(JSTypeRegistry registry, TemplateTypeMap templateTypeMap) { this.registry = registry; - this.templateTypeMap = templateTypeMap == null ? - registry.createTemplateTypeMap(null, null) : templateTypeMap; + this.templateTypeMap = + (templateTypeMap == null) ? registry.getEmptyTemplateTypeMap() : templateTypeMap; } /** @@ -543,11 +543,11 @@ public final ImmutableSet getTypeParameters() { } /** - * Extends the template type map associated with this type, merging in the - * keys and values of the specified map. + * Prepends the template type map associated with this type, merging in the keys and values of the + * specified map. */ - public void extendTemplateTypeMap(TemplateTypeMap otherMap) { - templateTypeMap = templateTypeMap.extend(otherMap); + public void prependTemplateTypeMap(TemplateTypeMap otherMap) { + templateTypeMap = otherMap.copyWithExtension(templateTypeMap); } /** diff --git a/src/com/google/javascript/rhino/jstype/JSTypeRegistry.java b/src/com/google/javascript/rhino/jstype/JSTypeRegistry.java index ba5b47734fc..01740cd219f 100644 --- a/src/com/google/javascript/rhino/jstype/JSTypeRegistry.java +++ b/src/com/google/javascript/rhino/jstype/JSTypeRegistry.java @@ -234,9 +234,9 @@ public JSTypeRegistry(ErrorReporter reporter) { public JSTypeRegistry(ErrorReporter reporter, Set forwardDeclaredTypes) { this.reporter = reporter; this.forwardDeclaredTypes = forwardDeclaredTypes; - this.emptyTemplateTypeMap = new TemplateTypeMap( - this, ImmutableList.of(), ImmutableList.of()); - nativeTypes = new JSType[JSTypeNative.values().length]; + this.emptyTemplateTypeMap = TemplateTypeMap.createEmpty(this); + this.nativeTypes = new JSType[JSTypeNative.values().length]; + resetForTypeCheck(); } @@ -372,7 +372,8 @@ private void initializeBuiltInTypes() { FunctionType iObjectFunctionType = nativeInterface("IObject", iObjectIndexTemplateKey, iObjectElementTemplateKey); registerNativeType(JSTypeNative.I_OBJECT_FUNCTION_TYPE, iObjectFunctionType); - registerNativeType(JSTypeNative.I_OBJECT_TYPE, iObjectFunctionType.getInstanceType()); + ObjectType iObjectType = iObjectFunctionType.getInstanceType(); + registerNativeType(JSTypeNative.I_OBJECT_TYPE, iObjectType); // Object FunctionType objectFunctionType = @@ -428,8 +429,9 @@ private void initializeBuiltInTypes() { registerNativeType(JSTypeNative.I_ITERABLE_RESULT_TYPE, iiterableResultType); // IArrayLike. - // TODO(lharker): Should the native Array implement IArrayLike? FunctionType iArrayLikeFunctionType = nativeRecord("IArrayLike", iArrayLikeTemplate); + iArrayLikeFunctionType.setExtendedInterfaces( + ImmutableList.of(createTemplatizedType(iObjectType, numberType, iArrayLikeTemplate))); registerNativeType(JSTypeNative.I_ARRAY_LIKE_FUNCTION_TYPE, iArrayLikeFunctionType); ObjectType iArrayLikeType = iArrayLikeFunctionType.getInstanceType(); registerNativeType(JSTypeNative.I_ARRAY_LIKE_TYPE, iArrayLikeType); @@ -439,16 +441,13 @@ private void initializeBuiltInTypes() { nativeConstructorBuilder("Array") .withParamsNode(createParametersWithVarArgs(allType)) .withReturnsOwnInstanceType() - // TODO(nickreid): Could this be achieved by having `Array` implement `IObject`? - .withTemplateTypeMap( - new TemplateTypeMap( - this, - ImmutableList.of(iObjectElementTemplateKey, arrayElementTemplateKey), - ImmutableList.of(arrayElementTemplateKey))) + .withTemplateKeys(arrayElementTemplateKey) .build(); arrayFunctionType.getPrototype(); // Force initialization arrayFunctionType.setImplementedInterfaces( - ImmutableList.of(createTemplatizedType(iterableType, arrayElementTemplateKey))); + ImmutableList.of( + createTemplatizedType(iArrayLikeType, arrayElementTemplateKey), + createTemplatizedType(iterableType, arrayElementTemplateKey))); registerNativeType(JSTypeNative.ARRAY_FUNCTION_TYPE, arrayFunctionType); ObjectType arrayType = arrayFunctionType.getInstanceType(); @@ -523,7 +522,6 @@ private void initializeBuiltInTypes() { nativeConstructorBuilder("Promise") .withParamsNode(IR.paramList(promiseParameter)) .withTemplateKeys(promiseTemplateKey) - .withExtendedTemplate(iThenableTemplateKey, promiseTemplateKey) .build(); promiseFunctionType.setImplementedInterfaces( ImmutableList.of(createTemplatizedType(ithenableType, promiseTemplateKey))); @@ -1740,7 +1738,7 @@ public FunctionType createConstructorType( Node source, Node parameters, JSType returnType, - ImmutableList templateKeys, + @Nullable ImmutableList templateKeys, boolean isAbstract) { checkArgument(source == null || source.isFunction() || source.isClass()); return FunctionType.builder(this) @@ -1749,7 +1747,7 @@ public FunctionType createConstructorType( .withSourceNode(source) .withParamsNode(parameters) .withReturnType(returnType) - .withTemplateKeys(templateKeys) + .withTemplateKeys((templateKeys == null) ? ImmutableList.of() : templateKeys) .withIsAbstract(isAbstract) .build(); } @@ -1770,7 +1768,7 @@ public FunctionType createInterfaceType( .withName(name) .withSourceNode(source) .withEmptyParams() - .withTemplateKeys(templateKeys) + .withTemplateKeys((templateKeys == null) ? ImmutableList.of() : templateKeys) .build(); if (struct) { fn.setStruct(); @@ -1793,22 +1791,8 @@ public TemplateType createTemplateTypeWithTransformation( return new TemplateType(this, name, expr); } - /** - * Creates a template type map from the specified list of template keys and - * template value types. - */ - public TemplateTypeMap createTemplateTypeMap( - ImmutableList templateKeys, - ImmutableList templateValues) { - if (templateKeys == null) { - templateKeys = ImmutableList.of(); - } - if (templateValues == null) { - templateValues = ImmutableList.of(); - } - return (templateKeys.isEmpty() && templateValues.isEmpty()) - ? emptyTemplateTypeMap - : new TemplateTypeMap(this, templateKeys, templateValues); + public TemplateTypeMap getEmptyTemplateTypeMap() { + return this.emptyTemplateTypeMap; } public ObjectType instantiateGenericsWithUnknown(ObjectType obj) { diff --git a/src/com/google/javascript/rhino/jstype/TemplateTypeMap.java b/src/com/google/javascript/rhino/jstype/TemplateTypeMap.java index f5e9e9a6d08..5b703cd91ae 100644 --- a/src/com/google/javascript/rhino/jstype/TemplateTypeMap.java +++ b/src/com/google/javascript/rhino/jstype/TemplateTypeMap.java @@ -57,6 +57,7 @@ * @author izaakr@google.com (Izaak Rubin) */ public class TemplateTypeMap implements Serializable { + // The TemplateType keys of the map. private final ImmutableList templateKeys; // The JSType values, which are index-aligned with their corresponding keys. @@ -69,11 +70,18 @@ public class TemplateTypeMap implements Serializable { // instance. These fully-resolved values are necessary for determining the // equivalence of two TemplateTypeMap instances. private final JSType[] resolvedTemplateValues; - final JSTypeRegistry registry; + private final JSTypeRegistry registry; + + static final TemplateTypeMap createEmpty(JSTypeRegistry registry) { + // This method should only be called during registry initialization. + checkArgument(registry.getEmptyTemplateTypeMap() == null); + return new TemplateTypeMap(registry, ImmutableList.of(), ImmutableList.of()); + } - TemplateTypeMap(JSTypeRegistry registry, - ImmutableList templateKeys, - ImmutableList templateValues) { + private TemplateTypeMap( + JSTypeRegistry registry, + ImmutableList templateKeys, + ImmutableList templateValues) { checkNotNull(templateKeys); checkNotNull(templateValues); checkArgument(templateValues.size() <= templateKeys.size()); @@ -102,6 +110,90 @@ public class TemplateTypeMap implements Serializable { this.resolvedTemplateValues = resolvedValues; } + /** + * Create a new map in which any unfilled values in this map have been filled with {@code values}. + * + *

If there are fewer {@code values} than unfilled values, `?` will be used to fill the rest. + */ + TemplateTypeMap copyFilledWithValues(ImmutableList values) { + int requiredUnknownCount = numUnfilledTemplateKeys() - values.size(); + checkArgument(requiredUnknownCount >= 0, requiredUnknownCount); + + if (numUnfilledTemplateKeys() == 0) { + return this; // Nothing will change. + } + + ImmutableList.Builder extendedValues = ImmutableList.builder(); + extendedValues.addAll(this.templateValues); + extendedValues.addAll(values); + padWithUnknown(extendedValues, requiredUnknownCount); + + return new TemplateTypeMap(this.registry, this.templateKeys, extendedValues.build()); + } + + /** + * Create a new map in which the keys and values have been extended by {@code extension}. + * + *

Before extension, any unfilled values in the initial map will be filled with `?`. + */ + public TemplateTypeMap copyWithExtension(TemplateTypeMap extension) { + return copyWithExtension(extension.templateKeys, extension.templateValues); + } + + /** + * Create a new map in which the keys and values have been extended by {@code keys} and {@code + * values} respectively. + * + *

Before extension, any unfilled values in the initial map will be filled with `?`. + */ + public TemplateTypeMap copyWithExtension( + ImmutableList keys, ImmutableList values) { + int extendedUnfilledCount = keys.size() - values.size(); + checkArgument(extendedUnfilledCount >= 0, extendedUnfilledCount); + + if (numUnfilledTemplateKeys() == 0 && keys.isEmpty()) { + return this; // Nothing will change. + } + + ImmutableList extendedKeys = + ImmutableList.builder().addAll(this.templateKeys).addAll(keys).build(); + + ImmutableList.Builder extendedValues = ImmutableList.builder(); + extendedValues.addAll(this.templateValues); + padWithUnknown(extendedValues, numUnfilledTemplateKeys()); + extendedValues.addAll(values); + + return new TemplateTypeMap(this.registry, extendedKeys, extendedValues.build()); + } + + /** + * Create a new map in which keys contained in {@code removals} are eliminated. + * + *

The keys in {@code removals} will only be removed if they are unfilled. + */ + TemplateTypeMap copyWithoutKeys(Set removals) { + ImmutableList.Builder keys = ImmutableList.builder(); + keys.addAll(templateKeys.subList(0, templateValues.size())); + for (int i = templateValues.size(); i < templateKeys.size(); i++) { + TemplateType key = templateKeys.get(i); + if (!removals.contains(key)) { + keys.add(key); + } + } + + // There are some checks we could do for this before calculating the removal, but it was less + // error prone to only check in one place. + if (keys.build().size() == templateKeys.size()) { + return this; // Nothing will change. + } + + return new TemplateTypeMap(this.registry, keys.build(), this.templateValues); + } + + public int size() { + return this.templateKeys.size(); + } + /** * Returns true if the map is empty; false otherwise. */ @@ -109,13 +201,15 @@ public boolean isEmpty() { return templateKeys.isEmpty(); } - /** - * Returns a list of all template keys. - */ + /** Returns a list of all template keys. */ public ImmutableList getTemplateKeys() { return templateKeys; } + public ImmutableList getTemplateValues() { + return templateValues; + } + /** * Returns true if this map contains the specified template key, false * otherwise. @@ -186,7 +280,7 @@ public TemplateType getTemplateTypeKeyByName(String keyName) { private int getTemplateTypeIndex(TemplateType key) { int maxIndex = Math.min(templateKeys.size(), templateValues.size()); for (int i = maxIndex - 1; i >= 0; i--) { - if (isSameKey(templateKeys.get(i), key)) { + if (JSType.areIdentical(templateKeys.get(i), key)) { return i; } } @@ -235,11 +329,6 @@ public boolean checkEquivalenceHelper(TemplateTypeMap that, } } - @SuppressWarnings("ReferenceEquality") - private static boolean isSameKey(TemplateType thisKey, TemplateType thatKey) { - return thisKey == thatKey; - } - private static boolean checkEquivalenceHelper(EquivalenceMethod eqMethod, TemplateTypeMap thisMap, TemplateTypeMap thatMap, EqCache eqCache, SubtypingMode subtypingMode) { @@ -258,7 +347,7 @@ private static boolean checkEquivalenceHelper(EquivalenceMethod eqMethod, // Cross-compare every key-value pair in this TemplateTypeMap with // those in that TemplateTypeMap. Update the Equivalence match for both // key-value pairs involved. - if (isSameKey(thisKey, thatKey)) { + if (JSType.areIdentical(thisKey, thatKey)) { EquivalenceMatch newMatchType = EquivalenceMatch.VALUE_MISMATCH; if (thisType.checkEquivalenceHelper(thatType, eqMethod, eqCache) || (subtypingMode == SubtypingMode.IGNORE_NULL_UNDEFINED @@ -291,40 +380,6 @@ private static boolean failedEquivalenceCheck( || (eqMatch == EquivalenceMatch.NO_KEY_MATCH && eqMethod != EquivalenceMethod.INVARIANT); } - /** - * Extends this TemplateTypeMap with the contents of the specified map. UNKNOWN_TYPE will be used - * as the value for any missing values in the specified map. - */ - TemplateTypeMap extend(TemplateTypeMap other) { - ImmutableList resizedOtherValues = other.resizedToMatchKeys(other.templateValues); - return registry.createTemplateTypeMap( - concatImmutableLists(other.templateKeys, templateKeys), - concatImmutableLists(resizedOtherValues, templateValues)); - } - - /** Returns a new TemplateTypeMap whose values have been extended with the specified list. */ - TemplateTypeMap copyFilledWithValues(ImmutableList additionalValues) { - ImmutableList finalValues = - resizedToMatchKeys(concatImmutableLists(templateValues, additionalValues)); - return registry.createTemplateTypeMap(templateKeys, finalValues); - } - - /** - * Returns a new TemplateTypeMap with the given template types removed. Keys will only be removed - * if they are unmapped. - */ - TemplateTypeMap remove(Set toRemove) { - ImmutableList.Builder keys = ImmutableList.builder(); - keys.addAll(templateKeys.subList(0, templateValues.size())); - for (int i = templateValues.size(); i < templateKeys.size(); i++) { - TemplateType key = templateKeys.get(i); - if (!toRemove.contains(key)) { - keys.add(key); - } - } - return registry.createTemplateTypeMap(keys.build(), templateValues); - } - boolean hasAnyTemplateTypesInternal() { if (resolvedTemplateValues != null) { for (JSType templateValue : resolvedTemplateValues) { @@ -358,35 +413,9 @@ public String toString() { return s; } - private ImmutableList resizedToMatchKeys(ImmutableList values) { - if (values.size() == templateKeys.size()) { - return values; - } else if (values.size() > templateKeys.size()) { - return values.subList(0, templateKeys.size()); - } else { - ImmutableList.Builder builder = ImmutableList.builder(); - builder.addAll(values); - for (int i = values.size(); i < templateKeys.size(); i++) { - builder.add(registry.getNativeType(JSTypeNative.UNKNOWN_TYPE)); - } - return builder.build(); + private void padWithUnknown(ImmutableList.Builder builder, int count) { + for (int i = 0; i < count; i++) { + builder.add(registry.getNativeType(JSTypeNative.UNKNOWN_TYPE)); } } - - /** - * Concatenates two ImmutableList instances. If either input is empty, the other is returned; - * otherwise, a new ImmutableList instance is created that contains the contents of both - * arguments. - */ - private static ImmutableList concatImmutableLists( - ImmutableList first, ImmutableList second) { - if (first.isEmpty()) { - return second; - } - if (second.isEmpty()) { - return first; - } - - return ImmutableList.builder().addAll(first).addAll(second).build(); - } } diff --git a/src/com/google/javascript/rhino/jstype/TemplateTypeReplacer.java b/src/com/google/javascript/rhino/jstype/TemplateTypeReplacer.java index 4c2eda36a97..32112035155 100644 --- a/src/com/google/javascript/rhino/jstype/TemplateTypeReplacer.java +++ b/src/com/google/javascript/rhino/jstype/TemplateTypeReplacer.java @@ -81,7 +81,7 @@ public static TemplateTypeReplacer forInference( .map(bindings::get) .map((v) -> (v != null) ? v : registry.getNativeType(JSTypeNative.UNKNOWN_TYPE)) .collect(toImmutableList()); - TemplateTypeMap map = new TemplateTypeMap(registry, keys, values); + TemplateTypeMap map = registry.getEmptyTemplateTypeMap().copyWithExtension(keys, values); return new TemplateTypeReplacer(registry, map, true, true, true); } diff --git a/src/com/google/javascript/rhino/testing/TemplateTypeMapSubject.java b/src/com/google/javascript/rhino/testing/TemplateTypeMapSubject.java new file mode 100644 index 00000000000..5876104bf7e --- /dev/null +++ b/src/com/google/javascript/rhino/testing/TemplateTypeMapSubject.java @@ -0,0 +1,75 @@ +/* + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bob Jervis + * Google Inc. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +package com.google.javascript.rhino.testing; + +import static com.google.common.truth.Truth.assertAbout; + +import com.google.common.collect.ImmutableList; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Subject; +import com.google.javascript.rhino.jstype.JSType; +import com.google.javascript.rhino.jstype.TemplateType; +import com.google.javascript.rhino.jstype.TemplateTypeMap; +import javax.annotation.CheckReturnValue; + +/** A Truth Subject for {@link TemplateTypeMap}s. */ +public final class TemplateTypeMapSubject extends Subject { + @CheckReturnValue + public static TemplateTypeMapSubject assertThat(TemplateTypeMap actual) { + return assertAbout(typeMaps()).that(actual); + } + + public static Subject.Factory typeMaps() { + return TemplateTypeMapSubject::new; + } + + private final TemplateTypeMap actual; + + private TemplateTypeMapSubject(FailureMetadata failureMetadata, TemplateTypeMap actual) { + super(failureMetadata, actual); + this.actual = actual; + } + + public void hasKeysAndValues( + ImmutableList keys, ImmutableList values) { + check("getTemplateKeys()").that(actual.getTemplateKeys()).isEqualTo(keys); + check("getTemplateValues()").that(actual.getTemplateValues()).isEqualTo(values); + } +} diff --git a/test/com/google/javascript/jscomp/IntegrationTestCase.java b/test/com/google/javascript/jscomp/IntegrationTestCase.java index 36c241d9fca..b8c160cb427 100644 --- a/test/com/google/javascript/jscomp/IntegrationTestCase.java +++ b/test/com/google/javascript/jscomp/IntegrationTestCase.java @@ -142,7 +142,7 @@ protected static String lines(String... lines) { "", "/**", " * @constructor", - " * @implements {IArrayLike}", + " * @implements {IArrayLike}", " * @implements {Iterable}", " * @return {!Array}", " * @param {...*} var_args", diff --git a/test/com/google/javascript/jscomp/TypeCheckTest.java b/test/com/google/javascript/jscomp/TypeCheckTest.java index b8a50f05206..3e9e2431d35 100644 --- a/test/com/google/javascript/jscomp/TypeCheckTest.java +++ b/test/com/google/javascript/jscomp/TypeCheckTest.java @@ -16,7 +16,6 @@ package com.google.javascript.jscomp; -import static com.google.common.base.Preconditions.checkState; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static com.google.javascript.jscomp.TypeCheck.INSTANTIATE_ABSTRACT_CLASS; @@ -7250,17 +7249,6 @@ public void testForinOnStruct() { "Cannot use the IN operator with structs"); } - @Test - public void testArrayLegacyAccess1() { - String externs = DEFAULT_EXTERNS.replace( - " * @implements {IArrayLike}", - lines( - " * @implements {IObject} ", - " * @implements {IArrayLike} ")); - checkState(DEFAULT_EXTERNS.length() != externs.length()); - testTypesWithExterns(externs, "var a = []; var b = a['hi'];"); - } - @Test public void testIArrayLikeAccess1() { testTypesWithCommonExterns( diff --git a/test/com/google/javascript/rhino/jstype/FunctionTypeTest.java b/test/com/google/javascript/rhino/jstype/FunctionTypeTest.java index 2bc5ad7c127..b80cf433f66 100644 --- a/test/com/google/javascript/rhino/jstype/FunctionTypeTest.java +++ b/test/com/google/javascript/rhino/jstype/FunctionTypeTest.java @@ -39,7 +39,7 @@ package com.google.javascript.rhino.jstype; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.javascript.rhino.testing.Asserts.assertThrows; import static com.google.javascript.rhino.testing.TypeSubject.assertType; import com.google.common.collect.ImmutableList; @@ -516,12 +516,8 @@ public void testSetImplementsOnInterface() { ImmutableList.of(), false); FunctionType subIface = registry.createInterfaceType("SubI", null, ImmutableList.of(), false); - try { - subIface.setImplementedInterfaces( - ImmutableList.of(iface.getInstanceType())); - assertWithMessage("Expected exception").fail(); - } catch (UnsupportedOperationException e) { - // OK - } + assertThrows( + Exception.class, + () -> subIface.setImplementedInterfaces(ImmutableList.of(iface.getInstanceType()))); } } diff --git a/test/com/google/javascript/rhino/jstype/TemplateTypeMapTest.java b/test/com/google/javascript/rhino/jstype/TemplateTypeMapTest.java new file mode 100644 index 00000000000..b833483ac68 --- /dev/null +++ b/test/com/google/javascript/rhino/jstype/TemplateTypeMapTest.java @@ -0,0 +1,255 @@ +/* + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Nick Santos + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +package com.google.javascript.rhino.jstype; + +import static com.google.javascript.rhino.testing.Asserts.assertThrows; +import static com.google.javascript.rhino.testing.TemplateTypeMapSubject.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.javascript.rhino.testing.BaseJSTypeTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class TemplateTypeMapTest extends BaseJSTypeTestCase { + + private TemplateTypeMap emptyMap; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + emptyMap = registry.getEmptyTemplateTypeMap(); + } + + @Test + public void testCreateEmptyMap_throwsWhenDuplicateRequested() { + assertThrows(Exception.class, () -> TemplateTypeMap.createEmpty(registry)); + } + + @Test + public void testCopyExtend_createsNewMap() { + // When + TemplateTypeMap result = + emptyMap.copyWithExtension(ImmutableList.of(key("A")), ImmutableList.of(NUMBER_TYPE)); + + // Then + assertThat(result).isNotSameInstanceAs(emptyMap); + } + + @Test + public void testCopyExtend_validatesCountOfKeysVsValues() { + assertThrows( + Exception.class, + () -> emptyMap.copyWithExtension(ImmutableList.of(), ImmutableList.of(NUMBER_TYPE))); + } + + @Test + public void testCopyExtend_emptyMap() { + // Given + TemplateType keyT = key("T"); + TemplateType keyU = key("U"); + + // When + TemplateTypeMap result = + emptyMap.copyWithExtension(ImmutableList.of(keyT, keyU), ImmutableList.of(NUMBER_TYPE)); + + // Then + assertThat(result) + .hasKeysAndValues(ImmutableList.of(keyT, keyU), ImmutableList.of(NUMBER_TYPE)); + } + + @Test + public void testCopyExtend_partialMap_fillsExitingUnfilledKeysWithUnknown() { + // Given + TemplateType keyA = key("A"); + TemplateType keyB = key("B"); + TemplateType keyC = key("C"); + TemplateTypeMap existing = + createMap(ImmutableList.of(keyA, keyB, keyC), ImmutableList.of(NUMBER_TYPE)); + + TemplateType keyX = key("X"); + TemplateType keyY = key("Y"); + TemplateType keyZ = key("Z"); + + // When + TemplateTypeMap result = + existing.copyWithExtension( + ImmutableList.of(keyX, keyY, keyZ), ImmutableList.of(STRING_TYPE)); + + // Then + assertThat(result) + .hasKeysAndValues( + ImmutableList.of(keyA, keyB, keyC, keyX, keyY, keyZ), + ImmutableList.of(NUMBER_TYPE, UNKNOWN_TYPE, UNKNOWN_TYPE, STRING_TYPE)); + } + + @Test + public void testCopyExtend_partialMap_emptyExtension_fillsExitingUnfilledKeysWithUnknown() { + // Given + TemplateType keyA = key("A"); + TemplateType keyB = key("B"); + TemplateType keyC = key("C"); + TemplateTypeMap existing = + createMap(ImmutableList.of(keyA, keyB, keyC), ImmutableList.of(NUMBER_TYPE)); + // When + TemplateTypeMap result = existing.copyWithExtension(ImmutableList.of(), ImmutableList.of()); + + // Then + assertThat(result) + .hasKeysAndValues( + ImmutableList.of(keyA, keyB, keyC), + ImmutableList.of(NUMBER_TYPE, UNKNOWN_TYPE, UNKNOWN_TYPE)); + } + + @Test + public void testCopyExtend_ifMapFilled_ifExtensionEmpty_returnsSelf() { + // Given + TemplateTypeMap existing = createMap(ImmutableList.of(key("A")), ImmutableList.of(NUMBER_TYPE)); + + // When + TemplateTypeMap result = emptyMap.copyWithExtension(ImmutableList.of(), ImmutableList.of()); + + // Then + assertThat(result).isNotSameInstanceAs(existing); + } + + @Test + public void testCopyFill_validatesValueCount_againstUnfilledKeys() { + assertThrows( + Exception.class, () -> emptyMap.copyFilledWithValues(ImmutableList.of(NUMBER_TYPE))); + } + + @Test + public void testCopyFill_ifNoSlotsEmpty_returnsSelf() { + // Given + TemplateType keyA = key("A"); + TemplateType keyB = key("B"); + TemplateTypeMap existing = + createMap(ImmutableList.of(keyA, keyB), ImmutableList.of(NUMBER_TYPE, STRING_TYPE)); + + // When + TemplateTypeMap result = existing.copyFilledWithValues(ImmutableList.of()); + + // Then + assertThat(result).isSameInstanceAs(existing); + } + + @Test + public void testCopyFill_partialMap_fillsExtraKeysWithUnknown() { + // Given + TemplateType keyA = key("A"); + TemplateType keyB = key("B"); + TemplateType keyC = key("C"); + TemplateTypeMap existing = + createMap(ImmutableList.of(keyA, keyB, keyC), ImmutableList.of(NUMBER_TYPE)); + + // When + TemplateTypeMap result = existing.copyFilledWithValues(ImmutableList.of(STRING_TYPE)); + + // Then + assertThat(result) + .hasKeysAndValues( + ImmutableList.of(keyA, keyB, keyC), + ImmutableList.of(NUMBER_TYPE, STRING_TYPE, UNKNOWN_TYPE)); + } + + @Test + public void testCopyWithout_canRemoveUnfilledKeys() { + // Given + TemplateType keyA = key("A"); + TemplateType keyB = key("B"); + TemplateType keyC = key("C"); + TemplateTypeMap existing = + createMap(ImmutableList.of(keyA, keyB, keyC), ImmutableList.of(NUMBER_TYPE)); + + // When + TemplateTypeMap result = existing.copyWithoutKeys(ImmutableSet.of(keyB)); + + // Then + assertThat(result) + .hasKeysAndValues(ImmutableList.of(keyA, keyC), ImmutableList.of(NUMBER_TYPE)); + } + + @Test + public void testCopyWithout_retainFilledKeys() { + // Given + TemplateType keyA = key("A"); + TemplateType keyB = key("B"); + TemplateType keyC = key("C"); + TemplateTypeMap existing = + createMap(ImmutableList.of(keyA, keyB, keyC), ImmutableList.of(NUMBER_TYPE)); + + // When + TemplateTypeMap result = existing.copyWithoutKeys(ImmutableSet.of(keyA)); + + // Then + assertThat(result) + .hasKeysAndValues(ImmutableList.of(keyA, keyB, keyC), ImmutableList.of(NUMBER_TYPE)); + } + + @Test + public void testCopyWithout_ifNothingRemoved_returnsSelf() { + // Given + TemplateType keyA = key("A"); + TemplateType keyB = key("B"); + TemplateType keyC = key("C"); + TemplateTypeMap existing = + createMap(ImmutableList.of(keyA, keyB, keyC), ImmutableList.of(NUMBER_TYPE)); + + // When + TemplateTypeMap result = existing.copyWithoutKeys(ImmutableSet.of(keyA)); + + // Then + assertThat(result).isSameInstanceAs(existing); + } + + private TemplateTypeMap createMap( + ImmutableList keys, ImmutableList values) { + return emptyMap.copyWithExtension(keys, values); + } + + private TemplateType key(String name) { + return registry.createTemplateType(name); + } +} diff --git a/test/com/google/javascript/rhino/jstype/TemplatizedTypeTest.java b/test/com/google/javascript/rhino/jstype/TemplatizedTypeTest.java index f86be55cefd..23a5ac1c847 100644 --- a/test/com/google/javascript/rhino/jstype/TemplatizedTypeTest.java +++ b/test/com/google/javascript/rhino/jstype/TemplatizedTypeTest.java @@ -73,13 +73,6 @@ public void testTemplatizedType() { assertThat(arrOfNumber.isEquivalentTo(arrOfString)).isFalse(); } - @Test - public void testEquality() { - // Weird that we allow this as a type at all. - TemplatizedType booleanOfString = createTemplatizedType(BOOLEAN_OBJECT_TYPE, STRING_TYPE); - assertThat(booleanOfString.hashCode()).isEqualTo(BOOLEAN_OBJECT_TYPE.hashCode()); - } - @Test public void testPrint1() { TemplatizedType arrOfString = createTemplatizedType(