Skip to content

Commit

Permalink
Include type parameters of parent types in generated factory classes.
Browse files Browse the repository at this point in the history
Suppose you have the following types:
```
interface Foo<M extends Bar> {...}

interface FooFactory<M extends Bar> {
  Foo<M> create();
}

@autofactory(implementing = FooFactory.class)
class FooImpl<M extends Bar> implements Foo<M> {...}
```

Then the generated `FooImplFactory` class should look like this:

```
class FooImplFactory<M extends Bar> implements FooFactory<M> {...}
```

But before this change that was only the case if there was a `@Provided M` constructor parameter. Now we examine whether the requested supertypes of the generated class have type parameters, and if so we copy those onto the generated class.

This may not be 100% accurate but it should work for at least the commonest cases.

RELNOTES=When a requested supertype of the generated factory class has a type parameter, the generated class now always has the same parameter.
PiperOrigin-RevId: 400259157
  • Loading branch information
eamonnmcmanus authored and Google Java Core Libraries committed Oct 1, 2021
1 parent 781de86 commit 206b673
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import com.google.auto.common.AnnotationMirrors;
import com.google.auto.common.AnnotationValues;
import com.google.auto.common.MoreTypes;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
Expand Down Expand Up @@ -59,6 +60,7 @@
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
Expand Down Expand Up @@ -338,9 +340,28 @@ private static ImmutableSet<TypeVariableName> getFactoryTypeVariables(
for (ProviderField provider : descriptor.providers().values()) {
typeVariables.addAll(getReferencedTypeParameterNames(provider.key().type().get()));
}
// If a parent type has a type parameter, like FooFactory<T>, then the generated factory needs
// to have the same parameter, like FooImplFactory<T> extends FooFactory<T>. This is a little
// approximate, at least in the case where there is more than one parent type that has a type
// parameter. But that should be pretty rare, so let's keep it simple for now.
typeVariables.addAll(typeVariablesFrom(descriptor.extendingType()));
for (TypeMirror implementing : descriptor.implementingTypes()) {
typeVariables.addAll(typeVariablesFrom(implementing));
}
return typeVariables.build();
}

private static List<TypeVariableName> typeVariablesFrom(TypeMirror type) {
if (type.getKind().equals(TypeKind.DECLARED)) {
DeclaredType declaredType = MoreTypes.asDeclared(type);
return declaredType.getTypeArguments().stream()
.filter(t -> t.getKind().equals(TypeKind.TYPEVAR))
.map(t -> TypeVariableName.get(MoreTypes.asTypeVariable(t)))
.collect(toList());
}
return ImmutableList.of();
}

private static ImmutableSet<TypeVariableName> getMethodTypeVariables(
FactoryMethodDescriptor methodDescriptor,
ImmutableSet<TypeVariableName> factoryTypeVariables) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,22 @@ public void defaultPackage() {
.hasSourceEquivalentTo(loadExpectedFile("expected/DefaultPackageFactory.java"));
}

@Test
public void generics() {
JavaFileObject file = JavaFileObjects.forResource("good/Generics.java");
Compilation compilation = javac.compile(file);
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation)
.generatedSourceFile("tests.Generics_FooImplFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/Generics_FooImplFactory.java"));
assertThat(compilation)
.generatedSourceFile("tests.Generics_ExplicitFooImplFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/Generics_ExplicitFooImplFactory.java"));
assertThat(compilation)
.generatedSourceFile("tests.Generics_FooImplWithClassFactory")
.hasSourceEquivalentTo(loadExpectedFile("expected/Generics_FooImplWithClassFactory.java"));
}

private JavaFileObject loadExpectedFile(String resourceName) {
if (isJavaxAnnotationProcessingGeneratedAvailable()) {
return JavaFileObjects.forResource(resourceName);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2021 Google LLC
*
* 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 tests;

import javax.annotation.processing.Generated;
import javax.inject.Inject;
import javax.inject.Provider;

@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
comments = "https://github.com/google/auto/tree/master/factory"
)
final class Generics_ExplicitFooImplFactory<M extends Generics.Bar>
implements Generics.FooFactory<M> {
private final Provider<M> unusedProvider;

@Inject
Generics_ExplicitFooImplFactory(Provider<M> unusedProvider) {
this.unusedProvider = checkNotNull(unusedProvider, 1);
}

@Override
public Generics.ExplicitFooImpl<M> create() {
return new Generics.ExplicitFooImpl<M>(checkNotNull(unusedProvider.get(), 1));
}

private static <T> T checkNotNull(T reference, int argumentIndex) {
if (reference == null) {
throw new NullPointerException(
"@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+ argumentIndex);
}
return reference;
}
}
34 changes: 34 additions & 0 deletions factory/src/test/resources/expected/Generics_FooImplFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2021 Google LLC
*
* 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 tests;

import javax.annotation.processing.Generated;
import javax.inject.Inject;

@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
comments = "https://github.com/google/auto/tree/master/factory"
)
final class Generics_FooImplFactory<M extends Generics.Bar> implements Generics.FooFactory<M> {
@Inject
Generics_FooImplFactory() {
}

@Override
public Generics.FooImpl<M> create() {
return new Generics.FooImpl<M>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2021 Google LLC
*
* 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 tests;

import javax.annotation.processing.Generated;
import javax.inject.Inject;

@Generated(
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
comments = "https://github.com/google/auto/tree/master/factory"
)
final class Generics_FooImplWithClassFactory<M extends Generics.Bar> extends Generics.FooFactoryClass<M> {
@Inject
Generics_FooImplWithClassFactory() {
}

@Override
public Generics.FooImplWithClass<M> create() {
return new Generics.FooImplWithClass<M>();
}
}
50 changes: 50 additions & 0 deletions factory/src/test/resources/good/Generics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2021 Google LLC
*
* 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 tests;

import com.google.auto.factory.AutoFactory;
import com.google.auto.factory.Provided;

class Generics {
interface Bar {}

interface Foo<M extends Bar> {}

interface FooFactory<M extends Bar> {
Foo<M> create();
}

// The generated FooImplFactory should also have an <M extends Bar> type parameter, so we can
// have FooImplFactory<M extends Bar> implements FooFactory<M>.
@AutoFactory(implementing = FooFactory.class)
static final class FooImpl<M extends Bar> implements Foo<M> {
FooImpl() {}
}

// The generated ExplicitFooImplFactory should have an <M extends Bar> type parameter, which
// serves both for FooFactory<M> and for Provider<M> in the constructor.
@AutoFactory(implementing = FooFactory.class)
static final class ExplicitFooImpl<M extends Bar> implements Foo<M> {
ExplicitFooImpl(@Provided M unused) {}
}

abstract static class FooFactoryClass<M extends Bar> {
abstract Foo<M> create();
}

@AutoFactory(extending = FooFactoryClass.class)
static final class FooImplWithClass<M extends Bar> implements Foo<M> {}
}

0 comments on commit 206b673

Please sign in to comment.