/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import static com.google.javascript.jscomp.ConcreteType.ALL;
import static com.google.javascript.jscomp.ConcreteType.NONE;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.ConcreteType.ConcreteFunctionType;
import com.google.javascript.jscomp.ConcreteType.ConcreteInstanceType;
import com.google.javascript.jscomp.ConcreteType.ConcreteUnionType;
import com.google.javascript.jscomp.ConcreteType.Factory;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
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.ObjectType;
import com.google.javascript.rhino.jstype.StaticReference;
import com.google.javascript.rhino.jstype.StaticScope;
import com.google.javascript.rhino.jstype.StaticSlot;
import com.google.javascript.rhino.testing.AbstractStaticScope;
import com.google.javascript.rhino.testing.TestErrorReporter;
import junit.framework.TestCase;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Unit test for the the subclasses of ConcreteType.
*
*/
public class ConcreteTypeTest extends TestCase {
private JSTypeRegistry typeRegistry;
private JSType unknownType;
private Factory factory;
@Override
public void setUp() {
typeRegistry = new JSTypeRegistry(new TestErrorReporter(null, null));
unknownType = typeRegistry.getNativeType(JSTypeNative.UNKNOWN_TYPE);
factory = new FakeFactory();
}
private void checkEquality(List<ConcreteType> types) {
for (int i = 0; i < types.size(); ++i) {
for (int j = 0; j < types.size(); ++j) {
if (i == j) {
assertEquals(types.get(i), types.get(j));
} else {
assertFalse(types.get(i).equals(types.get(j)));
}
}
}
}
public void testEquals() {
ConcreteFunctionType fun1 = createFunction("fun1");
ConcreteFunctionType fun2 = createFunction("fun2");
ConcreteType obj1 = fun1.getInstanceType();
ConcreteType obj2 = fun2.getInstanceType();
ConcreteType union1 = new ConcreteUnionType(fun1, fun2);
ConcreteType union2 = new ConcreteUnionType(fun1, obj1);
ConcreteType union3 = new ConcreteUnionType(fun1, obj1);
checkEquality(Lists.newArrayList(fun1, fun2, obj1, obj2,
union1, union2));
assertEquals(union2, union3);
}
public void testUnionWith() {
ConcreteFunctionType fun = createFunction("fun");
ConcreteType obj = fun.getInstanceType();
ConcreteType both = new ConcreteUnionType(fun, obj);
assertTrue(fun.isSingleton());
assertTrue(obj.isSingleton());
assertFalse(both.isSingleton());
assertFalse(NONE.isSingleton());
assertFalse(ALL.isSingleton());
checkUnionWith(fun, NONE, fun);
checkUnionWith(fun, ALL, ALL);
checkUnionWith(fun, obj, both);
checkUnionWith(both, NONE, both);
checkUnionWith(both, ALL, ALL);
}
private void checkUnionWith(ConcreteType a, ConcreteType b, ConcreteType c) {
assertEquals(a, a.unionWith(a));
assertEquals(b, b.unionWith(b));
assertEquals(c, a.unionWith(b));
assertEquals(c, b.unionWith(a));
}
public void testIntersectionWith() {
ConcreteFunctionType fun = createFunction("fun");
ConcreteFunctionType fun2 = createFunction("fun2");
ConcreteType obj = fun.getInstanceType();
ConcreteType both = new ConcreteUnionType(fun, obj);
assertEquals(NONE, fun.intersectWith(obj));
assertEquals(NONE, obj.intersectWith(fun));
assertEquals(fun, both.intersectWith(fun));
assertEquals(fun, fun.intersectWith(both));
assertEquals(NONE, NONE.intersectWith(both));
assertEquals(NONE, both.intersectWith(NONE));
assertEquals(NONE, fun.intersectWith(NONE));
assertEquals(NONE, NONE.intersectWith(fun));
assertEquals(NONE, both.intersectWith(fun2));
assertEquals(both, ALL.intersectWith(both));
assertEquals(both, both.intersectWith(ALL));
assertEquals(fun, ALL.intersectWith(fun));
assertEquals(fun, fun.intersectWith(ALL));
assertEquals(NONE, ALL.intersectWith(NONE));
assertEquals(NONE, NONE.intersectWith(ALL));
}
public void testFunction() {
ConcreteFunctionType fun = createFunction("fun", "a", "b");
assertTrue(fun.isFunction());
assertNotNull(fun.getCallSlot());
assertNotNull(fun.getReturnSlot());
assertNotNull(fun.getParameterSlot(0));
assertNotNull(fun.getParameterSlot(1));
assertNull(fun.getParameterSlot(2));
assertTrue(fun.getInstanceType().isInstance());
}
public void testInstance() {
ConcreteInstanceType obj = createInstance("MyObj", "a", "b");
assertTrue(obj.isInstance());
assertNotNull(obj.getPropertySlot("a"));
assertNotNull(obj.getPropertySlot("b"));
assertNull(obj.getPropertySlot("c"));
// The prototype chain should be: MyObj -> MyObj.prototype -> Object ->
// Object.prototype -> null.
for (int i = 0; i < 3; ++i) {
assertNotNull(obj = obj.getImplicitPrototype());
assertTrue(obj.isInstance());
}
assertNull(obj.getImplicitPrototype());
}
public void testGetX() {
ConcreteFunctionType fun1 = createFunction("fun1");
ConcreteFunctionType fun2 = createFunction("fun2");
ConcreteInstanceType obj1 = fun1.getInstanceType();
ConcreteInstanceType obj2 = fun2.getInstanceType();
ConcreteType union1 = fun1.unionWith(obj1);
ConcreteType union2 =
union1.unionWith(fun2).unionWith(obj2);
assertEqualSets(Lists.newArrayList(), NONE.getFunctions());
assertEqualSets(Lists.newArrayList(), NONE.getInstances());
assertEqualSets(Lists.newArrayList(fun1), fun1.getFunctions());
assertEqualSets(Lists.newArrayList(), fun1.getInstances());
assertEqualSets(Lists.newArrayList(), obj1.getFunctions());
assertEqualSets(Lists.newArrayList(obj1), obj1.getInstances());
assertEqualSets(Lists.newArrayList(fun1), union1.getFunctions());
assertEqualSets(Lists.newArrayList(obj1), union1.getInstances());
assertEqualSets(Lists.newArrayList(fun1, fun2), union2.getFunctions());
assertEqualSets(Lists.newArrayList(obj1, obj2), union2.getInstances());
}
/** Checks that the two collections are equal as sets. */
private void assertEqualSets(Collection<?> first, Collection<?> second) {
assertEquals(Sets.newHashSet(first), Sets.newHashSet(second));
}
/** Creates a fake function with the given description. */
private ConcreteFunctionType createFunction(
String name, String... paramNames) {
Node args = new Node(Token.LP);
for (int i = 0; i < paramNames.length; ++i) {
args.addChildToBack(Node.newString(Token.NAME, paramNames[i]));
}
Node decl = new Node(Token.FUNCTION,
Node.newString(Token.NAME, name),
args,
new Node(Token.BLOCK));
JSType[] paramTypes = new JSType[paramNames.length];
Arrays.fill(paramTypes, unknownType);
decl.setJSType(
typeRegistry.createConstructorType(name, decl, args, unknownType));
return new ConcreteFunctionType(factory, decl, null);
}
/** Creates a fake instance with the given description. */
private ConcreteInstanceType createInstance(
String name, String... propNames) {
ObjectType objType = typeRegistry.createObjectType(name, null,
typeRegistry.createObjectType(name + ".prototype", null, null));
for (int i = 0; i < propNames.length; ++i) {
objType.defineDeclaredProperty(propNames[i], unknownType, null);
}
return new ConcreteInstanceType(factory, objType);
}
private class FakeFactory implements Factory {
private final Map<Node, ConcreteFunctionType> functionByDeclaration =
Maps.newHashMap();
private final Map<FunctionType, ConcreteFunctionType> functionByJSType =
Maps.newHashMap();
private final Map<ObjectType, ConcreteInstanceType> instanceByJSType =
Maps.newHashMap();
private final JSTypeRegistry registry = new JSTypeRegistry(
new TestErrorReporter(null, null));
@Override
public JSTypeRegistry getTypeRegistry() {
return registry;
}
/** {@inheritDoc} */
@Override
public ConcreteFunctionType createConcreteFunction(
Node decl, StaticScope<ConcreteType> parent) {
ConcreteFunctionType funcType = functionByDeclaration.get(decl);
if (funcType == null) {
functionByDeclaration.put(decl, funcType =
new ConcreteFunctionType(this, decl, parent));
if (decl.getJSType() != null) {
functionByJSType.put((FunctionType) decl.getJSType(), funcType);
}
}
return funcType;
}
/** {@inheritDoc} */
@Override
public ConcreteInstanceType createConcreteInstance(
ObjectType instanceType) {
ConcreteInstanceType instType = instanceByJSType.get(instanceType);
if (instType == null) {
instanceByJSType.put(instanceType,
instType = new ConcreteInstanceType(this, instanceType));
}
return instType;
}
/** {@inheritDoc} */
@Override
public ConcreteFunctionType getConcreteFunction(FunctionType functionType) {
return functionByJSType.get(functionType);
}
/** {@inheritDoc} */
@Override
public ConcreteInstanceType getConcreteInstance(ObjectType instanceType) {
return instanceByJSType.get(instanceType);
}
/** {@inheritDoc} */
@Override
public StaticScope<ConcreteType> createFunctionScope(
Node decl, StaticScope<ConcreteType> parent) {
FakeScope scope = new FakeScope((FakeScope) parent);
scope.addSlot(ConcreteFunctionType.CALL_SLOT_NAME);
scope.addSlot(ConcreteFunctionType.THIS_SLOT_NAME);
scope.addSlot(ConcreteFunctionType.RETURN_SLOT_NAME);
for (Node n = decl.getFirstChild().getNext().getFirstChild();
n != null;
n = n.getNext()) {
scope.addSlot(n.getString());
}
return scope;
}
/** {@inheritDoc} */
@Override
public StaticScope<ConcreteType> createInstanceScope(
ObjectType instanceType) {
FakeScope parentScope = null;
if (instanceType.getImplicitPrototype() != null) {
ConcreteInstanceType prototype =
createConcreteInstance(instanceType.getImplicitPrototype());
parentScope = (FakeScope) prototype.getScope();
}
FakeScope scope = new FakeScope(parentScope);
for (String propName : instanceType.getOwnPropertyNames()) {
scope.addSlot(propName);
}
return scope;
}
}
// TODO(user): move to a common place if it can be used elsewhere
private class FakeScope extends AbstractStaticScope<ConcreteType> {
private final FakeScope parent;
private final Map<String, FakeSlot> slots = Maps.newHashMap();
FakeScope(FakeScope parent) {
this.parent = parent;
}
/** {@inheritDoc} */
@Override
public StaticScope<ConcreteType> getParentScope() { return parent; }
/** {@inheritDoc} */
@Override
public StaticSlot<ConcreteType> getOwnSlot(String name) {
return slots.get(name);
}
/** {@inheritDoc} */
@Override
public StaticSlot<ConcreteType> getSlot(String name) {
if (slots.containsKey(name)) {
return slots.get(name);
} else if (parent != null) {
return parent.getSlot(name);
} else {
return null;
}
}
/** {@inheritDoc} */
@Override
public ConcreteType getTypeOfThis() { return ConcreteType.ALL; }
void addSlot(String name) {
slots.put(name, new FakeSlot(name));
}
}
// TODO(user): move to a common place if it can be used elsewhere
private class FakeSlot implements StaticSlot<ConcreteType> {
private final String name;
FakeSlot(String name) {
this.name = name;
}
@Override
public String getName() { return name; }
@Override
public ConcreteType getType() { return ConcreteType.ALL; }
@Override
public boolean isTypeInferred() { return true; }
@Override
public StaticReference<ConcreteType> getDeclaration() { return null; }
}
}