Skip to content

Commit

Permalink
Inline requests for @BINDS bindings
Browse files Browse the repository at this point in the history
This is a rollforward of e2bff35 and fixes an issue where Expressions for inline Foo_Factory.create() were not properly typed internally.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=171578370
  • Loading branch information
ronshapiro committed Oct 9, 2017
1 parent 6d1e6ac commit e8d7cd4
Show file tree
Hide file tree
Showing 15 changed files with 1,502 additions and 86 deletions.
4 changes: 4 additions & 0 deletions java/dagger/internal/codegen/BindingExpression.java
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ private BindingExpression create(
return new OptionalBindingExpression(
provisionBinding, bindingExpression, componentBindingExpressions, types);

case SYNTHETIC_DELEGATE_BINDING:
return DelegateBindingExpression.create(
graph, bindingExpression, componentBindingExpressions, types, elements);

case BUILDER_BINDING:
return new BoundInstanceBindingExpression(
bindingExpression,
Expand Down
86 changes: 7 additions & 79 deletions java/dagger/internal/codegen/BindsMethodValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,15 @@
import static dagger.internal.codegen.ErrorMessages.BINDS_ELEMENTS_INTO_SET_METHOD_RETURN_SET;
import static dagger.internal.codegen.ErrorMessages.BINDS_METHOD_ONE_ASSIGNABLE_PARAMETER;

import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import dagger.Binds;
import dagger.Module;
import dagger.producers.ProducerModule;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

Expand All @@ -47,7 +40,7 @@
*/
final class BindsMethodValidator extends BindingMethodValidator {
private final Types types;
private final Elements elements;
private final BindsTypeChecker bindsTypeChecker;

BindsMethodValidator(Elements elements, Types types) {
super(
Expand All @@ -59,7 +52,7 @@ final class BindsMethodValidator extends BindingMethodValidator {
RUNTIME_EXCEPTION,
ALLOWS_MULTIBINDINGS);
this.types = types;
this.elements = elements;
this.bindsTypeChecker = new BindsTypeChecker(types, elements);
}

@Override
Expand All @@ -76,83 +69,18 @@ private void checkParameters(ValidationReport.Builder<ExecutableElement> builder
TypeMirror leftHandSide = boxIfNecessary(method.getReturnType());
TypeMirror rightHandSide = parameter.asType();
ContributionType contributionType = ContributionType.fromBindingMethod(method);
switch (contributionType) {
case SET_VALUES:
if (!SetType.isSet(leftHandSide)) {
builder.addError(BINDS_ELEMENTS_INTO_SET_METHOD_RETURN_SET);
} else {
validateTypesAreAssignable(
builder,
rightHandSide,
methodParameterType(MoreTypes.asDeclared(leftHandSide), "addAll"));
}
break;
case SET:
DeclaredType parameterizedSetType = types.getDeclaredType(setElement(), leftHandSide);
validateTypesAreAssignable(
builder,
rightHandSide,
methodParameterType(parameterizedSetType, "add"));
break;
case MAP:
DeclaredType parameterizedMapType =
types.getDeclaredType(mapElement(), unboundedWildcard(), leftHandSide);
validateTypesAreAssignable(
builder,
rightHandSide,
methodParameterTypes(parameterizedMapType, "put").get(1));
break;
case UNIQUE:
validateTypesAreAssignable(builder, rightHandSide, leftHandSide);
break;
default:
throw new AssertionError(
String.format(
"Unknown contribution type (%s) for method: %s", contributionType, method));
if (contributionType.equals(ContributionType.SET_VALUES) && !SetType.isSet(leftHandSide)) {
builder.addError(BINDS_ELEMENTS_INTO_SET_METHOD_RETURN_SET);
}
} else {
builder.addError(BINDS_METHOD_ONE_ASSIGNABLE_PARAMETER);
}
}

private ImmutableList<TypeMirror> methodParameterTypes(DeclaredType type, String methodName) {
ImmutableList.Builder<ExecutableElement> methodsForName = ImmutableList.builder();
for (ExecutableElement method :
ElementFilter.methodsIn(MoreElements.asType(type.asElement()).getEnclosedElements())) {
if (method.getSimpleName().contentEquals(methodName)) {
methodsForName.add(method);
if (!bindsTypeChecker.isAssignable(rightHandSide, leftHandSide, contributionType)) {
builder.addError(BINDS_METHOD_ONE_ASSIGNABLE_PARAMETER);
}
}
ExecutableElement method = getOnlyElement(methodsForName.build());
return ImmutableList.<TypeMirror>copyOf(
MoreTypes.asExecutable(types.asMemberOf(type, method)).getParameterTypes());
}

private TypeMirror methodParameterType(DeclaredType type, String methodName) {
return getOnlyElement(methodParameterTypes(type, methodName));
}

private void validateTypesAreAssignable(
ValidationReport.Builder<ExecutableElement> builder,
TypeMirror rightHandSide,
TypeMirror leftHandSide) {
if (!types.isAssignable(rightHandSide, leftHandSide)) {
} else {
builder.addError(BINDS_METHOD_ONE_ASSIGNABLE_PARAMETER);
}
}

private TypeElement setElement() {
return elements.getTypeElement(Set.class.getName());
}

private TypeElement mapElement() {
return elements.getTypeElement(Map.class.getName());
}

private TypeMirror unboundedWildcard() {
return types.getWildcardType(null, null);
}

private TypeMirror boxIfNecessary(TypeMirror maybePrimitive) {
if (maybePrimitive.getKind().isPrimitive()) {
return types.boxedClass(MoreTypes.asPrimitiveType(maybePrimitive)).asType();
Expand Down
105 changes: 105 additions & 0 deletions java/dagger/internal/codegen/BindsTypeChecker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* 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;

import static com.google.common.collect.Iterables.getOnlyElement;
import static javax.lang.model.util.ElementFilter.methodsIn;

import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.collect.ImmutableList;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

/**
* Checks the assignability of one type to another, given a {@link ContributionType} context. This
* is used by {@link BindsMethodValidator} to validate that the right-hand-side of a {@link
* dagger.Binds} method is valid, as well as in {@link DelegateBindingExpression} when the
* right-hand-side in generated code might be an erased type due to accessibility.
*/
final class BindsTypeChecker {
private final Types types;
private final Elements elements;

BindsTypeChecker(Types types, Elements elements) {
this.types = types;
this.elements = elements;
}

/**
* Checks the assignability of {@code rightHandSide} to {@code leftHandSide} given a {@link
* ContributionType} context.
*/
boolean isAssignable(
TypeMirror rightHandSide, TypeMirror leftHandSide, ContributionType contributionType) {
return types.isAssignable(rightHandSide, desiredAssignableType(leftHandSide, contributionType));
}

private TypeMirror desiredAssignableType(
TypeMirror leftHandSide, ContributionType contributionType) {
switch (contributionType) {
case UNIQUE:
return leftHandSide;
case SET:
DeclaredType parameterizedSetType = types.getDeclaredType(setElement(), leftHandSide);
return methodParameterType(parameterizedSetType, "add");
case SET_VALUES:
return methodParameterType(MoreTypes.asDeclared(leftHandSide), "addAll");
case MAP:
DeclaredType parameterizedMapType =
types.getDeclaredType(mapElement(), unboundedWildcard(), leftHandSide);
return methodParameterTypes(parameterizedMapType, "put").get(1);
default:
throw new AssertionError("Unknown contribution type: " + contributionType);
}
}

private ImmutableList<TypeMirror> methodParameterTypes(DeclaredType type, String methodName) {
ImmutableList.Builder<ExecutableElement> methodsForName = ImmutableList.builder();
for (ExecutableElement method :
methodsIn(MoreElements.asType(type.asElement()).getEnclosedElements())) {
if (method.getSimpleName().contentEquals(methodName)) {
methodsForName.add(method);
}
}
ExecutableElement method = getOnlyElement(methodsForName.build());
return ImmutableList.copyOf(
MoreTypes.asExecutable(types.asMemberOf(type, method)).getParameterTypes());
}

private TypeMirror methodParameterType(DeclaredType type, String methodName) {
return getOnlyElement(methodParameterTypes(type, methodName));
}

private TypeElement setElement() {
return elements.getTypeElement(Set.class.getName());
}

private TypeElement mapElement() {
return elements.getTypeElement(Map.class.getName());
}

private TypeMirror unboundedWildcard() {
return types.getWildcardType(null, null);
}
}
137 changes: 137 additions & 0 deletions java/dagger/internal/codegen/DelegateBindingExpression.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* 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;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.Accessibility.isTypeAccessibleFrom;
import static dagger.internal.codegen.Scope.reusableScope;

import com.squareup.javapoet.ClassName;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;

/** A {@link BindingExpression} for {@code @Binds} methods. */
final class DelegateBindingExpression extends BindingExpression {
private final ContributionBinding binding;
private final ComponentBindingExpressions componentBindingExpressions;
private final DaggerTypes types;
private final Elements elements;
private final BindsTypeChecker bindsTypeChecker;

private DelegateBindingExpression(
ResolvedBindings resolvedBindings,
ComponentBindingExpressions componentBindingExpressions,
DaggerTypes types,
Elements elements) {
super(resolvedBindings);
this.binding = checkNotNull(resolvedBindings.contributionBinding());
this.componentBindingExpressions = checkNotNull(componentBindingExpressions);
this.types = checkNotNull(types);
this.elements = checkNotNull(elements);
this.bindsTypeChecker = new BindsTypeChecker(types, elements);
}

static BindingExpression create(
BindingGraph graph,
BindingExpression bindingExpression,
ComponentBindingExpressions componentBindingExpressions,
DaggerTypes types,
Elements elements) {
ResolvedBindings resolvedBindings = bindingExpression.resolvedBindings();
ContributionBinding binding = resolvedBindings.contributionBinding();
Binding delegateBinding =
graph
.resolvedBindings()
.get(getOnlyElement(binding.dependencies()).bindingKey())
.binding();
ScopeKind bindsScope = ScopeKind.get(binding, graph, elements);
ScopeKind delegateScope = ScopeKind.get(delegateBinding, graph, elements);
if (bindsScope.isSimilarOrWeakerScopeThan(delegateScope)) {
return new DelegateBindingExpression(
resolvedBindings, componentBindingExpressions, types, elements);
}
return bindingExpression;
}

@Override
Expression getDependencyExpression(
DependencyRequest.Kind requestKind, ClassName requestingClass) {
Expression delegateExpression =
componentBindingExpressions.getDependencyExpression(
getOnlyElement(binding.dependencies()).bindingKey(), requestKind, requestingClass);

TypeMirror contributedType = binding.contributedType();
switch (requestKind) {
case INSTANCE:
return instanceRequiresCast(delegateExpression, requestingClass)
? delegateExpression.castTo(contributedType)
: delegateExpression;
default:
return castToRawTypeIfNecessary(
delegateExpression, requestKind.type(contributedType, types));
}
}

private boolean instanceRequiresCast(Expression delegateExpression, ClassName requestingClass) {
// delegateExpression.type() could be Object if expression is satisfied with a raw
// Provider's get() method.
return !bindsTypeChecker.isAssignable(
delegateExpression.type(), binding.contributedType(), binding.contributionType())
&& isTypeAccessibleFrom(binding.contributedType(), requestingClass.packageName());
}

/**
* If {@code delegateExpression} can be assigned to {@code desiredType} safely, then {@code
* delegateExpression} is returned unchanged. If the {@code delegateExpression} is already a raw
* type, returns {@code delegateExpression} as well, as casting would have no effect. Otherwise,
* returns a {@link Expression#castTo(TypeMirror) casted} version of {@code delegateExpression}
* to the raw type of {@code desiredType}.
*/
// TODO(ronshapiro): this probably can be generalized for usage in InjectionMethods
private Expression castToRawTypeIfNecessary(
Expression delegateExpression, TypeMirror desiredType) {
if (types.isAssignable(delegateExpression.type(), desiredType)) {
return delegateExpression;
}
return delegateExpression.castTo(types.erasure(desiredType));
}

private enum ScopeKind {
UNSCOPED,
RELEASABLE,
SINGLE_CHECK,
DOUBLE_CHECK,
;

static ScopeKind get(Binding binding, BindingGraph graph, Elements elements) {
if (!binding.scope().isPresent()) {
return UNSCOPED;
}

Scope scope = binding.scope().get();
if (graph.scopesRequiringReleasableReferenceManagers().contains(scope)) {
return RELEASABLE;
}
return scope.equals(reusableScope(elements)) ? SINGLE_CHECK : DOUBLE_CHECK;
}

boolean isSimilarOrWeakerScopeThan(ScopeKind other) {
return ordinal() <= other.ordinal();
}
}
}
Loading

0 comments on commit e8d7cd4

Please sign in to comment.