Skip to content

Commit

Permalink
Update hjar generators to output valid method bodies for methods and …
Browse files Browse the repository at this point in the history
…constructors.

This CL updates Dagger's hjar generators to have valid return statements for methods with non-`void` return types and valid super calls for constructors. For example:

```
// Example of hjar generated class with valid constructor and method bodies.
class Foo extends FooSuper {
  Foo() {
    super(null, 0, null);
  }

  String stringMethod() {
    return null;
  }

  int intMethod() {
    return 0;
  }
}
```

Note that this isn't required when compiling with Turbine, but is necessary when compiling the Hjar stubs in Javac.

RELNOTES=N/A
PiperOrigin-RevId: 588456719
  • Loading branch information
bcorso authored and Dagger Team committed Dec 6, 2023
1 parent 344e135 commit 668269d
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 141 deletions.
32 changes: 21 additions & 11 deletions java/dagger/internal/codegen/DelegateComponentProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import dagger.internal.codegen.base.ClearableCache;
import dagger.internal.codegen.base.SourceFileGenerationException;
import dagger.internal.codegen.base.SourceFileGenerator;
import dagger.internal.codegen.base.SourceFileHjarGenerator;
import dagger.internal.codegen.binding.BindingGraphFactory;
import dagger.internal.codegen.binding.InjectBindingRegistry;
import dagger.internal.codegen.binding.MembersInjectionBinding;
Expand All @@ -53,7 +54,6 @@
import dagger.internal.codegen.validation.InjectValidator;
import dagger.internal.codegen.validation.ValidationBindingGraphPlugins;
import dagger.internal.codegen.writing.FactoryGenerator;
import dagger.internal.codegen.writing.HjarSourceFileGenerator;
import dagger.internal.codegen.writing.MembersInjectorGenerator;
import dagger.internal.codegen.writing.ModuleGenerator;
import dagger.internal.codegen.writing.ModuleProxies.ModuleConstructorProxyGenerator;
Expand Down Expand Up @@ -191,34 +191,44 @@ interface ProcessingRoundCacheModule {
interface SourceFileGeneratorsModule {
@Provides
static SourceFileGenerator<ProvisionBinding> factoryGenerator(
FactoryGenerator generator, CompilerOptions compilerOptions) {
return hjarWrapper(generator, compilerOptions);
FactoryGenerator generator,
CompilerOptions compilerOptions,
XProcessingEnv processingEnv) {
return hjarWrapper(generator, compilerOptions, processingEnv);
}

@Provides
static SourceFileGenerator<ProductionBinding> producerFactoryGenerator(
ProducerFactoryGenerator generator, CompilerOptions compilerOptions) {
return hjarWrapper(generator, compilerOptions);
ProducerFactoryGenerator generator,
CompilerOptions compilerOptions,
XProcessingEnv processingEnv) {
return hjarWrapper(generator, compilerOptions, processingEnv);
}

@Provides
static SourceFileGenerator<MembersInjectionBinding> membersInjectorGenerator(
MembersInjectorGenerator generator, CompilerOptions compilerOptions) {
return hjarWrapper(generator, compilerOptions);
MembersInjectorGenerator generator,
CompilerOptions compilerOptions,
XProcessingEnv processingEnv) {
return hjarWrapper(generator, compilerOptions, processingEnv);
}

@Provides
@ModuleGenerator
static SourceFileGenerator<XTypeElement> moduleConstructorProxyGenerator(
ModuleConstructorProxyGenerator generator, CompilerOptions compilerOptions) {
return hjarWrapper(generator, compilerOptions);
ModuleConstructorProxyGenerator generator,
CompilerOptions compilerOptions,
XProcessingEnv processingEnv) {
return hjarWrapper(generator, compilerOptions, processingEnv);
}
}

private static <T> SourceFileGenerator<T> hjarWrapper(
SourceFileGenerator<T> generator, CompilerOptions compilerOptions) {
SourceFileGenerator<T> generator,
CompilerOptions compilerOptions,
XProcessingEnv processingEnv) {
return compilerOptions.headerCompilation()
? HjarSourceFileGenerator.wrap(generator)
? SourceFileHjarGenerator.wrap(generator, processingEnv)
: generator;
}
}
211 changes: 211 additions & 0 deletions java/dagger/internal/codegen/base/SourceFileHjarGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* Copyright (C) 2017 The Dagger 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 dagger.internal.codegen.base;

