blob: 7de55520842844e24d237b34846de88fc316e030 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.google.dart.compiler.resolver;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.dart.compiler.DartCompilationError;
import com.google.dart.compiler.DartCompilerListener;
import com.google.dart.compiler.ErrorCode;
import com.google.dart.compiler.ast.ASTVisitor;
import com.google.dart.compiler.ast.DartClass;
import com.google.dart.compiler.ast.DartIdentifier;
import com.google.dart.compiler.ast.DartNode;
import com.google.dart.compiler.ast.DartParameterizedTypeNode;
import com.google.dart.compiler.ast.DartTypeNode;
import com.google.dart.compiler.ast.DartTypeParameter;
import com.google.dart.compiler.ast.DartUnit;
import com.google.dart.compiler.ast.LibraryUnit;
import com.google.dart.compiler.ast.Modifiers;
import com.google.dart.compiler.common.ErrorExpectation;
import com.google.dart.compiler.parser.DartParser;
import com.google.dart.compiler.testing.TestCompilerContext;
import com.google.dart.compiler.type.DynamicType;
import com.google.dart.compiler.type.InterfaceType;
import com.google.dart.compiler.type.Type;
import com.google.dart.compiler.type.Types;
import com.google.dart.compiler.util.DartSourceString;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Utility methods for resolver tests.
*/
abstract class ResolverTestCase extends TestCase {
private List<DartCompilationError> parseErrors = Lists.newArrayList();
@Override
public void setUp() {
resetParseErrors();
}
@Override
public void tearDown() {
resetParseErrors();
}
private static CoreTypeProvider setupTypeProvider(DartUnit unit, TestCompilerContext context, Scope scope) {
new TopLevelElementBuilder().exec(unit.getLibrary(), unit, context);
new TopLevelElementBuilder().fillInUnitScope(unit, context, scope, null);
ClassElement object = (ClassElement) scope.findElement(null, "Object");
assertNotNull("Cannot resolve Object", object);
return new MockCoreTypeProvider(object);
}
static Scope resolve(DartUnit unit, TestCompilerContext context) {
LibraryUnit libraryUnit = MockLibraryUnit.create(unit);
// Prepare for running phases.
Scope scope = libraryUnit.getElement().getScope();
CoreTypeProvider typeProvider = setupTypeProvider(unit, context, scope);
// Run phases as in compiler.
new SupertypeResolver().exec(unit, context, scope, typeProvider);
new MemberBuilder().exec(unit, context, scope, typeProvider);
new Resolver(context, scope, typeProvider).exec(unit);
// TODO(zundel): One day, we want all AST nodes that are identifiers to point to
// elements if they are resolved. Uncommenting this line helps track missing elements
// down.
// ResolverAuditVisitor.exec(unit);
return scope;
}
static Scope resolveCompileTimeConst(DartUnit unit, TestCompilerContext context) {
LibraryUnit libraryUnit = MockLibraryUnit.create(unit);
// Prepare for running phases.
Scope scope = libraryUnit.getElement().getScope();
CoreTypeProvider typeProvider = setupTypeProvider(unit, context, scope);
// Run phases as in compiler.
new SupertypeResolver().exec(unit, context, scope, typeProvider);
new MemberBuilder().exec(unit, context, scope, typeProvider);
new Resolver.Phase().exec(unit, context, typeProvider);
new CompileTimeConstantAnalyzer(typeProvider, context).exec(unit);
return scope;
}
static DartClass makeClass(String name, DartTypeNode supertype, String... typeParameters) {
return makeClass(name, supertype, Collections.<DartTypeNode>emptyList(), typeParameters);
}
static DartClass makeInterface(String name, String... typeParameters) {
return makeInterface(name, Collections.<DartTypeNode>emptyList(), null, typeParameters);
}
static DartClass makeClass(String name, DartTypeNode supertype, List<DartTypeNode> interfaces,
String... typeParameters) {
List<DartTypeNode> mixins = new ArrayList<DartTypeNode>();
List<DartTypeParameter> parameterNodes = new ArrayList<DartTypeParameter>();
for (String parameter : typeParameters) {
parameterNodes.add(makeTypeVariable(parameter));
}
List<DartNode> members = Arrays.<DartNode> asList();
return new DartClass(-1, 0, new DartIdentifier(name), null, supertype, -1, interfaces, mixins,
-1, -1, -1, members, parameterNodes, null, false, Modifiers.NONE);
}
static DartClass makeInterface(String name, List<DartTypeNode> interfaces,
DartParameterizedTypeNode defaultClass, String... typeParameters) {
List<DartTypeNode> mixins = new ArrayList<DartTypeNode>();
List<DartTypeParameter> parameterNodes = new ArrayList<DartTypeParameter>();
for (String parameter : typeParameters) {
parameterNodes.add(makeTypeVariable(parameter));
}
List<DartNode> members = Arrays.<DartNode> asList();
return new DartClass(-1, 0, new DartIdentifier(name), null, null, -1, interfaces, mixins, -1,
-1, -1, members, parameterNodes, defaultClass, true, Modifiers.NONE);
}
static DartParameterizedTypeNode makeDefault(String name) {
return new DartParameterizedTypeNode(new DartIdentifier(name), null);
}
private static DartTypeParameter makeTypeVariable(String name) {
return new DartTypeParameter(new DartIdentifier(name), null);
}
/**
* Look for DartIdentifier nodes in the tree whose elements are null. They should all either
* be resolved, or marked as an unresolved element.
*/
static class ResolverAuditVisitor extends ASTVisitor<Void> {
private List<String> failures = Lists.newArrayList();
@Override
public Void visitIdentifier(DartIdentifier node) {
if (node.getElement() == null) {
failures.add("Identifier: "
+ node.getName()
+ " has null element @ ("
+ node.getSourceInfo().getLine()
+ ":"
+ node.getSourceInfo().getColumn()
+ ")");
}
return null;
}
public List<String> getFailures() {
return failures;
}
public static void exec(DartNode root) {
ResolverAuditVisitor visitor = new ResolverAuditVisitor();
root.accept(visitor);
List<String> results = visitor.getFailures();
if (results.size() > 0) {
StringBuilder out = new StringBuilder("Missing elements found in AST\n");
Joiner.on("\n").appendTo(out, results);
fail(out.toString());
}
}
}
static class MockCoreTypeProvider implements CoreTypeProvider {
private final InterfaceType boolType;
private final InterfaceType intType;
private final InterfaceType doubleType;
private final InterfaceType numType;
private final InterfaceType stringType;
private final InterfaceType functionType;
private final InterfaceType dynamicType;
private final InterfaceType defaultMapLiteralType;
private final InterfaceType defaultListType;
private final InterfaceType typeType;
private final Type voidType;
private final ClassElement objectElement;
{
ClassElement dynamicElement = Elements.classNamed("dynamic");
dynamicType = Types.interfaceType(dynamicElement, Collections.<Type>emptyList());
dynamicElement.setType(dynamicType);
ClassElement boolElement = Elements.classNamed("bool");
boolType = Types.interfaceType(boolElement, Collections.<Type>emptyList());
boolElement.setType(boolType);
ClassElement intElement = Elements.classNamed("int");
intType = Types.interfaceType(intElement, Collections.<Type>emptyList());
intElement.setType(intType);
ClassElement doubleElement = Elements.classNamed("double");
doubleType = Types.interfaceType(doubleElement, Collections.<Type>emptyList());
doubleElement.setType(doubleType);
ClassElement numElement = Elements.classNamed("num");
numType = Types.interfaceType(numElement, Collections.<Type>emptyList());
numElement.setType(numType);
ClassElement stringElement = Elements.classNamed("String");
stringType = Types.interfaceType(stringElement, Collections.<Type>emptyList());
stringElement.setType(stringType);
ClassElement functionElement = Elements.classNamed("Function");
functionType = Types.interfaceType(functionElement, Collections.<Type>emptyList());
functionElement.setType(functionType);
ClassElement mapElement = Elements.classNamed("Map");
defaultMapLiteralType =
Types.interfaceType(mapElement, Lists.<Type>newArrayList(stringType, dynamicType));
mapElement.setType(defaultMapLiteralType);
ClassElement listElement = Elements.classNamed("List");
defaultListType = Types.interfaceType(listElement, Lists.<Type>newArrayList(dynamicType));
listElement.setType(defaultListType);
ClassElement typeElement = Elements.classNamed("Type");
typeType = Types.interfaceType(typeElement, Collections.<Type>emptyList());
listElement.setType(defaultListType);
voidType = Types.newVoidType();
}
MockCoreTypeProvider(ClassElement objectElement) {
this.objectElement = objectElement;
}
@Override
public InterfaceType getIntType() {
return intType;
}
@Override
public InterfaceType getDoubleType() {
return doubleType;
}
@Override
public InterfaceType getNumType() {
return numType;
}
@Override
public InterfaceType getBoolType() {
return boolType;
}
@Override
public InterfaceType getStringType() {
return stringType;
}
@Override
public InterfaceType getFunctionType() {
return functionType;
}
@Override
public InterfaceType getArrayType(Type elementType) {
return defaultListType;
}
@Override
public Type getNullType() {
throw new AssertionError();
}
@Override
public Type getVoidType() {
return voidType;
}
@Override
public DynamicType getDynamicType() {
return Types.newDynamicType();
}
@Override
public InterfaceType getMapType(Type key, Type value) {
return defaultMapLiteralType;
}
@Override
public InterfaceType getObjectType() {
return objectElement.getType();
}
@Override
public InterfaceType getIteratorType(Type elementType) {
throw new AssertionError();
}
@Override
public InterfaceType getTypeType() {
return typeType;
}
}
protected static DartTypeNode makeType(String name, String... arguments) {
List<DartTypeNode> argumentNodes = makeTypes(arguments);
return new DartTypeNode(new DartIdentifier(name), argumentNodes);
}
static List<DartTypeNode> makeTypes(String... typeNames) {
List<DartTypeNode> types = new ArrayList<DartTypeNode>();
for (String typeName : typeNames) {
types.add(makeType(typeName));
}
return types;
}
protected static DartUnit makeUnit(DartNode... topLevelElements) {
DartUnit unit = new DartUnit(null, false);
for (DartNode topLevelElement : topLevelElements) {
unit.getTopLevelNodes().add(topLevelElement);
}
return unit;
}
protected DartUnit parseUnit(String firstLine, String secondLine, String... rest) {
return parseUnit(Joiner.on('\n').join(firstLine, secondLine, (Object[]) rest).toString());
}
protected DartUnit parseUnit(String string) {
DartSourceString source = new DartSourceString("<source string>", string);
DartParser parser = new DartParser(
source,
string,
false,
Sets.<String>newHashSet(),
getListener(),
null);
return parser.parseUnit();
}
private DartCompilerListener getListener() {
return new DartCompilerListener.Empty() {
@Override
public void onError(DartCompilationError event) {
parseErrors.add(event);
}
};
}
protected void checkExpectedErrors(ErrorCode[] errorCodes) {
checkExpectedErrors(parseErrors, errorCodes, null);
}
/**
* Given a list of errors encountered during parse/resolve, compare them to
* a list of expected error codes.
*
* @param encountered errors actually encountered
* @param errorCodes expected errors.
*/
protected void checkExpectedErrors(List<DartCompilationError> encountered,
ErrorCode[] errorCodes,
String source) {
if (errorCodes.length != encountered.size()) {
printSource(source);
printEncountered(encountered);
assertEquals(errorCodes.length, encountered.size());
}
int index = 0;
for (ErrorCode errorCode : errorCodes) {
ErrorCode found = encountered.get(index).getErrorCode();
if (!found.equals(errorCode)) {
printSource(source);
printEncountered(encountered);
assertEquals("Unexpected Error Code: ", errorCode, found);
}
index++;
}
}
/**
* Returns a context with a listener that remembers all DartCompilationErrors and records
* them.
* @return
*/
protected TestCompilerContext getContext() {
return new TestCompilerContext() {
@Override
public void onError(DartCompilationError event) {
recordParseError(event);
}
};
}
/**
* Resets the global list of encountered errors. Call this before evaluating a new test.
*/
protected void resetParseErrors() {
parseErrors = Lists.newArrayList();
}
/**
* Save an error event in the global list of encountered errors. For use by
* custom {@link DartCompilerListener} implementations.
*/
protected void recordParseError(DartCompilationError event) {
parseErrors.add(event);
}
protected void printSource(String source) {
if (source != null) {
int count = 1;
for (String line : Splitter.on("\n").split(source)) {
System.out.println(String.format(" %02d: %s", count++, line));
}
}
}
/**
* For debugging.
*/
protected void printEncountered(List<DartCompilationError> encountered) {
for (DartCompilationError error : encountered) {
ErrorCode errorCode = error.getErrorCode();
String msg =
String.format(
"%s > %s (%d:%d)",
errorCode.toString(),
error.getMessage(),
error.getLineNumber(),
error.getColumnNumber());
System.out.println(msg);
}
}
/**
* Convenience method to parse and resolve a code snippet, then test for error codes.
*
* @return resolve errors.
*/
protected List<DartCompilationError> resolveAndTest(String source, ErrorCode errorCode, ErrorCode... errorCodeRest) {
ErrorCode errorCodes[] = new ErrorCode[errorCodeRest.length + 1];
errorCodes[0] = errorCode;
if (errorCodeRest.length > 0) {
System.arraycopy(errorCodeRest, 0, errorCodes, 1, errorCodeRest.length);
}
// parse DartUnit
DartUnit unit = parseUnit(source);
if (parseErrors.size() != 0) {
printSource(source);
printEncountered(parseErrors);
assertEquals("Expected no errors in parse step:", 0, parseErrors.size());
}
// prepare for recording resolving errors
resetParseErrors();
final List<DartCompilationError> resolveErrors = Lists.newArrayList();
TestCompilerContext ctx = new TestCompilerContext() {
@Override
public void onError(DartCompilationError event) {
resolveErrors.add(event);
}
};
// resolve and check errors
resolve(unit, ctx);
checkExpectedErrors(resolveErrors, errorCodes, source);
return resolveErrors;
}
protected List<DartCompilationError> resolveAndTest(String source,
ErrorExpectation... expectedErrors) {
// parse DartUnit
DartUnit unit = parseUnit(source);
if (parseErrors.size() != 0) {
printSource(source);
printEncountered(parseErrors);
assertEquals("Expected no errors in parse step:", 0, parseErrors.size());
}
// prepare for recording resolving errors
resetParseErrors();
final List<DartCompilationError> resolveErrors = Lists.newArrayList();
TestCompilerContext ctx = new TestCompilerContext() {
@Override
public void onError(DartCompilationError event) {
resolveErrors.add(event);
}
};
// resolve and check errors
resolve(unit, ctx);
ErrorExpectation.assertErrors(resolveErrors, expectedErrors);
return resolveErrors;
}
/**
* Convenience method to parse and resolve a code snippet, then test for error codes.
*
* @return resolve errors.
*/
protected List<DartCompilationError> resolveAndTestCtConst(String source, ErrorCode... errorCodes) {
// parse DartUnit
DartUnit unit = parseUnit(source);
if (parseErrors.size() != 0) {
printSource(source);
printEncountered(parseErrors);
assertEquals("Expected no errors in parse step:", 0, parseErrors.size());
}
// prepare for recording resolving errors
resetParseErrors();
final List<DartCompilationError> resolveErrors = Lists.newArrayList();
TestCompilerContext ctx = new TestCompilerContext() {
@Override
public void onError(DartCompilationError event) {
resolveErrors.add(event);
}
};
// resolve and check errors
resolveCompileTimeConst(unit, ctx);
checkExpectedErrors(resolveErrors, errorCodes, source);
return resolveErrors;
}
protected List<DartCompilationError> resolveAndTestCtConstExpectErrors(String source, ErrorExpectation... expectedErrors) {
// parse DartUnit
DartUnit unit = parseUnit(source);
if (parseErrors.size() != 0) {
printSource(source);
printEncountered(parseErrors);
assertEquals("Expected no errors in parse step:", 0, parseErrors.size());
}
// prepare for recording resolving errors
resetParseErrors();
final List<DartCompilationError> resolveErrors = Lists.newArrayList();
TestCompilerContext ctx = new TestCompilerContext() {
@Override
public void onError(DartCompilationError event) {
resolveErrors.add(event);
}
};
// resolve and check errors
resolveCompileTimeConst(unit, ctx);
ErrorExpectation.assertErrors(resolveErrors, expectedErrors);
return resolveErrors;
}
}