Skip to content

Commit

Permalink
Fix issue square#322 by deferring the writing of the generated module…
Browse files Browse the repository at this point in the history
… adapter until all the generation is done in-memory, and interpret Error nodes from the AST more liberally, taking the raw type information from the text of the Error node.
  • Loading branch information
cgruber committed Nov 13, 2013
1 parent 03aa8f7 commit 709a48b
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 50 deletions.
Expand Up @@ -22,6 +22,7 @@
import dagger.internal.Linker;
import dagger.internal.ProblemDetector;
import dagger.internal.SetBinding;
import dagger.internal.codegen.Util.CodeGenerationIncompleteException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
Expand Down Expand Up @@ -95,9 +96,14 @@ public final class GraphAnalysisProcessor extends AbstractProcessor {
}

for (Element element : modules) {
Map<String, Object> annotation = getAnnotation(Module.class, element);
TypeElement moduleType = (TypeElement) element;
Map<String, Object> annotation = null;
try {
annotation = getAnnotation(Module.class, element);
} catch (CodeGenerationIncompleteException e) {
continue; // skip this element. An up-stream compiler error is in play.
}

TypeElement moduleType = (TypeElement) element;
if (annotation == null) {
error("Missing @Module annotation.", moduleType);
continue;
Expand Down
Expand Up @@ -23,7 +23,10 @@
import dagger.internal.Linker;
import dagger.internal.ModuleAdapter;
import dagger.internal.SetBinding;
import dagger.internal.codegen.Util.CodeGenerationIncompleteException;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
Expand All @@ -47,6 +50,7 @@
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
Expand Down Expand Up @@ -77,10 +81,13 @@
*/
@SupportedAnnotationTypes({ "*" })
public final class ModuleAdapterProcessor extends AbstractProcessor {
private final LinkedHashMap<String, List<ExecutableElement>> remainingTypes =
new LinkedHashMap<String, List<ExecutableElement>>();
private static final String BINDINGS_MAP = JavaWriter.type(
Map.class, String.class.getCanonicalName(), Binding.class.getCanonicalName() + "<?>");
private static final List<String> INVALID_RETURN_TYPES =
Arrays.asList(Provider.class.getCanonicalName(), Lazy.class.getCanonicalName());

private final LinkedHashMap<String, List<ExecutableElement>> remainingTypes =
new LinkedHashMap<String, List<ExecutableElement>>();

@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
Expand All @@ -92,19 +99,26 @@ public final class ModuleAdapterProcessor extends AbstractProcessor {
String typeName = i.next();
TypeElement type = processingEnv.getElementUtils().getTypeElement(typeName);
List<ExecutableElement> providesTypes = remainingTypes.get(typeName);

try {
// Attempt to get the annotation. If types are missing, this will throw
// IllegalStateException.
// CodeGenerationIncompleteException.
Map<String, Object> parsedAnnotation = getAnnotation(Module.class, type);
try {
generateModuleAdapter(type, parsedAnnotation, providesTypes);
} catch (IOException e) {
error("Code gen failed: " + e, type);
}
i.remove();
} catch (IllegalStateException e) {
// a dependent type was not defined, we'll catch it on another pass

//TODO(cgruber): Figure out an initial sizing of the StringWriter.
StringWriter stringWriter = new StringWriter();
String adapterName = adapterName(type, MODULE_ADAPTER_SUFFIX);
generateModuleAdapter(stringWriter, adapterName, type, parsedAnnotation, providesTypes);
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(adapterName, type);
Writer sourceWriter = sourceFile.openWriter();
sourceWriter.append(stringWriter.getBuffer());
sourceWriter.close();
} catch (CodeGenerationIncompleteException e) {
continue; // A dependent type was not defined, we'll try to catch it on another pass.
} catch (IOException e) {
error("Code gen failed: " + e, type);
}
i.remove();
}
if (env.processingOver() && remainingTypes.size() > 0) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
Expand Down Expand Up @@ -163,14 +177,17 @@ private Map<String, List<ExecutableElement>> providerMethodsByClass(RoundEnviron

// Invalidate return types.
TypeMirror returnType = types.erasure(providerMethodAsExecutable.getReturnType());
for (String invalidTypeName : Arrays.asList(Provider.class.getCanonicalName(),
Lazy.class.getCanonicalName())) {
TypeElement invalidTypeElement = elementUtils.getTypeElement(invalidTypeName);
if (invalidTypeElement != null && types.isSameType(returnType,
types.erasure(invalidTypeElement.asType()))) {
error(String.format("@Provides method must not return %s directly: %s.%s",
invalidTypeElement, type.getQualifiedName(), providerMethod), providerMethod);
continue provides; // Skip to next provides method.
if (!returnType.getKind().equals(TypeKind.ERROR)) {
// Validate if we have a type to validate (a type yet to be generated by other
// processors is not "invalid" in this way, so ignore).
for (String invalidTypeName : INVALID_RETURN_TYPES) {
TypeElement invalidTypeElement = elementUtils.getTypeElement(invalidTypeName);
if (invalidTypeElement != null && types.isSameType(returnType,
types.erasure(invalidTypeElement.asType()))) {
error(String.format("@Provides method must not return %s directly: %s.%s",
invalidTypeElement, type.getQualifiedName(), providerMethod), providerMethod);
continue provides; // Skip to next provides method.
}
}
}

Expand Down Expand Up @@ -216,8 +233,8 @@ private Set<? extends Element> findProvidesMethods(RoundEnvironment env) {
* Write a companion class for {@code type} that implements {@link
* ModuleAdapter} to expose its provider methods.
*/
private void generateModuleAdapter(TypeElement type, Map<String, Object> module,
List<ExecutableElement> providerMethods) throws IOException {
private void generateModuleAdapter(Writer ioWriter, String adapterName, TypeElement type,
Map<String, Object> module, List<ExecutableElement> providerMethods) throws IOException {
if (module == null) {
error(type + " has @Provides methods but no @Module annotation", type);
return;
Expand All @@ -231,10 +248,7 @@ private void generateModuleAdapter(TypeElement type, Map<String, Object> module,
boolean complete = (Boolean) module.get("complete");
boolean library = (Boolean) module.get("library");

String adapterName = adapterName(type, MODULE_ADAPTER_SUFFIX);
JavaFileObject sourceFile = processingEnv.getFiler()
.createSourceFile(adapterName, type);
JavaWriter writer = new JavaWriter(sourceFile.openWriter());
JavaWriter writer = new JavaWriter(ioWriter);

boolean multibindings = checkForMultibindings(providerMethods);
boolean providerMethodDependencies = checkForDependencies(providerMethods);
Expand Down Expand Up @@ -343,8 +357,8 @@ private void generateModuleAdapter(TypeElement type, Map<String, Object> module,
}

for (ExecutableElement providerMethod : providerMethods) {
generateProvidesAdapter(writer, providerMethod, methodToClassName, methodNameToNextId,
library);
generateProvidesAdapter(
writer, providerMethod, methodToClassName, methodNameToNextId, library);
}

writer.endType();
Expand Down
64 changes: 46 additions & 18 deletions compiler/src/main/java/dagger/internal/codegen/Util.java
Expand Up @@ -137,10 +137,17 @@ public static void typeToString(final TypeMirror type, final StringBuilder resul
return null;
}
@Override public Void visitError(ErrorType errorType, Void v) {
// There's already an error but it may not have been reported (most likely
// a missing import). If we throw an UnsupportedOperationException here
// we'll obscure the real error, so just continue.
result.append("error");
// Error type found, a type may not yet have been generated, but we need the type
// so we can generate the correct code in anticipation of the type being available
// to the compiler.

// Paramterized types which don't exist are returned as an error type whose name is "<any>"
if ("<any>".equals(errorType.toString())) {
throw new CodeGenerationIncompleteException(
"Type reported as <any> is likely a not-yet generated parameterized type.");
}
// TODO(cgruber): Figure out a strategy for non-FQCN cases.
result.append(errorType.toString());
return null;
}
@Override protected Void defaultAction(TypeMirror typeMirror, Void v) {
Expand All @@ -150,19 +157,30 @@ public static void typeToString(final TypeMirror type, final StringBuilder resul
}, null);
}

private static final AnnotationValueVisitor<Object, Void> VALUE_EXTRACTOR
= new SimpleAnnotationValueVisitor6<Object, Void>() {
@Override protected Object defaultAction(Object o, Void v) {
return o;
}
@Override public Object visitArray(List<? extends AnnotationValue> values, Void v) {
Object[] result = new Object[values.size()];
for (int i = 0; i < values.size(); i++) {
result[i] = values.get(i).accept(this, null);
}
return result;
}
};
private static final AnnotationValueVisitor<Object, Void> VALUE_EXTRACTOR =
new SimpleAnnotationValueVisitor6<Object, Void>() {
@Override public Object visitString(String s, Void p) {
if ("<error>".equals(s)) {
throw new CodeGenerationIncompleteException("Unknown type returned as <error>.");
} else if ("<any>".equals(s)) {
throw new CodeGenerationIncompleteException("Unknown type returned as <any>.");
}
return s;
}
@Override public Object visitType(TypeMirror t, Void p) {
return t;
}
@Override protected Object defaultAction(Object o, Void v) {
return o;
}
@Override public Object visitArray(List<? extends AnnotationValue> values, Void v) {
Object[] result = new Object[values.size()];
for (int i = 0; i < values.size(); i++) {
result[i] = values.get(i).accept(this, null);
}
return result;
}
};

/**
* Returns the annotation on {@code element} formatted as a Map. This returns
Expand Down Expand Up @@ -196,7 +214,6 @@ public static Map<String, Object> getAnnotation(Class<?> annotationType, Element
}
return result;
}

return null; // Annotation not found.
}

Expand Down Expand Up @@ -327,4 +344,15 @@ static boolean isStatic(Element element) {
}
return false;
}

/**
* An exception thrown when a type is not extant (returns as an error type),
* usually as a result of another processor not having yet generated its types upon
* which a dagger-annotated type depends.
*/
final static class CodeGenerationIncompleteException extends IllegalStateException {
public CodeGenerationIncompleteException(String s) {
super(s);
}
}
}
Expand Up @@ -18,6 +18,7 @@

import dagger.Module;
import dagger.Provides;
import dagger.internal.codegen.Util.CodeGenerationIncompleteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -64,16 +65,20 @@ public final class ValidationProcessor extends AbstractProcessor {
Map<Element, Element> parametersToTheirMethods = new LinkedHashMap<Element, Element>();
getAllElements(env, allElements, parametersToTheirMethods);
for (Element element : allElements) {
try {
validateProvides(element);
validateScoping(element);
validateQualifiers(element, parametersToTheirMethods);
} catch (CodeGenerationIncompleteException e) {
continue; // Upstream compiler issue in play. Ignore this element.
}
validateScoping(element);
validateQualifiers(element, parametersToTheirMethods);
}
return false;
}

private void validateProvides(Element element) {
if (element.getAnnotation(Provides.class) != null
&& element.getEnclosingElement().getAnnotation(Module.class) == null) {
&& Util.getAnnotation(Module.class, element.getEnclosingElement()) == null) {
error("@Provides methods must be declared in modules: " + elementToString(element), element);
}
}
Expand Down

0 comments on commit 709a48b

Please sign in to comment.