import static com.squareup.javapoet.MethodSpec.constructorBuilder;
import static com.squareup.javapoet.MethodSpec.methodBuilder;
import static com.squareup.javapoet.TypeSpec.classBuilder;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom;
import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
import static javax.lang.model.element.Modifier.PRIVATE;

import androidx.room.compiler.processing.XConstructorElement;
import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XExecutableParameterElement;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import dagger.internal.codegen.javapoet.CodeBlocks;
import dagger.internal.codegen.javapoet.TypeNames;
import java.util.Optional;
import javax.lang.model.element.Modifier;

/**
* A source file generator that only writes the relevant code necessary for Bazel to create a
* correct header (ABI) jar.
*/
public final class SourceFileHjarGenerator<T> extends SourceFileGenerator<T> {
public static <T> SourceFileGenerator<T> wrap(
SourceFileGenerator<T> delegate, XProcessingEnv processingEnv) {
return new SourceFileHjarGenerator<>(delegate, processingEnv);
}

private final SourceFileGenerator<T> delegate;
private final XProcessingEnv processingEnv;

private SourceFileHjarGenerator(SourceFileGenerator<T> delegate, XProcessingEnv processingEnv) {
super(delegate);
this.delegate = delegate;
this.processingEnv = processingEnv;
}

@Override
public XElement originatingElement(T input) {
return delegate.originatingElement(input);
}

@Override
public ImmutableList<TypeSpec.Builder> topLevelTypes(T input) {
String packageName = closestEnclosingTypeElement(originatingElement(input)).getPackageName();
return delegate.topLevelTypes(input).stream()
.map(completeType -> skeletonType(packageName, completeType.build()))
.collect(toImmutableList());
}

private TypeSpec.Builder skeletonType(String packageName, TypeSpec completeType) {
TypeSpec.Builder skeleton =
classBuilder(completeType.name)
.addSuperinterfaces(completeType.superinterfaces)
.addTypeVariables(completeType.typeVariables)
.addModifiers(completeType.modifiers.toArray(new Modifier[0]))
.addAnnotations(completeType.annotations);

if (!completeType.superclass.equals(ClassName.OBJECT)) {
skeleton.superclass(completeType.superclass);
}

completeType.methodSpecs.stream()
.filter(method -> !method.modifiers.contains(PRIVATE) || method.isConstructor())
.map(completeMethod -> skeletonMethod(packageName, completeType, completeMethod))
.forEach(skeleton::addMethod);

completeType.fieldSpecs.stream()
.filter(field -> !field.modifiers.contains(PRIVATE))
.map(this::skeletonField)
.forEach(skeleton::addField);

completeType.typeSpecs.stream()
.map(type -> skeletonType(packageName, type).build())
.forEach(skeleton::addType);

completeType.alwaysQualifiedNames
.forEach(skeleton::alwaysQualify);

return skeleton;
}

private MethodSpec skeletonMethod(
String packageName, TypeSpec completeType, MethodSpec completeMethod) {
MethodSpec.Builder skeleton =
completeMethod.isConstructor()
? constructorBuilder()
: methodBuilder(completeMethod.name).returns(completeMethod.returnType);

if (completeMethod.isConstructor()) {
getRequiredSuperCall(packageName, completeType)
.ifPresent(superCall -> skeleton.addStatement("$L", superCall));
} else if (!completeMethod.returnType.equals(TypeName.VOID)) {
skeleton.addStatement("return $L", getDefaultValueCodeBlock(completeMethod.returnType));
}

return skeleton
.addModifiers(completeMethod.modifiers)
.addTypeVariables(completeMethod.typeVariables)
.addParameters(completeMethod.parameters)
.addExceptions(completeMethod.exceptions)
.varargs(completeMethod.varargs)
.addAnnotations(completeMethod.annotations)
.build();
}

private Optional<CodeBlock> getRequiredSuperCall(String packageName, TypeSpec completeType) {
if (completeType.superclass.equals(TypeName.OBJECT)) {
return Optional.empty();
}

ClassName rawSuperClass = (ClassName) TypeNames.rawTypeName(completeType.superclass);
XTypeElement superTypeElement =
processingEnv.requireTypeElement(rawSuperClass.canonicalName());

ImmutableSet<XConstructorElement> accessibleConstructors =
superTypeElement.getConstructors().stream()
.filter(
constructor ->
// isElementAccessibleFrom doesn't take protected into account so check manually
constructor.isProtected()
|| isElementAccessibleFrom(constructor, packageName))
.collect(toImmutableSet());

// If there's an accessible default constructor we don't need to call super() manually.
if (accessibleConstructors.isEmpty()
|| accessibleConstructors.stream()
.anyMatch(constructor -> constructor.getParameters().isEmpty())) {
return Optional.empty();
}

return Optional.of(
CodeBlock.of(
"super($L)",
CodeBlocks.makeParametersCodeBlock(
// We just choose the first constructor (it doesn't really matter since we're just
// trying to ensure the constructor body compiles).
accessibleConstructors.stream().findFirst().get().getParameters().stream()
.map(XExecutableParameterElement::getType)
.map(XType::getTypeName)
.map(SourceFileHjarGenerator::getDefaultValueCodeBlock)
.collect(toImmutableList()))));
}

/**
* Returns a {@link CodeBlock} containing the default value for the given {@code typeName}.
*
* <p>See https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html.
*/
private static CodeBlock getDefaultValueCodeBlock(TypeName typeName) {
if (typeName.isPrimitive()) {
if (typeName.equals(TypeName.BOOLEAN)) {
return CodeBlock.of("false");
} else if (typeName.equals(TypeName.CHAR)) {
return CodeBlock.of("'\u0000'");
} else if (typeName.equals(TypeName.BYTE)) {
return CodeBlock.of("0");
} else if (typeName.equals(TypeName.SHORT)) {
return CodeBlock.of("0");
} else if (typeName.equals(TypeName.INT)) {
return CodeBlock.of("0");
} else if (typeName.equals(TypeName.LONG)) {
return CodeBlock.of("0L");
} else if (typeName.equals(TypeName.FLOAT)) {
return CodeBlock.of("0.0f");
} else if (typeName.equals(TypeName.DOUBLE)) {
return CodeBlock.of("0.0d");
} else {
throw new AssertionError("Unexpected type: " + typeName);
}
}
return CodeBlock.of("null");
}

private FieldSpec skeletonField(FieldSpec completeField) {
return FieldSpec.builder(
completeField.type,
completeField.name,
completeField.modifiers.toArray(new Modifier[0]))
.addAnnotations(completeField.annotations)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@

package dagger.internal.codegen.componentgenerator;

import androidx.room.compiler.processing.XProcessingEnv;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.internal.codegen.base.SourceFileGenerator;
import dagger.internal.codegen.base.SourceFileHjarGenerator;
import dagger.internal.codegen.binding.BindingGraph;
import dagger.internal.codegen.binding.ComponentDescriptor;

Expand All @@ -29,14 +32,23 @@ public interface ComponentGeneratorModule {
@Binds
abstract SourceFileGenerator<BindingGraph> componentGenerator(ComponentGenerator generator);

// The HjarSourceFileGenerator wrapper first generates the entire TypeSpec before stripping out
// The SourceFileHjarGenerator wrapper first generates the entire TypeSpec before stripping out
// things that aren't needed for the hjar. However, this can be really expensive for the component
// because it is usually the most expensive file to generate, and most of its content is not
// needed in the hjar. Thus, instead of wrapping the ComponentGenerator in HjarSourceFileGenerator
// we provide a completely separate processing step, ComponentHjarProcessingStep, and generator,
// ComponentHjarGenerator, for when generating hjars for components, which can avoid generating
// the parts of the component that would have been stripped out by the HjarSourceFileGenerator.
@Binds
abstract SourceFileGenerator<ComponentDescriptor> componentHjarGenerator(
ComponentHjarGenerator hjarGenerator);
// needed in the hjar. Thus, we provide a completely separate processing step,
// ComponentHjarProcessingStep and ComponentHjarGenerator, for when generating hjars for
// components, which can avoid generating the parts of the component that would have been stripped
// out by the HjarSourceFileGenerator anyway. Note that we still wrap ComponentHjarGenerator in
// SourceFileHjarGenerator because it adds in constructor and method bodies that are needed for
// Javac to compile correctly, e.g. super(...) calls in the constructor and return statements in
// methods.
@Provides
static SourceFileGenerator<ComponentDescriptor> componentHjarGenerator(
XProcessingEnv processingEnv,
ComponentHjarGenerator hjarGenerator) {
// Note: technically the ComponentHjarGenerator is already in hjar form, but the
// SourceFileHjarGenerator wrapper adds in proper method bodies, e.g. constructors that require
// super() calls or methods that require return statements.
return SourceFileHjarGenerator.wrap(hjarGenerator, processingEnv);
}
}
Loading

0 comments on commit 668269d

Please sign in to comment.