Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,17 @@ public int keysPerComponentShard(XTypeElement component) {
return 3500;
}

/**
* Each switch size is fixed at 100 cases each and put in its own method. This is to limit the
* size of the methods so that we don't reach the "huge" method size limit for Android that will
* prevent it from being AOT compiled in some versions of Android (b/77652521). This generally
* starts to happen around 1500 cases, but we are choosing 100 to be safe.
*/
// TODO(bcorso): Include a proguard_spec in the Dagger library to prevent inlining these methods?
public int casesPerSwitchingProviderSwitch() {
return 100;
}

/**
* This option enables a fix to an issue where Dagger previously would erroneously allow
* multibinding contributions in a component to have dependencies on child components. This will
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions {
// EnumOption<T> doesn't support integer inputs so just doing this as a 1-off for now.
private static final String KEYS_PER_COMPONENT_SHARD = "dagger.keysPerComponentShard";
private static final String CASES_PER_SWITCHING_PROVIDER_SWITCH =
"dagger.casesPerSwitchingProviderSwitch";

private final XProcessingEnv processingEnv;

Expand Down Expand Up @@ -233,6 +235,14 @@ public int keysPerComponentShard(XTypeElement component) {
return super.keysPerComponentShard(component);
}

@Override
public int casesPerSwitchingProviderSwitch() {
if (options.containsKey(CASES_PER_SWITCHING_PROVIDER_SWITCH)) {
return Integer.parseInt(options.get(CASES_PER_SWITCHING_PROVIDER_SWITCH));
}
return super.casesPerSwitchingProviderSwitch();
}

private boolean isEnabled(KeyOnlyOption keyOnlyOption) {
return options.containsKey(keyOnlyOption.toString());
}
Expand Down Expand Up @@ -467,6 +477,7 @@ public static ImmutableSet<String> supportedOptions() {
.flatMap(CommandLineOption::allNames)
.collect(toImmutableSet()))
.add(KEYS_PER_COMPONENT_SHARD)
.add(CASES_PER_SWITCHING_PROVIDER_SWITCH)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,9 @@
* that can provide instances for all types by switching on an id.
*/
final class SwitchingProviders {
/**
* Each switch size is fixed at 100 cases each and put in its own method. This is to limit the
* size of the methods so that we don't reach the "huge" method size limit for Android that will
* prevent it from being AOT compiled in some versions of Android (b/77652521). This generally
* starts to happen around 1500 cases, but we are choosing 100 to be safe.
*/
// TODO(bcorso): Include a proguard_spec in the Dagger library to prevent inlining these methods?
// TODO(ronshapiro): Consider making this configurable via a flag.
private static final int MAX_CASES_PER_SWITCH = 100;

private static final long MAX_CASES_PER_CLASS = MAX_CASES_PER_SWITCH * MAX_CASES_PER_SWITCH;

/**
* Maps a {@link Key} to an instance of a {@link SwitchingProviderBuilder}. Each group of {@code
* MAX_CASES_PER_CLASS} keys will share the same instance.
* maxCasesPerClass} keys will share the same instance.
*/
private final Map<Key, SwitchingProviderBuilder> switchingProviderBuilders =
new LinkedHashMap<>();
Expand All @@ -89,6 +77,8 @@ final class SwitchingProviders {
private final CompilerOptions compilerOptions;
private final XProcessingEnv processingEnv;
private final XTypeName typeVariable;
private final int maxCasesPerSwitch;
private final long maxCasesPerClass;

SwitchingProviders(
ShardImplementation shardImplementation,
Expand All @@ -97,6 +87,8 @@ final class SwitchingProviders {
this.shardImplementation = checkNotNull(shardImplementation);
this.compilerOptions = checkNotNull(compilerOptions);
this.processingEnv = checkNotNull(processingEnv);
this.maxCasesPerSwitch = compilerOptions.casesPerSwitchingProviderSwitch();
this.maxCasesPerClass = (long) maxCasesPerSwitch * maxCasesPerSwitch;
this.typeVariable =
XTypeName.getTypeVariableName(
"T",
Expand All @@ -121,7 +113,7 @@ public XCodeBlock creationExpression() {
}

private SwitchingProviderBuilder getSwitchingProviderBuilder() {
if (switchingProviderBuilders.size() % MAX_CASES_PER_CLASS == 0) {
if (switchingProviderBuilders.size() % maxCasesPerClass == 0) {
String name = shardImplementation.getUniqueClassName("SwitchingProvider");
SwitchingProviderBuilder switchingProviderBuilder =
new SwitchingProviderBuilder(shardImplementation.name().nestedClass(name));
Expand Down Expand Up @@ -217,7 +209,7 @@ private XTypeSpec build() {
private ImmutableList<XFunSpec> getMethods() {
ImmutableList<XCodeBlock> switchCodeBlockPartitions = switchCodeBlockPartitions();
if (switchCodeBlockPartitions.size() == 1) {
// The case amount does not exceed MAX_CASES_PER_SWITCH, so no need for extra get methods.
// The case amount does not exceed maxCasesPerSwitch, so no need for extra get methods.
return ImmutableList.of(
methodBuilder("get")
.isOverride(true)
Expand All @@ -234,7 +226,7 @@ private ImmutableList<XFunSpec> getMethods() {
.isOverride(true)
.addModifiers(PUBLIC)
.returns(typeVariable)
.beginControlFlow("switch (id / %L)", MAX_CASES_PER_SWITCH);
.beginControlFlow("switch (id / %L)", maxCasesPerSwitch);

ImmutableList.Builder<XFunSpec> getMethods = ImmutableList.builder();
for (int i = 0; i < switchCodeBlockPartitions.size(); i++) {
Expand All @@ -258,7 +250,7 @@ private ImmutableList<XFunSpec> getMethods() {
}

private ImmutableList<XCodeBlock> switchCodeBlockPartitions() {
return Lists.partition(ImmutableList.copyOf(switchCases.values()), MAX_CASES_PER_SWITCH)
return Lists.partition(ImmutableList.copyOf(switchCases.values()), maxCasesPerSwitch)
.stream()
.map(
partitionCases ->
Expand Down
49 changes: 49 additions & 0 deletions javatests/dagger/internal/codegen/SwitchingProviderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import androidx.room3.compiler.processing.util.Source;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import dagger.testing.compile.CompilerTests;
import dagger.testing.golden.GoldenFileRule;
import org.junit.Rule;
Expand Down Expand Up @@ -304,4 +305,52 @@ public void optionals() throws Exception {
subject.generatedSource(goldenFileRule.goldenSource("test/DaggerTestComponent"));
});
}

@Test
public void customCasesPerSwitchingProviderSwitch() throws Exception {
ImmutableList.Builder<Source> sources = ImmutableList.builder();
StringBuilder entryPoints = new StringBuilder();
for (int i = 0; i <= 10; i++) {
String bindingName = "Binding" + i;
sources.add(
CompilerTests.javaSource(
"test." + bindingName,
"package test;",
"",
"import javax.inject.Inject;",
"",
"final class " + bindingName + " {",
" @Inject",
" " + bindingName + "() {}",
"}"));
entryPoints.append(String.format(" Provider<%1$s> get%1$sProvider();\n", bindingName));
}

sources.add(
CompilerTests.javaSource(
"test.TestComponent",
"package test;",
"",
"import dagger.Component;",
"import javax.inject.Provider;",
"",
"@Component",
"interface TestComponent {",
entryPoints.toString(),
"}"));

ImmutableMap<String, String> options =
ImmutableMap.<String, String>builder()
.putAll(compilerMode.processorOptions())
.put("dagger.casesPerSwitchingProviderSwitch", "5")
.buildOrThrow();

CompilerTests.daggerCompiler(sources.build())
.withProcessingOptions(options)
.compile(
subject -> {
subject.hasErrorCount(0);
subject.hasWarningCount(0);
});
}
}
Loading