diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:01:31 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:01:31 +0000 |
commit | 176892ed88d3814500a0c610325b13af8a9f4b15 (patch) | |
tree | c527ec0940e792db8cf6d839c2c6525d2e3a325a | |
parent | 859e5f9beab0d02c324a1f6bdf869d281bdb0673 (diff) | |
parent | cd3b5ac62e070c8a3bccc1a0495eaa742438c252 (diff) | |
download | dagger2-android14-mainline-permission-release.tar.gz |
Snap for 10453563 from cd3b5ac62e070c8a3bccc1a0495eaa742438c252 to mainline-permission-releaseaml_per_341410020aml_per_341311000aml_per_341110020aml_per_341110010aml_per_341011100aml_per_341011020aml_per_340916010android14-mainline-permission-release
Change-Id: I2717e250d4383323ce44e8584258ecce15e65adf
861 files changed, 38802 insertions, 18413 deletions
diff --git a/.github/actions/artifact-android-emulator-tests/action.yml b/.github/actions/artifact-android-emulator-tests/action.yml new file mode 100644 index 000000000..a57fd08a4 --- /dev/null +++ b/.github/actions/artifact-android-emulator-tests/action.yml @@ -0,0 +1,39 @@ +name: 'Artifact Android emulator tests' +description: 'Runs Android emulator tests on the Dagger LOCAL-SNAPSHOT artifacts.' + +inputs: + api-level: + description: 'The version of Android emulator API to test with.' + required: true + +runs: + using: "composite" + steps: + - name: 'Check out repository' + uses: actions/checkout@v2 + - name: 'Cache Gradle files' + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: 'Download local snapshot for tests' + uses: actions/download-artifact@v2 + with: + name: local-snapshot + path: ~/.m2/repository/com/google/dagger + - name: 'Gradle Android emulator tests (API ${{ inputs.api-level }})' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ inputs.api-level }} + target: google_apis + script: ./util/run-local-emulator-tests.sh + - name: 'Upload test reports (API ${{ inputs.api-level }})' + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: androidTests-report-api-${{ inputs.api-level }} + path: ${{ github.workspace }}/**/build/reports/androidTests/connected/* diff --git a/.github/actions/artifact-android-local-tests/action.yml b/.github/actions/artifact-android-local-tests/action.yml new file mode 100644 index 000000000..090bbb5bd --- /dev/null +++ b/.github/actions/artifact-android-local-tests/action.yml @@ -0,0 +1,36 @@ +name: 'Artifact Android local tests' +description: 'Runs Android local tests on the Dagger LOCAL-SNAPSHOT artifacts.' + +inputs: + agp: + description: 'The version of AGP to test with.' + required: true + +runs: + using: "composite" + steps: + - name: 'Check out repository' + uses: actions/checkout@v2 + - name: 'Cache Gradle files' + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: 'Download local snapshot for tests' + uses: actions/download-artifact@v2 + with: + name: local-snapshot + path: ~/.m2/repository/com/google/dagger + - name: 'Gradle Android local tests (AGP ${{ inputs.agp }})' + run: ./util/run-local-gradle-android-tests.sh "${{ inputs.agp }}" + shell: bash + - name: 'Upload test reports (AGP ${{ inputs.agp }})' + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: tests-reports-agp-${{ inputs.agp }} + path: ${{ github.workspace }}/**/build/reports/tests/* diff --git a/.github/actions/artifact-java-local-tests/action.yml b/.github/actions/artifact-java-local-tests/action.yml new file mode 100644 index 000000000..eb31897cd --- /dev/null +++ b/.github/actions/artifact-java-local-tests/action.yml @@ -0,0 +1,25 @@ +name: 'Artifact Java local tests' +description: 'Runs java local tests on the Dagger LOCAL-SNAPSHOT artifacts.' + +runs: + using: "composite" + steps: + - name: 'Check out repository' + uses: actions/checkout@v2 + - name: 'Cache Gradle files' + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: 'Download local snapshot for tests' + uses: actions/download-artifact@v2 + with: + name: local-snapshot + path: ~/.m2/repository/com/google/dagger + - name: 'Gradle Java local tests' + run: ./util/run-local-gradle-tests.sh + shell: bash diff --git a/.github/actions/bazel-build/action.yml b/.github/actions/bazel-build/action.yml new file mode 100644 index 000000000..bc875c73e --- /dev/null +++ b/.github/actions/bazel-build/action.yml @@ -0,0 +1,39 @@ +name: 'Bazel build' +description: 'Builds artifacts and creates the Dagger local snapshots.' + +runs: + using: "composite" + steps: + - name: 'Install Java ${{ env.USE_JAVA_VERSION }}' + uses: actions/setup-java@v2 + with: + distribution: '${{ env.USE_JAVA_DISTRIBUTION }}' + java-version: '${{ env.USE_JAVA_VERSION }}' + - name: 'Check out repository' + uses: actions/checkout@v2 + - name: 'Cache Bazel files' + uses: actions/cache@v2 + with: + path: ~/.cache/bazel + key: ${{ runner.os }}-bazel-build-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-bazel-build- + - name: 'Java build' + run: bazel build //java/... + shell: bash + - name: 'Install local snapshot' + run: ./util/install-local-snapshot.sh + shell: bash + - name: 'Upload local snapshot for tests' + uses: actions/upload-artifact@v2 + with: + name: local-snapshot + path: ~/.m2/repository/com/google/dagger + - name: 'Clean bazel cache' + # According to the documentation, we should be able to exclude these via + # the actions/cache path, e.g. "!~/.cache/bazel/*/*/external/" but that + # doesn't seem to work. + run: | + rm -rf $(bazel info repository_cache) + rm -rf ~/.cache/bazel/*/*/external/ + shell: bash diff --git a/.github/actions/bazel-test/action.yml b/.github/actions/bazel-test/action.yml new file mode 100644 index 000000000..0fc5a1c3e --- /dev/null +++ b/.github/actions/bazel-test/action.yml @@ -0,0 +1,55 @@ +name: 'Bazel test' +description: 'Runs Bazel tests.' + +runs: + using: "composite" + steps: + - name: 'Install Java ${{ env.USE_JAVA_VERSION }}' + uses: actions/setup-java@v2 + with: + distribution: '${{ env.USE_JAVA_DISTRIBUTION }}' + java-version: '${{ env.USE_JAVA_VERSION }}' + - name: 'Check out repository' + uses: actions/checkout@v2 + - name: 'Cache local Maven repository' + uses: actions/cache@v2 + with: + path: | + ~/.m2/repository + !~/.m2/repository/com/google/dagger + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: 'Cache Bazel files' + uses: actions/cache@v2 + with: + path: ~/.cache/bazel + # Note: we could use the same key as bazel-build, but we separate them + # so that bazel-build's cache is smaller (~200Mb vs ~900Mb) and faster + # to load than this cache since it's the bottleneck of all other steps + key: ${{ runner.os }}-bazel-test-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-bazel-test- + - name: 'Cache Gradle files' + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: 'Run Bazel tests' + run: bazel test --test_output=errors //... + shell: bash + - name: 'Run Bazel examples' + run: cd examples/bazel; bazel test --test_output=errors //... + shell: bash + - name: 'Clean bazel cache' + # According to the documentation, we should be able to exclude these via + # the actions/cache path, e.g. "!~/.cache/bazel/*/*/external/" but that + # doesn't seem to work. + run: | + rm -rf $(bazel info repository_cache) + rm -rf ~/.cache/bazel/*/*/external/ + shell: bash diff --git a/.github/actions/build-gradle-plugin/action.yml b/.github/actions/build-gradle-plugin/action.yml new file mode 100644 index 000000000..6a388d445 --- /dev/null +++ b/.github/actions/build-gradle-plugin/action.yml @@ -0,0 +1,56 @@ +name: 'Build Hilt Gradle plugin' +description: 'Builds the Hilt Gradle plugin.' + +inputs: + agp: + description: 'The version of AGP to build with.' + required: true + +runs: + using: "composite" + steps: + - name: 'Install Java ${{ env.USE_JAVA_VERSION }}' + uses: actions/setup-java@v2 + with: + distribution: '${{ env.USE_JAVA_DISTRIBUTION }}' + java-version: '${{ env.USE_JAVA_VERSION }}' + - name: 'Check out repository' + uses: actions/checkout@v2 + - name: 'Cache local Maven repository' + uses: actions/cache@v2 + with: + path: | + ~/.m2/repository + !~/.m2/repository/com/google/dagger + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: 'Cache Bazel files' + uses: actions/cache@v2 + with: + path: ~/.cache/bazel + key: ${{ runner.os }}-bazel-build-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-bazel-build- + - name: 'Cache Gradle files' + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: 'Build and install Hilt Gradle plugin local snapshot' + run: ./util/deploy-hilt-gradle-plugin.sh "install:install-file" "LOCAL-SNAPSHOT" + shell: bash + env: + AGP_VERSION: '${{ inputs.agp }}' + - name: 'Clean bazel cache' + # According to the documentation, we should be able to exclude these via + # the actions/cache path, e.g. "!~/.cache/bazel/*/*/external/" but that + # doesn't seem to work. + run: | + rm -rf $(bazel info repository_cache) + rm -rf ~/.cache/bazel/*/*/external/ + shell: bash diff --git a/.github/actions/prechecks/action.yml b/.github/actions/prechecks/action.yml new file mode 100644 index 000000000..1365184ae --- /dev/null +++ b/.github/actions/prechecks/action.yml @@ -0,0 +1,20 @@ +name: 'Performs prechecks before running other actions.' +description: 'Validates that the Dagger version in the config.yml file is the latest version of Dagger released.' + +runs: + using: "composite" + steps: + # Cancel previous runs on the same branch to avoid unnecessary parallel + # runs of the same job. See https://github.com/google/go-github/pull/1821 + - name: Cancel previous + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + - name: 'Check out gh-pages repository' + uses: actions/checkout@v2 + with: + ref: 'refs/heads/gh-pages' + path: gh-pages + - name: 'Validate latest Dagger version' + run: ./gh-pages/.github/scripts/validate-latest-dagger-version.sh gh-pages/_config.yml + shell: bash diff --git a/.github/actions/test-gradle-plugin/action.yml b/.github/actions/test-gradle-plugin/action.yml new file mode 100644 index 000000000..93c6af7f6 --- /dev/null +++ b/.github/actions/test-gradle-plugin/action.yml @@ -0,0 +1,54 @@ +name: 'Test Hilt Gradle plugin' +description: 'Tests the Hilt Gradle plugin.' + +runs: + using: "composite" + steps: + - name: 'Install Java ${{ env.USE_JAVA_VERSION }}' + uses: actions/setup-java@v2 + with: + distribution: '${{ env.USE_JAVA_DISTRIBUTION }}' + java-version: '${{ env.USE_JAVA_VERSION }}' + - name: 'Check out repository' + uses: actions/checkout@v2 + - name: 'Cache local Maven repository' + uses: actions/cache@v2 + with: + path: | + ~/.m2/repository + !~/.m2/repository/com/google/dagger + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: 'Cache Bazel files' + uses: actions/cache@v2 + with: + path: ~/.cache/bazel + key: ${{ runner.os }}-bazel-build-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-bazel-build- + - name: 'Cache Gradle files' + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: 'Download local snapshot for tests' + uses: actions/download-artifact@v2 + with: + name: local-snapshot + path: ~/.m2/repository/com/google/dagger + - name: 'Build and test Hilt Gradle plugin' + run: ./java/dagger/hilt/android/plugin/gradlew -p java/dagger/hilt/android/plugin clean test --continue --no-daemon --stacktrace + shell: bash + - name: 'Clean bazel cache' + # According to the documentation, we should be able to exclude these via + # the actions/cache path, e.g. "!~/.cache/bazel/*/*/external/" but that + # doesn't seem to work. + run: | + rm -rf $(bazel info repository_cache) + rm -rf ~/.cache/bazel/*/*/external/ + shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c818cc98..d5c35d611 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,182 +9,106 @@ on: - master env: + USE_JAVA_DISTRIBUTION: 'zulu' # Our Bazel builds currently rely on JDK 8. USE_JAVA_VERSION: '8' - # Our Bazel builds currently rely on 3.7.1. The version is set via + # Our Bazel builds currently rely on 4.2.1. The version is set via # baselisk by USE_BAZEL_VERSION: https://github.com/bazelbuild/bazelisk. - USE_BAZEL_VERSION: '3.7.1' + USE_BAZEL_VERSION: '4.2.1' jobs: validate-latest-dagger-version: name: 'Validate Dagger version' runs-on: ubuntu-latest steps: - # Cancel previous runs on the same branch to avoid unnecessary parallel - # runs of the same job. See https://github.com/google/go-github/pull/1821 - - name: Cancel previous - uses: styfle/cancel-workflow-action@0.8.0 - with: - access_token: ${{ github.token }} - - name: 'Check out gh-pages repository' - uses: actions/checkout@v2 - with: - ref: 'refs/heads/gh-pages' - path: gh-pages - - name: 'Validate latest Dagger version' - run: ./gh-pages/.github/scripts/validate-latest-dagger-version.sh gh-pages/_config.yml + - uses: actions/checkout@v2 + - uses: ./.github/actions/prechecks + bazel-build: + name: 'Bazel build' + needs: validate-latest-dagger-version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/bazel-build bazel-test: name: 'Bazel tests' needs: validate-latest-dagger-version runs-on: ubuntu-latest steps: - - name: 'Install Java ${{ env.USE_JAVA_VERSION }}' - uses: actions/setup-java@v1 - with: - java-version: '${{ env.USE_JAVA_VERSION }}' - - name: 'Check out repository' - uses: actions/checkout@v2 - - name: 'Cache local Maven repository' - uses: actions/cache@v2 - with: - path: | - ~/.m2/repository - !~/.m2/repository/com/google/dagger - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - name: 'Cache Bazel files' - uses: actions/cache@v2 - with: - path: ~/.cache/bazel - key: ${{ runner.os }}-bazel-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-bazel- - - name: 'Cache Gradle files' - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: 'Run Bazel tests' - run: bazel test --test_output=errors //... --keep_going - shell: bash - - name: 'Install local snapshot' - run: ./util/install-local-snapshot.sh - shell: bash - - name: 'Upload local snapshot for tests' - uses: actions/upload-artifact@v2 - with: - name: local-snapshot - path: ~/.m2/repository/com/google/dagger + - uses: actions/checkout@v2 + - uses: ./.github/actions/bazel-test artifact-java-local-tests: name: 'Artifact Java local tests' - needs: bazel-test + needs: bazel-build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/artifact-java-local-tests + test-gradle-plugin: + name: 'Test Hilt Gradle plugin' + needs: bazel-build runs-on: ubuntu-latest steps: - - name: 'Check out repository' - uses: actions/checkout@v2 - - name: 'Cache Gradle files' - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: 'Download local snapshot for tests' - uses: actions/download-artifact@v2 - with: - name: local-snapshot - path: ~/.m2/repository/com/google/dagger - - name: 'Gradle Java local tests' - run: ./util/run-local-gradle-tests.sh - shell: bash + - uses: actions/checkout@v2 + - uses: ./.github/actions/test-gradle-plugin artifact-android-local-tests: name: 'Artifact Android local tests (AGP ${{ matrix.agp }})' - needs: bazel-test + needs: bazel-build runs-on: ubuntu-latest strategy: matrix: - agp: ['4.1.0', '4.2.0-beta04'] + agp: ['4.1.0', '4.2.0', '7.0.0'] steps: - - name: 'Check out repository' - uses: actions/checkout@v2 - - name: 'Cache Gradle files' - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: 'Download local snapshot for tests' - uses: actions/download-artifact@v2 - with: - name: local-snapshot - path: ~/.m2/repository/com/google/dagger - - name: 'Gradle Android local tests (AGP ${{ matrix.agp }})' - run: ./util/run-local-gradle-android-tests.sh "${{ matrix.agp }}" - shell: bash - - name: 'Upload test reports (AGP ${{ matrix.agp }})' - if: ${{ always() }} - uses: actions/upload-artifact@v2 + - uses: actions/checkout@v2 + - uses: ./.github/actions/artifact-android-local-tests with: - name: tests-reports-agp-${{ matrix.agp }} - path: ${{ github.workspace }}/**/build/reports/tests/* + agp: '${{ matrix.agp }}' artifact-android-emulator-tests: name: 'Artifact Android emulator tests (API 30)' - needs: bazel-test + needs: bazel-build # It's recommended to run emulator tests on macOS # See https://github.com/marketplace/actions/android-emulator-runner runs-on: macos-latest steps: - - name: 'Check out repository' - uses: actions/checkout@v2 - - name: 'Cache Gradle files' - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: 'Download local snapshot for tests' - uses: actions/download-artifact@v2 - with: - name: local-snapshot - path: ~/.m2/repository/com/google/dagger - - name: 'Gradle Android emulator tests (API 30)' - uses: reactivecircus/android-emulator-runner@v2 + - uses: actions/checkout@v2 + - uses: ./.github/actions/artifact-android-emulator-tests timeout-minutes: 25 with: - api-level: 30 - target: google_apis - script: ./util/run-local-emulator-tests.sh - - name: 'Upload test reports (API 30)' - if: ${{ always() }} - uses: actions/upload-artifact@v2 + api-level: '30' + artifact-android-emulator-legacy-api-tests: + name: 'Artifact Android emulator tests (API ${{ matrix.api-level }})' + # We only run this on master push (essentially a postsubmit) since these + # can take a while to run + if: github.event_name == 'push' && github.repository == 'google/dagger' && github.ref == 'refs/heads/master' + needs: bazel-build + # It's recommended to run emulator tests on macOS + # See https://github.com/marketplace/actions/android-emulator-runner + runs-on: macos-latest + strategy: + matrix: # Run on 16 (PreL), 21 (L), and 26 (O). + api-level: [16, 21, 26] + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/artifact-android-emulator-tests + timeout-minutes: 25 with: - name: androidTests-reports-api-30 - path: ${{ github.workspace }}/**/build/reports/androidTests/connected/* + api-level: '${{ matrix.api-level }}' publish-snapshot: name: 'Publish snapshot' # TODO(bcorso): Consider also waiting on artifact-android-emulator-tests # and artifact-android-emulator-legacy-api-tests after checking flakiness. - needs: [bazel-test, artifact-java-local-tests, artifact-android-local-tests] + needs: [bazel-test, artifact-java-local-tests, artifact-android-local-tests, test-gradle-plugin] if: github.event_name == 'push' && github.repository == 'google/dagger' && github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: - name: 'Install Java ${{ env.USE_JAVA_VERSION }}' - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: + distribution: '${{ env.USE_JAVA_DISTRIBUTION }}' java-version: '${{ env.USE_JAVA_VERSION }}' + server-id: sonatype-nexus-snapshots + server-username: CI_DEPLOY_USERNAME + server-password: CI_DEPLOY_PASSWORD - name: 'Check out repository' uses: actions/checkout@v2 - name: 'Cache local Maven repository' @@ -200,9 +124,9 @@ jobs: uses: actions/cache@v2 with: path: ~/.cache/bazel - key: ${{ runner.os }}-bazel-${{ github.sha }} + key: ${{ runner.os }}-bazel-build-${{ github.sha }} restore-keys: | - ${{ runner.os }}-bazel- + ${{ runner.os }}-bazel-build- - name: 'Cache Gradle files' uses: actions/cache@v2 with: @@ -218,50 +142,33 @@ jobs: env: GH_TOKEN: ${{ github.token }} - name: 'Publish latest snapshot' - run: ./util/publish-snapshot-on-commit.sh + run: | + util/deploy-all.sh \ + "deploy:deploy-file" \ + "HEAD-SNAPSHOT" \ + "-DrepositoryId=sonatype-nexus-snapshots" \ + "-Durl=https://oss.sonatype.org/content/repositories/snapshots" shell: bash env: CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} - artifact-android-emulator-legacy-api-tests: - name: 'Artifact Android emulator tests (API ${{ matrix.api-level }})' - # We only run this on master push (essentially a postsubmit) since these - # can take a while to run + - name: 'Clean bazel cache' + # According to the documentation, we should be able to exclude these via + # the actions/cache path, e.g. "!~/.cache/bazel/*/*/external/" but that + # doesn't seem to work. + run: | + rm -rf $(bazel info repository_cache) + rm -rf ~/.cache/bazel/*/*/external/ + shell: bash + build-gradle-plugin-latest-agp: + name: 'Build Hilt Gradle plugin against latest AGP version' + # We only run this on master push (essentially a postsubmit) since we + # don't want this job to prevent merges if: github.event_name == 'push' && github.repository == 'google/dagger' && github.ref == 'refs/heads/master' - needs: bazel-test - # It's recommended to run emulator tests on macOS - # See https://github.com/marketplace/actions/android-emulator-runner - runs-on: macos-latest - strategy: - matrix: # Run on 16 (PreL), 21 (L), and 26 (O). - api-level: [16, 21, 26] + needs: bazel-build + runs-on: ubuntu-latest steps: - - name: 'Check out repository' - uses: actions/checkout@v2 - - name: 'Cache Gradle files' - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: 'Download local snapshot for tests' - uses: actions/download-artifact@v2 - with: - name: local-snapshot - path: ~/.m2/repository/com/google/dagger - - name: 'Gradle Android emulator tests (API ${{ matrix.api-level }})' - uses: reactivecircus/android-emulator-runner@v2 - timeout-minutes: 25 - with: - api-level: ${{ matrix.api-level }} - target: google_apis - script: ./util/run-local-emulator-tests.sh - - name: 'Upload test reports (API ${{ matrix.api-level }})' - if: ${{ always() }} - uses: actions/upload-artifact@v2 + - uses: actions/checkout@v2 + - uses: ./.github/actions/build-gradle-plugin with: - name: androidTests-report-api-${{ matrix.api-level }} - path: ${{ github.workspace }}/**/build/reports/androidTests/connected/* + agp: '+' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..3359c7122 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,142 @@ +name: Dagger Release + +on: + workflow_dispatch: + inputs: + dagger_release_version: + description: 'The Dagger version to use in this release.' + required: true + +env: + USE_JAVA_DISTRIBUTION: 'zulu' + # Our Bazel builds currently rely on JDK 8. + USE_JAVA_VERSION: '8' + # Our Bazel builds currently rely on 4.2.1. The version is set via + # baselisk by USE_BAZEL_VERSION: https://github.com/bazelbuild/bazelisk. + USE_BAZEL_VERSION: '4.2.1' + DAGGER_RELEASE_VERSION: "${{ github.event.inputs.dagger_release_version }}" + +# TODO(bcorso):Convert these jobs into local composite actions to share with the +# continuous integration workflow. +jobs: + validate-latest-dagger-version: + name: 'Validate Dagger version' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/prechecks + bazel-build: + name: 'Bazel build' + needs: validate-latest-dagger-version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/bazel-build + bazel-test: + name: 'Bazel tests' + needs: validate-latest-dagger-version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/bazel-test + artifact-java-local-tests: + name: 'Artifact Java local tests' + needs: bazel-build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/artifact-java-local-tests + test-gradle-plugin: + name: 'Test Hilt Gradle plugin' + needs: bazel-build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/test-gradle-plugin + artifact-android-local-tests: + name: 'Artifact Android local tests (AGP ${{ matrix.agp }})' + needs: bazel-build + runs-on: ubuntu-latest + strategy: + matrix: + agp: ['4.1.0', '4.2.0', '7.0.0'] + steps: + - uses: actions/checkout@v2 + - uses: ./.github/actions/artifact-android-local-tests + with: + agp: '${{ matrix.agp }}' + publish-artifacts: + name: 'Publish Artifact' + needs: [bazel-test, artifact-java-local-tests, artifact-android-local-tests, test-gradle-plugin] + runs-on: ubuntu-latest + steps: + - name: 'Install Java ${{ env.USE_JAVA_VERSION }}' + uses: actions/setup-java@v2 + with: + distribution: '${{ env.USE_JAVA_DISTRIBUTION }}' + java-version: '${{ env.USE_JAVA_VERSION }}' + server-id: sonatype-nexus-staging + server-username: CI_DEPLOY_USERNAME + server-password: CI_DEPLOY_PASSWORD + gpg-private-key: ${{ secrets.CI_GPG_PRIVATE_KEY }} + gpg-passphrase: CI_GPG_PASSPHRASE + - name: 'Check out repository' + uses: actions/checkout@v2 + - name: 'Cache local Maven repository' + uses: actions/cache@v2 + with: + path: | + ~/.m2/repository + !~/.m2/repository/com/google/dagger + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: 'Cache Bazel files' + uses: actions/cache@v2 + with: + path: ~/.cache/bazel + key: ${{ runner.os }}-bazel-build-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-bazel-build- + - name: 'Cache Gradle files' + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Publish artifacts + run: | + util/deploy-all.sh \ + "gpg:sign-and-deploy-file" \ + "${{ env.DAGGER_RELEASE_VERSION }}" \ + "-DrepositoryId=sonatype-nexus-staging" \ + "-Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/" + shell: bash + env: + CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} + CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} + CI_GPG_PASSPHRASE: ${{ secrets.CI_GPG_PASSPHRASE }} + - name: 'Set git credentials' + run: | + git config --global user.email "dagger-dev+github@google.com" + git config --global user.name "Dagger Team" + shell: bash + - name: 'Publish tagged release' + run: util/publish-tagged-release.sh ${{ env.DAGGER_RELEASE_VERSION }} + shell: bash + - name: 'Publish tagged docs' + run: util/publish-tagged-docs.sh ${{ env.DAGGER_RELEASE_VERSION }} + shell: bash + env: + GH_TOKEN: ${{ github.token }} + - name: 'Clean bazel cache' + # According to the documentation, we should be able to exclude these via + # the actions/cache path, e.g. "!~/.cache/bazel/*/*/external/" but that + # doesn't seem to work. + run: | + rm -rf $(bazel info repository_cache) + rm -rf ~/.cache/bazel/*/*/external/ + shell: bash diff --git a/Android.bp b/Android.bp index 17db0d72c..61b133d92 100644 --- a/Android.bp +++ b/Android.bp @@ -45,7 +45,6 @@ license { ], license_text: [ "LICENSE.txt", - "NOTICE", ], } @@ -85,9 +84,18 @@ java_library { "com.android.adservices", "com.android.extservices", "com.android.ondevicepersonalization", + "com.android.healthfitness", + "com.android.devicelock", ], sdk_version: "core_current", + + errorprone: { + javacflags: [ + "-Xep:FormatStringAnnotation:WARN", + "-Xep:NoCanIgnoreReturnValueOnClasses:WARN", + ], + }, } // build dagger2 producers library @@ -126,6 +134,16 @@ java_plugin { jarjar_rules: "jarjar-rules.txt", } +// Dagger distributes its own copy of androidx.room.compiler.processing +// while the API is unstable. There are shading rules in jarjar-rules.txt +// to prevent conflicts with official version. When this is removed +// in favor of the official version the shading rules should also be +// removed. +java_import_host { + name: "dagger2-room-compiler-processing", + jars: ["java/dagger/internal/codegen/xprocessing/xprocessing.jar"], +} + java_library_host { name: "dagger2-compiler-lib", use_tools_jar: true, @@ -136,6 +154,7 @@ java_library_host { "java/dagger/model/*.java", "java/dagger/spi/*.java", + "java/dagger/spi/model/*.java", ], exclude_srcs: [ @@ -151,11 +170,13 @@ java_library_host { "auto_common", "dagger2", "dagger2-producers", + "dagger2-room-compiler-processing", "google_java_format", "guava", "javapoet", "jsr330", "kotlin-stdlib", + "kotlin-stdlib-jdk8", "kotlinx_metadata_jvm", ], @@ -193,6 +214,13 @@ java_library_host { "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", ], + + errorprone: { + javacflags: [ + "-Xep:FormatStringAnnotation:WARN", + "-Xep:NoCanIgnoreReturnValueOnClasses:WARN", + ], + }, } // Compile dummy implementations of annotations used by dagger2 but not @@ -230,7 +258,9 @@ java_library { "hilt_generates_root_input_processor", ], apex_available: [ + "//apex_available:platform", "com.android.ondevicepersonalization", + "com.android.healthfitness", ], } @@ -242,18 +272,22 @@ android_library { srcs: [ "java/dagger/hilt/android/*.java", + "java/dagger/hilt/android/*.kt", "java/dagger/hilt/android/components/*.java", + "java/dagger/hilt/android/flags/*.java", "java/dagger/hilt/android/migration/*.java", "java/dagger/hilt/android/qualifiers/*.java", "java/dagger/hilt/android/scopes/*.java", "java/dagger/hilt/android/internal/*.java", "java/dagger/hilt/android/internal/builders/*.java", + "java/dagger/hilt/android/internal/legacy/*.java", "java/dagger/hilt/android/internal/lifecycle/*.java", "java/dagger/hilt/android/internal/managers/*.java", "java/dagger/hilt/android/internal/migration/*.java", "java/dagger/hilt/android/internal/modules/*.java", "java/dagger/hilt/android/lifecycle/*.java", "java/dagger/hilt/internal/aggregatedroot/*.java", + "java/dagger/hilt/internal/componenttreedeps/*.java", "java/dagger/hilt/internal/processedrootsentinel/*.java", ], manifest: "java/dagger/hilt/android/AndroidManifest.xml", @@ -282,17 +316,22 @@ android_library { ], exported_plugins: [ "dagger2-compiler", + "hilt_android_entry_point_processor", "hilt_aggregated_deps_processor", "hilt_alias_of_processor", + "hilt_component_tree_deps_processor", "hilt_define_component_processor", + "hilt_early_entry_point_processor", "hilt_generates_root_input_processor", "hilt_originating_element_processor", "hilt_root_processor", "hilt_viewmodel_processor", ], apex_available: [ + "//apex_available:platform", "com.android.ondevicepersonalization", + "com.android.healthfitness", ], } @@ -302,7 +341,10 @@ android_library { srcs: [ "java/dagger/hilt/android/internal/testing/*.java", + "java/dagger/hilt/android/internal/testing/root/*.java", + "java/dagger/hilt/android/internal/uninstallmodules/*.java", "java/dagger/hilt/android/testing/*.java", + "java/dagger/hilt/testing/*.java", ], manifest: "java/dagger/hilt/android/testing/AndroidManifest.xml", static_libs: [ @@ -315,6 +357,7 @@ android_library { "android-support-multidex", "jsr305", "dagger2", + "hilt_android", "hilt_core", "junit", ], @@ -327,13 +370,18 @@ android_library { ], exported_plugins: [ "dagger2-compiler", + "hilt_android_entry_point_processor", "hilt_aggregated_deps_processor", + "hilt_alias_of_processor", + "hilt_component_tree_deps_processor", "hilt_define_component_processor", + "hilt_early_entry_point_processor", "hilt_generates_root_input_processor", "hilt_originating_element_processor", "hilt_root_processor", - "hilt_viewmodel_processor", + "hilt_viewmodel_processor", + "hilt_custom_test_application_processor", "hilt_bindvalue_processor", "hilt_uninstall_modules_processor", @@ -370,12 +418,24 @@ java_plugin { } java_plugin { + name: "hilt_component_tree_deps_processor", + generates_api: true, + processor_class: "dagger.hilt.processor.internal.root.ComponentTreeDepsProcessor", +} + +java_plugin { name: "hilt_define_component_processor", generates_api: true, processor_class: "dagger.hilt.processor.internal.definecomponent.DefineComponentProcessor", } java_plugin { + name: "hilt_early_entry_point_processor", + generates_api: true, + processor_class: "dagger.hilt.processor.internal.earlyentrypoint.EarlyEntryPointProcessor", +} + +java_plugin { name: "hilt_originating_element_processor", generates_api: true, processor_class: "dagger.hilt.processor.internal.originatingelement.OriginatingElementProcessor", @@ -420,6 +480,7 @@ java_library_host { "java/dagger/hilt/android/processor/**/*.kt", "java/dagger/hilt/codegen/*.java", "java/dagger/hilt/processor/internal/**/*.java", + "java/dagger/hilt/processor/internal/**/*.kt", ], plugins: [ "auto_service_plugin", @@ -79,7 +79,7 @@ jarjar_library( name = "shaded_android_processor", jars = [ "//java/dagger/android/processor", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", ], rules = [ "rule com.google.auto.common.** dagger.android.shaded.auto.common.@1", @@ -90,7 +90,7 @@ jarjar_library( name = "shaded_grpc_server_processor", jars = [ "//java/dagger/grpc/server/processor", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", ], rules = [ "rule com.google.auto.common.** dagger.grpc.shaded.auto.common.@1", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 54fb50aa9..f0748e091 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,10 @@ information on using pull requests. Dagger is built with [`bazel`](https://bazel.build). +Ensure that Dagger is checked out on a case-sensitive filesystem. On a +case-insensitive file system (e.g. Windows or MacOS by default) some tasks that +attempt to delete the `build/` folder will also delete the bazel `BUILD` files. + ### Building Dagger from the command line * [Install Bazel](https://docs.bazel.build/versions/master/install.html) diff --git a/LICENSE b/LICENSE new file mode 120000 index 000000000..85de3d454 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +LICENSE.txt
\ No newline at end of file @@ -11,7 +11,7 @@ third_party { type: GIT value: "https://github.com/google/dagger" } - version: "dagger-2.19.1" - last_upgrade_date { year: 2020 month: 11 day: 18 } + version: "dagger-2.41" + last_upgrade_date { year: 2022 month: 04 day: 04 } license_type: NOTICE } diff --git a/NOTICE b/NOTICE deleted file mode 100644 index d64569567..000000000 --- a/NOTICE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. @@ -1 +1,3 @@ include platform/libcore:/OWNERS + +ccross@android.com @@ -39,8 +39,8 @@ release. load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -DAGGER_TAG = "2.28.1" -DAGGER_SHA = "9e69ab2f9a47e0f74e71fe49098bea908c528aa02fa0c5995334447b310d0cdd" +DAGGER_TAG = "2.40.5" +DAGGER_SHA = "5a6923e56edbc1e34c8089ecab5338a1b8ddb79a3a54b6c86cdcf31212680d32" http_archive( name = "dagger", strip_prefix = "dagger-dagger-%s" % DAGGER_TAG, @@ -15,6 +15,39 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") ############################# +# Upgrade java_tools version +############################# + +# These targets added per instructions at +# https://github.com/bazelbuild/java_tools/releases/tag/javac11_v10.7 +http_archive( + name = "remote_java_tools_linux", + sha256 = "cf57fc238ed5c24c718436ab4178ade5eb838fe56e7c32c4fafe0b6fbdaec51f", + urls = [ + "https://mirror.bazel.build/bazel_java_tools/releases/javac11/v10.7/java_tools_javac11_linux-v10.7.zip", + "https://github.com/bazelbuild/java_tools/releases/download/javac11_v10.7/java_tools_javac11_linux-v10.7.zip", + ], +) + +http_archive( + name = "remote_java_tools_windows", + sha256 = "a0fc3a3be3ea01a4858d12f56892dd663c02f218104e8c1dc9f3e90d5e583bcb", + urls = [ + "https://mirror.bazel.build/bazel_java_tools/releases/javac11/v10.7/java_tools_javac11_windows-v10.7.zip", + "https://github.com/bazelbuild/java_tools/releases/download/javac11_v10.7/java_tools_javac11_windows-v10.7.zip", + ], +) + +http_archive( + name = "remote_java_tools_darwin", + sha256 = "51a4cf424d3b26d6c42703cf2d80002f1489ba0d28c939519c3bb9c3d6ee3720", + urls = [ + "https://mirror.bazel.build/bazel_java_tools/releases/javac11/v10.7/java_tools_javac11_darwin-v10.7.zip", + "https://github.com/bazelbuild/java_tools/releases/download/javac11_v10.7/java_tools_javac11_darwin-v10.7.zip", + ], +) + +############################# # Load nested repository ############################# @@ -31,9 +64,9 @@ local_repository( http_archive( name = "google_bazel_common", - sha256 = "d8aa0ef609248c2a494d5dbdd4c89ef2a527a97c5a87687e5a218eb0b77ff640", - strip_prefix = "bazel-common-4a8d451e57fb7e1efecbf9495587a10684a19eb2", - urls = ["https://github.com/google/bazel-common/archive/4a8d451e57fb7e1efecbf9495587a10684a19eb2.zip"], + sha256 = "8b6aebdc095c8448b2f6a72bb8eae4a563891467e2d20c943f21940b1c444e38", + strip_prefix = "bazel-common-3d0e5005cfcbee836e31695d4ab91b5328ccc506", + urls = ["https://github.com/google/bazel-common/archive/3d0e5005cfcbee836e31695d4ab91b5328ccc506.zip"], ) load("@google_bazel_common//:workspace_defs.bzl", "google_common_workspace_rules") @@ -87,36 +120,41 @@ robolectric_repositories() # Load Kotlin repository ############################# -RULES_KOTLIN_COMMIT = "2c283821911439e244285b5bfec39148e7d90e21" +RULES_KOTLIN_COMMIT = "686f0f1cf3e1cc8c750688bb082316b3eadb3cb6" -RULES_KOTLIN_SHA = "b04cd539e7e3571745179da95069586b6fa76a64306b24bb286154e652010608" +RULES_KOTLIN_SHA = "1d8758bbf27400a5f9d40f01e4337f6834d2b7864df34e9aa5cf0a9ab6cc9241" http_archive( - name = "io_bazel_rules_kotlin", + name = "io_bazel_rules_kotlin_head", sha256 = RULES_KOTLIN_SHA, strip_prefix = "rules_kotlin-%s" % RULES_KOTLIN_COMMIT, type = "zip", urls = ["https://github.com/bazelbuild/rules_kotlin/archive/%s.zip" % RULES_KOTLIN_COMMIT], ) -load("@io_bazel_rules_kotlin//kotlin:dependencies.bzl", "kt_download_local_dev_dependencies") +load("@io_bazel_rules_kotlin_head//src/main/starlark/release_archive:repository.bzl", "archive_repository") -kt_download_local_dev_dependencies() +archive_repository( + name = "io_bazel_rules_kotlin", + source_repository_name = "io_bazel_rules_kotlin_head", +) -load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories") +load("@io_bazel_rules_kotlin//kotlin:repositories.bzl", "kotlin_repositories", "kotlinc_version") -KOTLIN_VERSION = "1.4.20" +KOTLIN_VERSION = "1.5.32" -KOTLINC_RELEASE_SHA = "11db93a4d6789e3406c7f60b9f267eba26d6483dcd771eff9f85bb7e9837011f" +KOTLINC_RELEASE_SHA = "2e728c43ee0bf819eae06630a4cbbc28ba2ed5b19a55ee0af96d2c0ab6b6c2a5" -KOTLINC_RELEASE = { - "sha256": KOTLINC_RELEASE_SHA, - "urls": ["https://github.com/JetBrains/kotlin/releases/download/v{v}/kotlin-compiler-{v}.zip".format(v = KOTLIN_VERSION)], -} +kotlin_repositories( + compiler_release = kotlinc_version( + release = KOTLIN_VERSION, + sha256 = KOTLINC_RELEASE_SHA, + ), +) -kotlin_repositories(compiler_release = KOTLINC_RELEASE) +load("@io_bazel_rules_kotlin//kotlin:core.bzl", "kt_register_toolchains") -register_toolchains("//:kotlin_toolchain") +kt_register_toolchains() ############################# # Load Maven dependencies @@ -135,23 +173,39 @@ http_archive( load("@rules_jvm_external//:defs.bzl", "maven_install") -ANDROID_LINT_VERSION = "26.6.2" +ANDROID_LINT_VERSION = "30.1.0" + +AUTO_COMMON_VERSION = "1.2.1" + +# NOTE(bcorso): Even though we set the version here, our Guava version in +# processor code will use whatever version is built into JavaBuilder, which is +# tied to the version of Bazel we're using. +GUAVA_VERSION = "27.1" + +GRPC_VERSION = "1.2.0" + +INCAP_VERSION = "0.2" + +BYTE_BUDDY_VERSION = "1.9.10" + +CHECKER_FRAMEWORK_VERSION = "2.5.3" + +ERROR_PRONE_VERSION = "2.3.2" maven_install( artifacts = [ "androidx.annotation:annotation:1.1.0", - "androidx.appcompat:appcompat:1.2.0", - "androidx.activity:activity:1.2.2", - "androidx.fragment:fragment:1.3.2", + "androidx.appcompat:appcompat:1.3.1", + "androidx.activity:activity:1.3.1", + "androidx.fragment:fragment:1.3.6", "androidx.lifecycle:lifecycle-common:2.3.1", "androidx.lifecycle:lifecycle-viewmodel:2.3.1", "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1", "androidx.multidex:multidex:2.0.1", "androidx.savedstate:savedstate:1.0.0", - "androidx.test:monitor:1.1.1", - "androidx.test:core:1.1.0", - "androidx.test.ext:junit:1.1.2", - "com.google.auto:auto-common:0.11", + "androidx.test:monitor:1.4.0", + "androidx.test:core:1.4.0", + "androidx.test.ext:junit:1.1.3", "com.android.support:appcompat-v7:25.0.0", "com.android.support:support-annotations:25.0.0", "com.android.support:support-fragment:25.0.0", @@ -164,17 +218,54 @@ maven_install( "com.android.tools.lint:lint-tests:%s" % ANDROID_LINT_VERSION, "com.android.tools:testutils:%s" % ANDROID_LINT_VERSION, "com.github.tschuchortdev:kotlin-compile-testing:1.2.8", - "com.google.guava:guava:27.1-android", + "com.google.auto:auto-common:%s" % AUTO_COMMON_VERSION, + "com.google.auto.factory:auto-factory:1.0", + "com.google.auto.service:auto-service:1.0", + "com.google.auto.service:auto-service-annotations:1.0", + "com.google.auto.value:auto-value:1.6", + "com.google.auto.value:auto-value-annotations:1.6", + "com.google.code.findbugs:jsr305:3.0.1", + "com.google.devtools.ksp:symbol-processing-api:1.5.30-1.0.0", + "com.google.errorprone:error_prone_annotation:%s" % ERROR_PRONE_VERSION, + "com.google.errorprone:error_prone_annotations:%s" % ERROR_PRONE_VERSION, + "com.google.errorprone:error_prone_check_api:%s" % ERROR_PRONE_VERSION, + "com.google.googlejavaformat:google-java-format:1.5", + "com.google.guava:guava:%s-jre" % GUAVA_VERSION, + "com.google.guava:guava-testlib:%s-jre" % GUAVA_VERSION, + "com.google.guava:failureaccess:1.0.1", + "com.google.guava:guava-beta-checker:1.0", + "com.google.protobuf:protobuf-java:3.7.0", + "com.google.testing.compile:compile-testing:0.18", + "com.google.truth:truth:1.1", + "com.squareup:javapoet:1.13.0", + "io.grpc:grpc-context:%s" % GRPC_VERSION, + "io.grpc:grpc-core:%s" % GRPC_VERSION, + "io.grpc:grpc-netty:%s" % GRPC_VERSION, + "io.grpc:grpc-protobuf:%s" % GRPC_VERSION, + "jakarta.inject:jakarta.inject-api:2.0.1", + "javax.annotation:jsr250-api:1.0", + "javax.inject:javax.inject:1", + "javax.inject:javax.inject-tck:1", "junit:junit:4.13", + "net.bytebuddy:byte-buddy:%s" % BYTE_BUDDY_VERSION, + "net.bytebuddy:byte-buddy-agent:%s" % BYTE_BUDDY_VERSION, + "net.ltgt.gradle.incap:incap:%s" % INCAP_VERSION, + "net.ltgt.gradle.incap:incap-processor:%s" % INCAP_VERSION, + "org.checkerframework:checker-compat-qual:%s" % CHECKER_FRAMEWORK_VERSION, + "org.checkerframework:dataflow:%s" % CHECKER_FRAMEWORK_VERSION, + "org.checkerframework:javacutil:%s" % CHECKER_FRAMEWORK_VERSION, + "org.hamcrest:hamcrest-core:1.3", "org.jetbrains.kotlin:kotlin-stdlib:%s" % KOTLIN_VERSION, - "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.2.0", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8:%s" % KOTLIN_VERSION, + "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.3.0", + "org.mockito:mockito-core:2.28.2", + "org.objenesis:objenesis:1.0", "org.robolectric:robolectric:4.4", "org.robolectric:shadows-framework:4.4", # For ActivityController ], repositories = [ "https://repo1.maven.org/maven2", "https://maven.google.com", - "https://jcenter.bintray.com/", # Lint has one trove4j dependency in jCenter ], ) diff --git a/android-annotation-stubs/gen_annotations.sh b/android-annotation-stubs/gen_annotations.sh index d4a029029..1aa5d278b 100755 --- a/android-annotation-stubs/gen_annotations.sh +++ b/android-annotation-stubs/gen_annotations.sh @@ -58,9 +58,14 @@ cat > ${f} <<EOF */ package net.ltgt.gradle.incap; +import java.util.Locale; public enum IncrementalAnnotationProcessorType { AGGREGATING, DYNAMIC, - ISOLATING + ISOLATING; + + public String getProcessorOption() { + return "org.gradle.annotation.processing." + name().toLowerCase(Locale.ROOT); + } } EOF diff --git a/android-annotation-stubs/src/net/ltgt/gradle/incap/IncrementalAnnotationProcessorType.java b/android-annotation-stubs/src/net/ltgt/gradle/incap/IncrementalAnnotationProcessorType.java index ef86328ae..55a3a5e47 100644 --- a/android-annotation-stubs/src/net/ltgt/gradle/incap/IncrementalAnnotationProcessorType.java +++ b/android-annotation-stubs/src/net/ltgt/gradle/incap/IncrementalAnnotationProcessorType.java @@ -15,8 +15,13 @@ */ package net.ltgt.gradle.incap; +import java.util.Locale; public enum IncrementalAnnotationProcessorType { AGGREGATING, DYNAMIC, - ISOLATING + ISOLATING; + + public String getProcessorOption() { + return "org.gradle.annotation.processing." + name().toLowerCase(Locale.ROOT); + } } diff --git a/examples/bazel/WORKSPACE b/examples/bazel/WORKSPACE index 23f93b11c..6a1197f4d 100644 --- a/examples/bazel/WORKSPACE +++ b/examples/bazel/WORKSPACE @@ -19,7 +19,9 @@ # Load Dagger repository ######################## -# TODO(bcorso): Replace with `http_archive` pointing to tagged released. +# In a real project, this repository would use `http_archive` to link to a +# tagged, released version of the Dagger, but we use `local_repository` so that +# CI testing can test local changes to workspace_defs.bzl. local_repository( name = "dagger", path = "../../", diff --git a/jarjar-rules.txt b/jarjar-rules.txt index 57941b373..abb7fb27a 100644 --- a/jarjar-rules.txt +++ b/jarjar-rules.txt @@ -1,3 +1,6 @@ # shade guava to avoid conflicts with guava embedded in Error Prone. rule com.google.common.** com.google.dagger.common.@1 rule com.google.auto.** com.google.dagger.auto.@1 + +# shade local xprocessing.jar to avoid conflicts with upstream Xprocessing +rule androidx.room.compiler.processing.** dagger.spi.shaded.androidx.room.compiler.processing.@1 diff --git a/java/dagger/BUILD b/java/dagger/BUILD index aaebadee0..17c6c9665 100644 --- a/java/dagger/BUILD +++ b/java/dagger/BUILD @@ -32,9 +32,9 @@ java_library( srcs = glob(["**/*.java"]), javacopts = SOURCE_7_TARGET_7 + DOCLINT_HTML_AND_SYNTAX, tags = ["maven_coordinates=com.google.dagger:dagger:" + POM_VERSION], - exports = ["@google_bazel_common//third_party/java/jsr330_inject"], + exports = ["//third_party/java/jsr330_inject"], deps = [ - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/jsr330_inject", ], ) @@ -55,5 +55,5 @@ javadoc_library( srcs = [":javadoc-srcs"], exclude_packages = ["dagger.internal"], root_packages = ["dagger"], - deps = ["@google_bazel_common//third_party/java/jsr330_inject"], + deps = ["//third_party/java/jsr330_inject"], ) diff --git a/java/dagger/Component.java b/java/dagger/Component.java index 709b0e6be..be746e815 100644 --- a/java/dagger/Component.java +++ b/java/dagger/Component.java @@ -35,14 +35,16 @@ import javax.inject.Singleton; * example, {@code @Component interface MyComponent {...}} will produce an implementation named * {@code DaggerMyComponent}. * - * <a name="component-methods"></a> + * <p><a id="component-methods"></a> + * * <h2>Component methods</h2> * * <p>Every type annotated with {@code @Component} must contain at least one abstract component * method. Component methods may have any name, but must have signatures that conform to either * {@linkplain Provider provision} or {@linkplain MembersInjector members-injection} contracts. * - * <a name="provision-methods"></a> + * <p><a id="provision-methods"></a> + * * <h3>Provision methods</h3> * * <p>Provision methods have no parameters and return an {@link Inject injected} or {@link Provides @@ -68,7 +70,8 @@ import javax.inject.Singleton; * {@literal Lazy<SomeType>} getLazySomeType(); * </code></pre> * - * <a name="members-injection-methods"></a> + * <a id="members-injection-methods"></a> + * * <h3>Members-injection methods</h3> * * <p>Members-injection methods have a single parameter and inject dependencies into each of the @@ -111,7 +114,8 @@ import javax.inject.Singleton; * } * </code></pre> * - * <a name="instantiation"></a> + * <a id="instantiation"></a> + * * <h2>Instantiation</h2> * * <p>Component implementations are primarily instantiated via a generated <a @@ -131,17 +135,17 @@ import javax.inject.Singleton; * <p>Example of using a builder: * * <pre>{@code - * public static void main(String[] args) { - * OtherComponent otherComponent = ...; - * MyComponent component = DaggerMyComponent.builder() - * // required because component dependencies must be set - * .otherComponent(otherComponent) - * // required because FlagsModule has constructor parameters - * .flagsModule(new FlagsModule(args)) - * // may be elided because a no-args constructor is visible - * .myApplicationModule(new MyApplicationModule()) - * .build(); - * } + * public static void main(String[] args) { + * OtherComponent otherComponent = ...; + * MyComponent component = DaggerMyComponent.builder() + * // required because component dependencies must be set + * .otherComponent(otherComponent) + * // required because FlagsModule has constructor parameters + * .flagsModule(new FlagsModule(args)) + * // may be elided because a no-args constructor is visible + * .myApplicationModule(new MyApplicationModule()) + * .build(); + * } * }</pre> * * <p>Example of using a factory: @@ -162,7 +166,8 @@ import javax.inject.Singleton; * SomeComponent.create()} and {@code SomeComponent.builder().build()} are both valid and * equivalent. * - * <a name="scope"></a> + * <p><a id="scope"></a> + * * <h2>Scope</h2> * * <p>Each Dagger component can be associated with a scope by annotating it with the {@linkplain @@ -184,14 +189,16 @@ import javax.inject.Singleton; * self-contained implementations, exiting a scope is as simple as dropping all references to the * component instance. * - * <a name="component-relationships"></a> + * <p><a id="component-relationships"></a> + * * <h2>Component relationships</h2> * * <p>While there is much utility in isolated components with purely unscoped bindings, many * applications will call for multiple components with multiple scopes to interact. Dagger provides * two mechanisms for relating components. * - * <a name="subcomponents"></a> + * <p><a id="subcomponents"></a> + * * <h3>Subcomponents</h3> * * <p>The simplest way to relate two components is by declaring a {@link Subcomponent}. A @@ -219,7 +226,8 @@ import javax.inject.Singleton; * } * </code></pre> * - * <a name="component-dependencies"></a> + * <a id="component-dependencies"></a> + * * <h3>Component dependencies</h3> * * <p>While subcomponents are the simplest way to compose subgraphs of bindings, subcomponents are diff --git a/java/dagger/Module.java b/java/dagger/Module.java index bd32a1f23..a3cdb54b8 100644 --- a/java/dagger/Module.java +++ b/java/dagger/Module.java @@ -23,18 +23,15 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotates a class that contributes to the object graph. - */ +/** Annotates a class that contributes to the object graph. */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Module { /** - * Additional {@code @Module}-annotated classes from which this module is - * composed. The de-duplicated contributions of the modules in - * {@code includes}, and of their inclusions recursively, are all contributed - * to the object graph. + * Additional {@code @Module}-annotated classes from which this module is composed. The + * de-duplicated contributions of the modules in {@code includes}, and of their inclusions + * recursively, are all contributed to the object graph. */ Class<?>[] includes() default {}; diff --git a/java/dagger/Provides.java b/java/dagger/Provides.java index 5d7827be7..011f9903f 100644 --- a/java/dagger/Provides.java +++ b/java/dagger/Provides.java @@ -33,14 +33,14 @@ import java.lang.annotation.Target; * <p>Dagger forbids injecting {@code null} by default. Component implementations that invoke * {@code @Provides} methods that return {@code null} will throw a {@link NullPointerException} * immediately thereafter. {@code @Provides} methods may opt into allowing {@code null} by - * annotating the method with any {@code @Nullable} annotation like - * {@code javax.annotation.Nullable} or {@code androidx.annotation.Nullable}. + * annotating the method with any {@code @Nullable} annotation like {@code + * javax.annotation.Nullable} or {@code androidx.annotation.Nullable}. * - * <p>If a {@code @Provides} method is marked {@code @Nullable}, Dagger will <em>only</em> - * allow injection into sites that are marked {@code @Nullable} as well. A component that - * attempts to pair a {@code @Nullable} provision with a non-{@code @Nullable} injection site - * will fail to compile. + * <p>If a {@code @Provides} method is marked {@code @Nullable}, Dagger will <em>only</em> allow + * injection into sites that are marked {@code @Nullable} as well. A component that attempts to pair + * a {@code @Nullable} provision with a non-{@code @Nullable} injection site will fail to compile. */ -@Documented @Target(METHOD) @Retention(RUNTIME) -public @interface Provides { -} +@Documented +@Target(METHOD) +@Retention(RUNTIME) +public @interface Provides {} diff --git a/java/dagger/android/BUILD b/java/dagger/android/BUILD index f0cea4149..503a29860 100644 --- a/java/dagger/android/BUILD +++ b/java/dagger/android/BUILD @@ -54,8 +54,8 @@ android_library( ], deps = [ "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/error_prone:annotations", + "//third_party/java/auto:value", + "//third_party/java/error_prone:annotations", "@maven//:androidx_annotation_annotation", ], ) @@ -68,14 +68,6 @@ pom_file( targets = [":android"], ) -# b/37741866 and https://github.com/google/dagger/issues/715 -pom_file( - name = "jarimpl-pom", - artifact_id = "dagger-android-jarimpl", - artifact_name = "Dagger Android", - targets = [":android"], -) - dejetified_library( name = "dejetified-android", input = ":android.aar", @@ -87,8 +79,8 @@ android_library( tags = ["maven_coordinates=com.google.dagger:dagger-android-legacy:" + POM_VERSION], exports = [ "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/error_prone:annotations", + "//third_party/java/auto:value", + "//third_party/java/error_prone:annotations", "@maven//:com_android_support_support_annotations", ], ) diff --git a/java/dagger/android/internal/proguard/BUILD b/java/dagger/android/internal/proguard/BUILD index a11d0c07e..5a85279fb 100644 --- a/java/dagger/android/internal/proguard/BUILD +++ b/java/dagger/android/internal/proguard/BUILD @@ -25,7 +25,7 @@ java_library( srcs = ["ProguardProcessor.java"], javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES, deps = [ - "@google_bazel_common//third_party/java/auto:service", + "//third_party/java/auto:service", ], ) diff --git a/java/dagger/android/processor/AndroidInjectorDescriptor.java b/java/dagger/android/processor/AndroidInjectorDescriptor.java index 3ec66139e..0b9d74b6d 100644 --- a/java/dagger/android/processor/AndroidInjectorDescriptor.java +++ b/java/dagger/android/processor/AndroidInjectorDescriptor.java @@ -16,10 +16,8 @@ package dagger.android.processor; -import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations; import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; -import static com.google.auto.common.MoreElements.getAnnotationMirror; -import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotatedAnnotations; import static java.util.stream.Collectors.toList; import static javax.lang.model.element.Modifier.ABSTRACT; @@ -27,16 +25,13 @@ import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; -import dagger.Module; -import dagger.android.ContributesAndroidInjector; import java.util.List; import java.util.Optional; import javax.annotation.processing.Messager; -import javax.inject.Qualifier; -import javax.inject.Scope; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; @@ -47,24 +42,24 @@ import javax.lang.model.util.SimpleAnnotationValueVisitor8; import javax.tools.Diagnostic.Kind; /** - * A descriptor of a generated {@link Module} and {@link dagger.Subcomponent} to be generated from a - * {@link ContributesAndroidInjector} method. + * A descriptor of a generated {@link dagger.Module} and {@link dagger.Subcomponent} to be generated + * from a {@code ContributesAndroidInjector} method. */ @AutoValue abstract class AndroidInjectorDescriptor { - /** The type to be injected; the return type of the {@link ContributesAndroidInjector} method. */ + /** The type to be injected; the return type of the {@code ContributesAndroidInjector} method. */ abstract ClassName injectedType(); /** Scopes to apply to the generated {@link dagger.Subcomponent}. */ abstract ImmutableSet<AnnotationSpec> scopes(); - /** @see ContributesAndroidInjector#modules() */ + /** See {@code ContributesAndroidInjector#modules()} */ abstract ImmutableSet<ClassName> modules(); - /** The {@link Module} that contains the {@link ContributesAndroidInjector} method. */ + /** The {@link dagger.Module} that contains the {@code ContributesAndroidInjector} method. */ abstract ClassName enclosingModule(); - /** The method annotated with {@link ContributesAndroidInjector}. */ + /** The method annotated with {@code ContributesAndroidInjector}. */ abstract ExecutableElement method(); @AutoValue.Builder @@ -90,7 +85,7 @@ abstract class AndroidInjectorDescriptor { } /** - * Validates a {@link ContributesAndroidInjector} method, returning an {@link + * Validates a {@code ContributesAndroidInjector} method, returning an {@link * AndroidInjectorDescriptor} if it is valid, or {@link Optional#empty()} otherwise. */ Optional<AndroidInjectorDescriptor> createIfValid(ExecutableElement method) { @@ -107,7 +102,7 @@ abstract class AndroidInjectorDescriptor { AndroidInjectorDescriptor.Builder builder = new AutoValue_AndroidInjectorDescriptor.Builder().method(method); TypeElement enclosingElement = MoreElements.asType(method.getEnclosingElement()); - if (!isAnnotationPresent(enclosingElement, Module.class)) { + if (!MoreDaggerElements.isAnnotationPresent(enclosingElement, TypeNames.MODULE)) { reporter.reportError("@ContributesAndroidInjector methods must be in a @Module"); } builder.enclosingModule(ClassName.get(enclosingElement)); @@ -121,21 +116,26 @@ abstract class AndroidInjectorDescriptor { } AnnotationMirror annotation = - getAnnotationMirror(method, ContributesAndroidInjector.class).get(); + MoreDaggerElements.getAnnotationMirror(method, TypeNames.CONTRIBUTES_ANDROID_INJECTOR) + .get(); for (TypeMirror module : getAnnotationValue(annotation, "modules").accept(new AllTypesVisitor(), null)) { - if (isAnnotationPresent(MoreTypes.asElement(module), Module.class)) { + if (MoreDaggerElements.isAnnotationPresent(MoreTypes.asElement(module), TypeNames.MODULE)) { builder.modulesBuilder().add((ClassName) TypeName.get(module)); } else { reporter.reportError(String.format("%s is not a @Module", module), annotation); } } - for (AnnotationMirror scope : getAnnotatedAnnotations(method, Scope.class)) { + for (AnnotationMirror scope : Sets.union( + getAnnotatedAnnotations(method, TypeNames.SCOPE), + getAnnotatedAnnotations(method, TypeNames.SCOPE_JAVAX))) { builder.scopesBuilder().add(AnnotationSpec.get(scope)); } - for (AnnotationMirror qualifier : getAnnotatedAnnotations(method, Qualifier.class)) { + for (AnnotationMirror qualifier : Sets.union( + getAnnotatedAnnotations(method, TypeNames.QUALIFIER), + getAnnotatedAnnotations(method, TypeNames.QUALIFIER_JAVAX))) { reporter.reportError( "@ContributesAndroidInjector methods cannot have qualifiers", qualifier); } diff --git a/java/dagger/android/processor/AndroidMapKeyValidator.java b/java/dagger/android/processor/AndroidMapKeyValidator.java index f6e808ac6..02135265e 100644 --- a/java/dagger/android/processor/AndroidMapKeyValidator.java +++ b/java/dagger/android/processor/AndroidMapKeyValidator.java @@ -17,25 +17,19 @@ package dagger.android.processor; import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations; -import static com.google.auto.common.MoreElements.getAnnotationMirror; -import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.android.processor.AndroidMapKeys.injectedTypeFromMapKey; +import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotatedAnnotations; -import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; +import com.google.auto.common.BasicAnnotationProcessor.Step; import com.google.auto.common.MoreElements; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.SetMultimap; -import dagger.Binds; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Sets; +import com.squareup.javapoet.ClassName; import dagger.MapKey; -import dagger.android.AndroidInjectionKey; -import dagger.android.AndroidInjector; -import dagger.multibindings.ClassKey; -import java.lang.annotation.Annotation; -import java.util.Set; import javax.annotation.processing.Messager; -import javax.inject.Qualifier; -import javax.inject.Scope; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; @@ -46,8 +40,13 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; -/** Validates the correctness of {@link MapKey}s used with {@code dagger.android}. */ -final class AndroidMapKeyValidator implements ProcessingStep { +/** Validates the correctness of {@link dagger.MapKey}s used with {@code dagger.android}. */ +final class AndroidMapKeyValidator implements Step { + private static final ImmutableMap<String, ClassName> SUPPORTED_ANNOTATIONS = + ImmutableMap.of( + TypeNames.ANDROID_INJECTION_KEY.toString(), TypeNames.ANDROID_INJECTION_KEY, + TypeNames.CLASS_KEY.toString(), TypeNames.CLASS_KEY); + private final Elements elements; private final Types types; private final Messager messager; @@ -59,16 +58,12 @@ final class AndroidMapKeyValidator implements ProcessingStep { } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.<Class<? extends Annotation>>builder() - .add(AndroidInjectionKey.class) - .add(ClassKey.class) - .build(); + public ImmutableSet<String> annotations() { + return SUPPORTED_ANNOTATIONS.keySet(); } @Override - public Set<Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + public ImmutableSet<Element> process(ImmutableSetMultimap<String, Element> elementsByAnnotation) { ImmutableSet.Builder<Element> deferredElements = ImmutableSet.builder(); elementsByAnnotation .entries() @@ -83,8 +78,9 @@ final class AndroidMapKeyValidator implements ProcessingStep { return deferredElements.build(); } - private void validateMethod(Class<? extends Annotation> annotation, ExecutableElement method) { - if (!getAnnotatedAnnotations(method, Qualifier.class).isEmpty()) { + private void validateMethod(String annotation, ExecutableElement method) { + if (!Sets.union(getAnnotatedAnnotations(method, TypeNames.QUALIFIER), + getAnnotatedAnnotations(method, TypeNames.QUALIFIER_JAVAX)).isEmpty()) { return; } @@ -94,7 +90,8 @@ final class AndroidMapKeyValidator implements ProcessingStep { return; } - if (!getAnnotatedAnnotations(method, Scope.class).isEmpty()) { + if (!Sets.union(getAnnotatedAnnotations(method, TypeNames.SCOPE), + getAnnotatedAnnotations(method, TypeNames.SCOPE_JAVAX)).isEmpty()) { SuppressWarnings suppressedWarnings = method.getAnnotation(SuppressWarnings.class); if (suppressedWarnings == null || !ImmutableSet.copyOf(suppressedWarnings.value()) @@ -107,7 +104,7 @@ final class AndroidMapKeyValidator implements ProcessingStep { Kind.ERROR, String.format( "%s bindings should not be scoped. Scoping this method may leak instances of %s.", - AndroidInjector.Factory.class.getCanonicalName(), + TypeNames.ANDROID_INJECTOR_FACTORY.canonicalName(), mapKeyValueElement.getQualifiedName()), method); } @@ -117,7 +114,8 @@ final class AndroidMapKeyValidator implements ProcessingStep { // @Binds methods should only have one parameter, but we can't guarantee the order of Processors // in javac, so do a basic check for valid form - if (isAnnotationPresent(method, Binds.class) && method.getParameters().size() == 1) { + if (MoreDaggerElements.isAnnotationPresent(method, TypeNames.BINDS) + && method.getParameters().size() == 1) { validateMapKeyMatchesBindsParameter(annotation, method); } } @@ -138,10 +136,10 @@ final class AndroidMapKeyValidator implements ProcessingStep { } /** - * A valid @Binds method could bind an {@link AndroidInjector.Factory} for one type, while giving + * A valid @Binds method could bind an {@code AndroidInjector.Factory} for one type, while giving * it a map key of a different type. The return type and parameter type would pass typical @Binds - * validation, but the map lookup in {@link dagger.android.DispatchingAndroidInjector} would - * retrieve the wrong injector factory. + * validation, but the map lookup in {@code DispatchingAndroidInjector} would retrieve the wrong + * injector factory. * * <pre>{@code * {@literal @Binds} @@ -151,10 +149,10 @@ final class AndroidMapKeyValidator implements ProcessingStep { * BlueActivityComponent.Builder builder); * }</pre> */ - private void validateMapKeyMatchesBindsParameter( - Class<? extends Annotation> annotation, ExecutableElement method) { + private void validateMapKeyMatchesBindsParameter(String annotation, ExecutableElement method) { TypeMirror parameterType = getOnlyElement(method.getParameters()).asType(); - AnnotationMirror annotationMirror = getAnnotationMirror(method, annotation).get(); + AnnotationMirror annotationMirror = + MoreDaggerElements.getAnnotationMirror(method, SUPPORTED_ANNOTATIONS.get(annotation)).get(); TypeMirror mapKeyType = elements.getTypeElement(injectedTypeFromMapKey(annotationMirror).get()).asType(); if (!types.isAssignable(parameterType, injectorFactoryOf(mapKeyType))) { @@ -172,6 +170,6 @@ final class AndroidMapKeyValidator implements ProcessingStep { } private TypeElement factoryElement() { - return elements.getTypeElement(AndroidInjector.Factory.class.getCanonicalName()); + return elements.getTypeElement(TypeNames.ANDROID_INJECTOR_FACTORY.canonicalName()); } } diff --git a/java/dagger/android/processor/AndroidMapKeys.java b/java/dagger/android/processor/AndroidMapKeys.java index fb1fc3853..28da2715a 100644 --- a/java/dagger/android/processor/AndroidMapKeys.java +++ b/java/dagger/android/processor/AndroidMapKeys.java @@ -19,7 +19,6 @@ package dagger.android.processor; import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; import com.google.auto.common.MoreTypes; -import dagger.android.AndroidInjectionKey; import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.TypeElement; @@ -27,7 +26,7 @@ import javax.lang.model.type.TypeMirror; final class AndroidMapKeys { /** - * If {@code mapKey} is {@link AndroidInjectionKey}, returns the string value for the map key. If + * If {@code mapKey} is {@code AndroidInjectionKey}, returns the string value for the map key. If * it's {@link dagger.multibindings.ClassKey}, returns the fully-qualified class name of the * annotation value. Otherwise returns {@link Optional#empty()}. */ diff --git a/java/dagger/android/processor/AndroidProcessor.java b/java/dagger/android/processor/AndroidProcessor.java index ad7f08ef0..357f7dea4 100644 --- a/java/dagger/android/processor/AndroidProcessor.java +++ b/java/dagger/android/processor/AndroidProcessor.java @@ -55,7 +55,7 @@ public final class AndroidProcessor extends BasicAnnotationProcessor { "dagger.android.experimentalUseStringKeys"; @Override - protected Iterable<? extends ProcessingStep> initSteps() { + protected Iterable<? extends Step> steps() { Filer filer = new FormattingFiler(processingEnv.getFiler()); Messager messager = processingEnv.getMessager(); Elements elements = processingEnv.getElementUtils(); diff --git a/java/dagger/android/processor/BUILD b/java/dagger/android/processor/BUILD index 7e63c7633..3ab6eee3a 100644 --- a/java/dagger/android/processor/BUILD +++ b/java/dagger/android/processor/BUILD @@ -15,7 +15,7 @@ # Description: # Public Dagger API for Android -load("@rules_java//java:defs.bzl", "java_import", "java_library", "java_plugin") +load("@rules_java//java:defs.bzl", "java_library", "java_plugin") load( "//:build_defs.bzl", "DOCLINT_HTML_AND_SYNTAX", @@ -38,35 +38,20 @@ java_library( javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES, tags = ["maven_coordinates=com.google.dagger:dagger-android-processor:" + POM_VERSION], deps = [ - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/auto:value", - "@maven//:com_google_auto_auto_common", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/google_java_format", "//java/dagger:core", + "//java/dagger/internal/codegen/langmodel", "//java/dagger/spi", - # https://github.com/bazelbuild/bazel/issues/2517 - ":dagger-android-jar", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/auto:value", + "//third_party/java/google_java_format", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/incap", + "//third_party/java/javapoet", ], ) -# https://github.com/bazelbuild/bazel/issues/2517 -# This target serves two (related) purposes: -# 1. Bazel does not allow a java_library to depend on an android_library, even if that java_library -# will be used in a java_plugin. -# 2. It stores the metadata for the "jarimpl" target that we use to work-around Gradle not loading -# aar artifacts that are declared as deps of an annotation processor. Our pom.xml generator reads -# the tags and includes them apppropriately. -java_import( - name = "dagger-android-jar", - jars = ["//java/dagger/android:libandroid.jar"], - tags = ["maven_coordinates=com.google.dagger:dagger-android-jarimpl:" + POM_VERSION], - visibility = ["//visibility:private"], -) - pom_file( name = "pom", artifact_id = "dagger-android-processor", diff --git a/java/dagger/android/processor/ContributesAndroidInjectorGenerator.java b/java/dagger/android/processor/ContributesAndroidInjectorGenerator.java index 5c99fd413..f3f4d18ab 100644 --- a/java/dagger/android/processor/ContributesAndroidInjectorGenerator.java +++ b/java/dagger/android/processor/ContributesAndroidInjectorGenerator.java @@ -29,10 +29,10 @@ import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import static javax.lang.model.util.ElementFilter.methodsIn; -import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; +import com.google.auto.common.BasicAnnotationProcessor.Step; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.SetMultimap; +import com.google.common.collect.ImmutableSetMultimap; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; @@ -41,26 +41,16 @@ import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.WildcardTypeName; -import dagger.Binds; -import dagger.Module; -import dagger.Subcomponent; -import dagger.android.AndroidInjectionKey; -import dagger.android.AndroidInjector; -import dagger.android.ContributesAndroidInjector; import dagger.android.processor.AndroidInjectorDescriptor.Validator; -import dagger.multibindings.ClassKey; -import dagger.multibindings.IntoMap; import java.io.IOException; -import java.lang.annotation.Annotation; -import java.util.Set; import javax.annotation.processing.Filer; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.util.Elements; -/** Generates the implementation specified in {@link ContributesAndroidInjector}. */ -final class ContributesAndroidInjectorGenerator implements ProcessingStep { +/** Generates the implementation specified in {@code ContributesAndroidInjector}. */ +final class ContributesAndroidInjectorGenerator implements Step { private final AndroidInjectorDescriptor.Validator validator; private final Filer filer; @@ -82,13 +72,12 @@ final class ContributesAndroidInjectorGenerator implements ProcessingStep { } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(ContributesAndroidInjector.class); + public ImmutableSet<String> annotations() { + return ImmutableSet.of(TypeNames.CONTRIBUTES_ANDROID_INJECTOR.toString()); } @Override - public Set<Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { + public ImmutableSet<Element> process(ImmutableSetMultimap<String, Element> elementsByAnnotation) { ImmutableSet.Builder<Element> deferredElements = ImmutableSet.builder(); for (ExecutableElement method : methodsIn(elementsByAnnotation.values())) { try { @@ -118,7 +107,7 @@ final class ContributesAndroidInjectorGenerator implements ProcessingStep { classBuilder(moduleName) .addOriginatingElement(descriptor.method()) .addAnnotation( - AnnotationSpec.builder(Module.class) + AnnotationSpec.builder(TypeNames.MODULE) .addMember("subcomponents", "$T.class", subcomponentName) .build()) .addModifiers(PUBLIC, ABSTRACT) @@ -141,25 +130,24 @@ final class ContributesAndroidInjectorGenerator implements ProcessingStep { private MethodSpec bindAndroidInjectorFactory( AndroidInjectorDescriptor descriptor, ClassName subcomponentBuilderName) { return methodBuilder("bindAndroidInjectorFactory") - .addAnnotation(Binds.class) - .addAnnotation(IntoMap.class) + .addAnnotation(TypeNames.BINDS) + .addAnnotation(TypeNames.INTO_MAP) .addAnnotation(androidInjectorMapKey(descriptor)) .addModifiers(ABSTRACT) .returns( - parameterizedTypeName( - AndroidInjector.Factory.class, - WildcardTypeName.subtypeOf(TypeName.OBJECT))) + ParameterizedTypeName.get( + TypeNames.ANDROID_INJECTOR_FACTORY, WildcardTypeName.subtypeOf(TypeName.OBJECT))) .addParameter(subcomponentBuilderName, "builder") .build(); } private AnnotationSpec androidInjectorMapKey(AndroidInjectorDescriptor descriptor) { if (useStringKeys) { - return AnnotationSpec.builder(AndroidInjectionKey.class) + return AnnotationSpec.builder(TypeNames.ANDROID_INJECTION_KEY) .addMember("value", "$S", descriptor.injectedType().toString()) .build(); } - return AnnotationSpec.builder(ClassKey.class) + return AnnotationSpec.builder(TypeNames.CLASS_KEY) .addMember("value", "$T.class", descriptor.injectedType()) .build(); } @@ -168,7 +156,7 @@ final class ContributesAndroidInjectorGenerator implements ProcessingStep { AndroidInjectorDescriptor descriptor, ClassName subcomponentName, ClassName subcomponentFactoryName) { - AnnotationSpec.Builder subcomponentAnnotation = AnnotationSpec.builder(Subcomponent.class); + AnnotationSpec.Builder subcomponentAnnotation = AnnotationSpec.builder(TypeNames.SUBCOMPONENT); for (ClassName module : descriptor.modules()) { subcomponentAnnotation.addMember("modules", "$T.class", module); } @@ -177,7 +165,8 @@ final class ContributesAndroidInjectorGenerator implements ProcessingStep { .addModifiers(PUBLIC) .addAnnotation(subcomponentAnnotation.build()) .addAnnotations(descriptor.scopes()) - .addSuperinterface(parameterizedTypeName(AndroidInjector.class, descriptor.injectedType())) + .addSuperinterface( + ParameterizedTypeName.get(TypeNames.ANDROID_INJECTOR, descriptor.injectedType())) .addType(subcomponentFactory(descriptor, subcomponentFactoryName)) .build(); } @@ -185,15 +174,11 @@ final class ContributesAndroidInjectorGenerator implements ProcessingStep { private TypeSpec subcomponentFactory( AndroidInjectorDescriptor descriptor, ClassName subcomponentFactoryName) { return interfaceBuilder(subcomponentFactoryName) - .addAnnotation(Subcomponent.Factory.class) + .addAnnotation(TypeNames.SUBCOMPONENT_FACTORY) .addModifiers(PUBLIC, STATIC) .addSuperinterface( - parameterizedTypeName(AndroidInjector.Factory.class, descriptor.injectedType())) + ParameterizedTypeName.get( + TypeNames.ANDROID_INJECTOR_FACTORY, descriptor.injectedType())) .build(); } - - private static ParameterizedTypeName parameterizedTypeName( - Class<?> clazz, TypeName... typeArguments) { - return ParameterizedTypeName.get(ClassName.get(clazz), typeArguments); - } } diff --git a/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java b/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java index a19c5efaa..bcc8e5a1e 100644 --- a/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java +++ b/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java @@ -30,8 +30,6 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Multimaps; import dagger.MapKey; -import dagger.android.AndroidInjector; -import dagger.android.DispatchingAndroidInjector; import dagger.model.Binding; import dagger.model.BindingGraph; import dagger.model.BindingKind; @@ -43,13 +41,12 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; -import javax.inject.Provider; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; /** - * Validates that the two maps that {@link DispatchingAndroidInjector} injects have logically + * Validates that the two maps that {@code DispatchingAndroidInjector} injects have logically * different keys. If a contribution exists for the same {@code FooActivity} with * {@code @ActivityKey(FooActivity.class)} and * {@code @AndroidInjectionKey("com.example.FooActivity")}, report an error. @@ -67,7 +64,7 @@ public final class DuplicateAndroidInjectorsChecker implements BindingGraphPlugi private boolean isDispatchingAndroidInjector(Binding binding) { Key key = binding.key(); - return MoreTypes.isTypeOf(DispatchingAndroidInjector.class, key.type()) + return MoreDaggerTypes.isTypeOf(TypeNames.DISPATCHING_ANDROID_INJECTOR, key.type()) && !key.qualifier().isPresent(); } @@ -118,12 +115,12 @@ public final class DuplicateAndroidInjectorsChecker implements BindingGraphPlugi requestedBinding -> { TypeMirror valueType = MoreTypes.asDeclared(requestedBinding.key().type()).getTypeArguments().get(1); - if (!MoreTypes.isTypeOf(Provider.class, valueType) + if (!MoreDaggerTypes.isTypeOf(TypeNames.PROVIDER, valueType) || !valueType.getKind().equals(TypeKind.DECLARED)) { return false; } TypeMirror providedType = MoreTypes.asDeclared(valueType).getTypeArguments().get(0); - return MoreTypes.isTypeOf(AndroidInjector.Factory.class, providedType); + return MoreDaggerTypes.isTypeOf(TypeNames.ANDROID_INJECTOR_FACTORY, providedType); }); } diff --git a/java/dagger/android/processor/MoreDaggerElements.java b/java/dagger/android/processor/MoreDaggerElements.java new file mode 100644 index 000000000..06dea38ca --- /dev/null +++ b/java/dagger/android/processor/MoreDaggerElements.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 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.android.processor; + +import com.google.auto.common.MoreElements; +import com.squareup.javapoet.ClassName; +import java.util.Optional; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +// TODO(bcorso): Dedupe with dagger/internal/codegen/langmodel/DaggerElements.java? +// TODO(bcorso): Contribute upstream to auto common? +/** Similar to auto common, but uses {@link ClassName} rather than {@link Class}. */ +final class MoreDaggerElements { + /** + * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain + * AnnotationMirror#getAnnotationType() annotation type} has the same canonical name as that of + * {@code annotationClass}. This method is a safer alternative to calling {@link + * Element#getAnnotation} and checking for {@code null} as it avoids any interaction with + * annotation proxies. + */ + public static boolean isAnnotationPresent(Element element, ClassName annotationName) { + return getAnnotationMirror(element, annotationName).isPresent(); + } + + /** + * Returns an {@link AnnotationMirror} for the annotation of type {@code annotationClass} on + * {@code element}, or {@link Optional#empty()} if no such annotation exists. This method is a + * safer alternative to calling {@link Element#getAnnotation} as it avoids any interaction with + * annotation proxies. + */ + public static Optional<AnnotationMirror> getAnnotationMirror( + Element element, ClassName annotationName) { + String annotationClassName = annotationName.canonicalName(); + for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { + TypeElement annotationTypeElement = + MoreElements.asType(annotationMirror.getAnnotationType().asElement()); + if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName)) { + return Optional.of(annotationMirror); + } + } + return Optional.empty(); + } + + private MoreDaggerElements() {} +} diff --git a/java/dagger/android/processor/MoreDaggerTypes.java b/java/dagger/android/processor/MoreDaggerTypes.java new file mode 100644 index 000000000..4bde405e1 --- /dev/null +++ b/java/dagger/android/processor/MoreDaggerTypes.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2021 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.android.processor; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.auto.common.MoreElements; +import com.squareup.javapoet.ArrayTypeName; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ErrorType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleTypeVisitor8; + +// TODO(bcorso): Dedupe with dagger/internal/codegen/langmodel/DaggerTypes.java? +// TODO(bcorso): Contribute upstream to auto common? +/** Similar to auto common, but uses {@link ClassName} rather than {@link Class}. */ +final class MoreDaggerTypes { + + /** + * Returns true if the raw type underlying the given {@link TypeMirror} represents the same raw + * type as the given {@link Class} and throws an IllegalArgumentException if the {@link + * TypeMirror} does not represent a type that can be referenced by a {@link Class} + */ + public static boolean isTypeOf(final TypeName typeName, TypeMirror type) { + checkNotNull(typeName); + return type.accept(new IsTypeOf(typeName), null); + } + + private static final class IsTypeOf extends SimpleTypeVisitor8<Boolean, Void> { + private final TypeName typeName; + + IsTypeOf(TypeName typeName) { + this.typeName = typeName; + } + + @Override + protected Boolean defaultAction(TypeMirror type, Void ignored) { + throw new IllegalArgumentException(type + " cannot be represented as a Class<?>."); + } + + @Override + public Boolean visitNoType(NoType noType, Void p) { + if (noType.getKind().equals(TypeKind.VOID)) { + return typeName.equals(TypeName.VOID); + } + throw new IllegalArgumentException(noType + " cannot be represented as a Class<?>."); + } + + @Override + public Boolean visitError(ErrorType errorType, Void p) { + return false; + } + + @Override + public Boolean visitPrimitive(PrimitiveType type, Void p) { + switch (type.getKind()) { + case BOOLEAN: + return typeName.equals(TypeName.BOOLEAN); + case BYTE: + return typeName.equals(TypeName.BYTE); + case CHAR: + return typeName.equals(TypeName.CHAR); + case DOUBLE: + return typeName.equals(TypeName.DOUBLE); + case FLOAT: + return typeName.equals(TypeName.FLOAT); + case INT: + return typeName.equals(TypeName.INT); + case LONG: + return typeName.equals(TypeName.LONG); + case SHORT: + return typeName.equals(TypeName.SHORT); + default: + throw new IllegalArgumentException(type + " cannot be represented as a Class<?>."); + } + } + + @Override + public Boolean visitArray(ArrayType array, Void p) { + return (typeName instanceof ArrayTypeName) + && isTypeOf(((ArrayTypeName) typeName).componentType, array.getComponentType()); + } + + @Override + public Boolean visitDeclared(DeclaredType type, Void ignored) { + TypeElement typeElement = MoreElements.asType(type.asElement()); + return (typeName instanceof ClassName) + && typeElement.getQualifiedName().contentEquals(((ClassName) typeName).canonicalName()); + } + } + + private MoreDaggerTypes() {} +} diff --git a/java/dagger/android/processor/TypeNames.java b/java/dagger/android/processor/TypeNames.java new file mode 100644 index 000000000..7325690ca --- /dev/null +++ b/java/dagger/android/processor/TypeNames.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 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.android.processor; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; + +// TODO(bcorso): Dedupe with dagger/internal/codegen/javapoet/TypeNames.java? +/** Common names and methods for JavaPoet {@link TypeName} and {@link ClassName} usage. */ +public final class TypeNames { + + // Core Dagger classnames + public static final ClassName BINDS = ClassName.get("dagger", "Binds"); + public static final ClassName CLASS_KEY = ClassName.get("dagger.multibindings", "ClassKey"); + public static final ClassName INTO_MAP = ClassName.get("dagger.multibindings", "IntoMap"); + public static final ClassName MAP_KEY = ClassName.get("dagger", "MapKey"); + public static final ClassName MODULE = ClassName.get("dagger", "Module"); + public static final ClassName SUBCOMPONENT = ClassName.get("dagger", "Subcomponent"); + public static final ClassName SUBCOMPONENT_FACTORY = SUBCOMPONENT.nestedClass("Factory"); + + // Dagger.android classnames + public static final ClassName ANDROID_INJECTION_KEY = + ClassName.get("dagger.android", "AndroidInjectionKey"); + public static final ClassName ANDROID_INJECTOR = + ClassName.get("dagger.android", "AndroidInjector"); + public static final ClassName DISPATCHING_ANDROID_INJECTOR = + ClassName.get("dagger.android", "DispatchingAndroidInjector"); + public static final ClassName ANDROID_INJECTOR_FACTORY = ANDROID_INJECTOR.nestedClass("Factory"); + public static final ClassName CONTRIBUTES_ANDROID_INJECTOR = + ClassName.get("dagger.android", "ContributesAndroidInjector"); + + // Other classnames + public static final ClassName PROVIDER = ClassName.get("javax.inject", "Provider"); + public static final ClassName QUALIFIER = ClassName.get("jakarta.inject", "Qualifier"); + public static final ClassName QUALIFIER_JAVAX = ClassName.get("javax.inject", "Qualifier"); + public static final ClassName SCOPE = ClassName.get("jakarta.inject", "Scope"); + public static final ClassName SCOPE_JAVAX = ClassName.get("javax.inject", "Scope"); + + private TypeNames() {} +} diff --git a/java/dagger/android/support/BUILD b/java/dagger/android/support/BUILD index 8afa703d4..80747a4b1 100644 --- a/java/dagger/android/support/BUILD +++ b/java/dagger/android/support/BUILD @@ -40,7 +40,7 @@ android_library( deps = [ "//:dagger_with_compiler", "//java/dagger/android", - "@google_bazel_common//third_party/java/error_prone:annotations", + "//third_party/java/error_prone:annotations", "@maven//:androidx_activity_activity", "@maven//:androidx_annotation_annotation", "@maven//:androidx_appcompat_appcompat", @@ -71,7 +71,7 @@ android_library( exports = [ "//:dagger_with_compiler", "//java/dagger/android:legacy-deps", - "@google_bazel_common//third_party/java/error_prone:annotations", + "//third_party/java/error_prone:annotations", "@maven//:com_android_support_appcompat_v7", "@maven//:com_android_support_support_annotations", "@maven//:com_android_support_support_fragment", diff --git a/java/dagger/errorprone/BUILD b/java/dagger/errorprone/BUILD index 9c3707f09..0bb288a65 100644 --- a/java/dagger/errorprone/BUILD +++ b/java/dagger/errorprone/BUILD @@ -10,8 +10,8 @@ java_library( srcs = glob(["*.java"]), deps = [ "//java/dagger:core", - "//java/dagger/internal/guava:collect", + "//third_party/java/error_prone:check_api", + "//third_party/java/guava/collect", "@bazel_tools//tools/jdk:langtools-neverlink", - "@google_bazel_common//third_party/java/error_prone:check_api", ], ) diff --git a/java/dagger/example/gradle/android/simple/app/build.gradle b/java/dagger/example/gradle/android/simple/app/build.gradle deleted file mode 100644 index d8f6346cb..000000000 --- a/java/dagger/example/gradle/android/simple/app/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -apply plugin: 'com.android.application' - -android { - compileSdkVersion 30 - buildToolsVersion "30.0.2" - - defaultConfig { - applicationId "dagger.example.gradle.android.simple" - minSdkVersion 15 - targetSdkVersion 30 - versionCode 1 - versionName "1.0" - } -} - -dependencies { - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'com.google.dagger:dagger:LOCAL-SNAPSHOT' - implementation 'com.google.dagger:dagger-android-support:LOCAL-SNAPSHOT' - annotationProcessor 'com.google.dagger:dagger-compiler:LOCAL-SNAPSHOT' - annotationProcessor 'com.google.dagger:dagger-android-processor:LOCAL-SNAPSHOT' - - // To help us catch usages of Guava APIs for Java 8 in the '-jre' variant. - annotationProcessor'com.google.guava:guava:28.1-android' -} diff --git a/java/dagger/example/gradle/android/simple/app/src/main/AndroidManifest.xml b/java/dagger/example/gradle/android/simple/app/src/main/AndroidManifest.xml deleted file mode 100644 index cc666d2f2..000000000 --- a/java/dagger/example/gradle/android/simple/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,30 +0,0 @@ -<!-- - ~ 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. - --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="dagger.example.gradle.android.simple"> - - <application - android:name=".SimpleApplication" - android:label="@string/appName" - android:theme="@style/Theme.AppCompat.Light"> - <activity android:name=".SimpleActivity" android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/java/dagger/example/gradle/android/simple/app/src/main/java/dagger/example/gradle/android/simple/SimpleActivity.java b/java/dagger/example/gradle/android/simple/app/src/main/java/dagger/example/gradle/android/simple/SimpleActivity.java deleted file mode 100644 index 734590afd..000000000 --- a/java/dagger/example/gradle/android/simple/app/src/main/java/dagger/example/gradle/android/simple/SimpleActivity.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2019 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.example.gradle.android.simple; - -import android.os.Bundle; -import android.util.Log; -import android.widget.TextView; -import dagger.Binds; -import dagger.Module; -import dagger.Subcomponent; -import dagger.android.AndroidInjector; -import dagger.android.support.DaggerAppCompatActivity; -import dagger.multibindings.ClassKey; -import dagger.multibindings.IntoMap; -import javax.inject.Inject; - -/** - * The main activity of the application. - * - * <p>It can be injected with any binding from both {@link SimpleActivityComponent} and {@link - * SimpleApplication.SimpleComponent}. - */ -public class SimpleActivity extends DaggerAppCompatActivity { - @Subcomponent - interface SimpleActivityComponent extends AndroidInjector<SimpleActivity> { - - @Subcomponent.Factory - interface Factory extends AndroidInjector.Factory<SimpleActivity> {} - } - - @Module(subcomponents = SimpleActivityComponent.class) - abstract static class InjectorModule { - - @Binds - @IntoMap - @ClassKey(SimpleActivity.class) - abstract AndroidInjector.Factory<?> bind(SimpleActivityComponent.Factory factory); - } - - private static final String TAG = SimpleActivity.class.getSimpleName(); - - @Inject @Model String model; - - @Inject - void logInjection() { - Log.i(TAG, "Injecting"); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_main); - - TextView greeting = (TextView) findViewById(R.id.greeting); - String text = getResources().getString(R.string.welcome, model); - greeting.setText(text); - } -} diff --git a/java/dagger/example/gradle/android/simple/app/src/main/java/dagger/example/gradle/android/simple/SimpleApplication.java b/java/dagger/example/gradle/android/simple/app/src/main/java/dagger/example/gradle/android/simple/SimpleApplication.java deleted file mode 100644 index e2e34ea50..000000000 --- a/java/dagger/example/gradle/android/simple/app/src/main/java/dagger/example/gradle/android/simple/SimpleApplication.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2019 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.example.gradle.android.simple; - -import android.util.Log; -import dagger.Component; -import dagger.android.AndroidInjectionModule; -import dagger.android.AndroidInjector; -import dagger.android.DaggerApplication; -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * A simple, skeletal application that demonstrates a dependency-injected application using the - * utilities in {@code dagger.android}. - */ -public class SimpleApplication extends DaggerApplication { - private static final String TAG = SimpleApplication.class.getSimpleName(); - - @Singleton - @Component( - modules = { - AndroidInjectionModule.class, - SimpleActivity.InjectorModule.class, - BuildModule.class - } - ) - interface SimpleComponent extends AndroidInjector<SimpleApplication> { - @Component.Factory - interface Factory extends AndroidInjector.Factory<SimpleApplication> {} - } - - @Inject - void logInjection() { - Log.i(TAG, "Injecting " + SimpleApplication.class.getSimpleName()); - } - - @Override - public void onCreate() { - super.onCreate(); - } - - @Override - protected AndroidInjector<SimpleApplication> applicationInjector() { - return DaggerSimpleApplication_SimpleComponent.factory().create(this); - } -} diff --git a/java/dagger/example/gradle/android/simple/app/src/main/res/layout/activity_main.xml b/java/dagger/example/gradle/android/simple/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 18a547bbd..000000000 --- a/java/dagger/example/gradle/android/simple/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/background_light"> - - <TextView - android:id="@+id/greeting" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_alignParentTop="true" - android:textColor="@android:color/primary_text_light" - /> -</RelativeLayout> diff --git a/java/dagger/example/gradle/android/simple/app/src/main/res/values/strings.xml b/java/dagger/example/gradle/android/simple/app/src/main/res/values/strings.xml deleted file mode 100644 index f45fd411c..000000000 --- a/java/dagger/example/gradle/android/simple/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="appName">Simple Dagger Android</string> - <string name="welcome">Hello, %s!</string> -</resources> diff --git a/java/dagger/example/gradle/android/simple/gradle.properties b/java/dagger/example/gradle/android/simple/gradle.properties deleted file mode 100644 index 2d8d1e4dd..000000000 --- a/java/dagger/example/gradle/android/simple/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -android.useAndroidX=true
\ No newline at end of file diff --git a/java/dagger/example/gradle/android/simple/settings.gradle b/java/dagger/example/gradle/android/simple/settings.gradle deleted file mode 100644 index c5a07bc20..000000000 --- a/java/dagger/example/gradle/android/simple/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -include ':app' -rootProject.name='Simple Dagger Android'
\ No newline at end of file diff --git a/java/dagger/example/spi/BUILD b/java/dagger/example/spi/BUILD index bd889c350..2cbb84ab9 100644 --- a/java/dagger/example/spi/BUILD +++ b/java/dagger/example/spi/BUILD @@ -23,12 +23,12 @@ java_plugin( name = "binding-graph-visualizer", srcs = glob(["*.java"]), deps = [ - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:graph", "//java/dagger/spi", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/error_prone:annotations", - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/auto:service", + "//third_party/java/error_prone:annotations", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/graph", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/grpc/server/BUILD b/java/dagger/grpc/server/BUILD index d9a2f9789..8aa82bee8 100644 --- a/java/dagger/grpc/server/BUILD +++ b/java/dagger/grpc/server/BUILD @@ -24,7 +24,7 @@ java_library( javacopts = DOCLINT_HTML_AND_SYNTAX, tags = ["maven_coordinates=com.google.dagger:dagger-grpc-server-annotations:" + POM_VERSION], deps = [ - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/jsr330_inject", ], ) @@ -41,15 +41,15 @@ java_library( exports = [":annotations"], deps = [ "//:dagger_with_compiler", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/grpc:context", - "@google_bazel_common//third_party/java/grpc:core", - "@google_bazel_common//third_party/java/grpc:netty", - "@google_bazel_common//third_party/java/grpc:protobuf", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/protobuf", + "//third_party/java/auto:value", + "//third_party/java/grpc:context", + "//third_party/java/grpc:core", + "//third_party/java/grpc:netty", + "//third_party/java/grpc:protobuf", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/jsr330_inject", + "//third_party/java/protobuf", ], ) diff --git a/java/dagger/grpc/server/processor/BUILD b/java/dagger/grpc/server/processor/BUILD index dd3365166..ed288363d 100644 --- a/java/dagger/grpc/server/processor/BUILD +++ b/java/dagger/grpc/server/processor/BUILD @@ -17,14 +17,14 @@ java_library( deps = [ "//:dagger_with_compiler", "//java/dagger/grpc/server:annotations", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:io", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/google_java_format", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/jsr250_annotations", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/google_java_format", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/io", + "//third_party/java/javapoet", + "//third_party/java/jsr250_annotations", ], ) diff --git a/java/dagger/hilt/BUILD b/java/dagger/hilt/BUILD index 3d12c91e7..13990a2bb 100644 --- a/java/dagger/hilt/BUILD +++ b/java/dagger/hilt/BUILD @@ -54,7 +54,7 @@ java_library( "//java/dagger/hilt/internal:generated_component", "//java/dagger/hilt/internal:preconditions", "//java/dagger/hilt/internal:test_singleton_component", - "@google_bazel_common//third_party/java/jsr305_annotations", + "//third_party/java/jsr305_annotations", ], ) @@ -96,7 +96,7 @@ java_library( name = "package_info", srcs = ["package-info.java"], deps = [ - "@google_bazel_common//third_party/java/jsr305_annotations", + "//third_party/java/jsr305_annotations", ], ) @@ -135,6 +135,7 @@ filegroup( srcs = [ "//java/dagger/hilt/android:srcs_filegroup", "//java/dagger/hilt/android/components:srcs_filegroup", + "//java/dagger/hilt/android/flags:srcs_filegroup", "//java/dagger/hilt/android/internal:srcs_filegroup", "//java/dagger/hilt/android/internal/builders:srcs_filegroup", "//java/dagger/hilt/android/internal/lifecycle:srcs_filegroup", @@ -175,6 +176,7 @@ filegroup( "//java/dagger/hilt/processor/internal/generatesrootinput:srcs_filegroup", "//java/dagger/hilt/processor/internal/originatingelement:srcs_filegroup", "//java/dagger/hilt/processor/internal/root:srcs_filegroup", + "//java/dagger/hilt/processor/internal/root/ir:srcs_filegroup", "//java/dagger/hilt/processor/internal/uninstallmodules:srcs_filegroup", ], ) diff --git a/java/dagger/hilt/android/BUILD b/java/dagger/hilt/android/BUILD index d8833e77c..1cce5ea8c 100644 --- a/java/dagger/hilt/android/BUILD +++ b/java/dagger/hilt/android/BUILD @@ -16,6 +16,7 @@ # A library based on Hilt that provides standard components and automated injection for Android. load("//:build_defs.bzl", "POM_VERSION") load("//tools:maven.bzl", "gen_maven_artifact") +load("//tools:bazel_compat.bzl", "compat_kt_android_library") package(default_visibility = ["//:src"]) @@ -31,6 +32,8 @@ android_library( exports = [ "//java/dagger/hilt:install_in", "//java/dagger/hilt/android/components", + "//java/dagger/hilt/android/flags:fragment_get_context_fix", + "//java/dagger/hilt/android/internal", "//java/dagger/hilt/android/internal/builders", "//java/dagger/hilt/android/internal/managers", "//java/dagger/hilt/android/internal/managers:component_supplier", @@ -60,7 +63,8 @@ android_library( exported_plugins = [ "//java/dagger/hilt/android/processor/internal/androidentrypoint:plugin", "//java/dagger/hilt/android/processor/internal/viewmodel:validation_plugin", - "//java/dagger/hilt/processor/internal/root:plugin", + "//java/dagger/hilt/processor/internal/root:component_tree_deps_plugin", + "//java/dagger/hilt/processor/internal/root:root_plugin", ], exports = [ ":activity_retained_lifecycle", @@ -68,6 +72,7 @@ android_library( "//java/dagger/hilt:install_in", "//java/dagger/hilt/android/components", "//java/dagger/hilt/android/internal/builders", + "//java/dagger/hilt/android/internal/legacy:aggregated_element_proxy", "//java/dagger/hilt/android/internal/managers", "//java/dagger/hilt/android/internal/managers:component_supplier", "//java/dagger/hilt/android/internal/modules", @@ -77,6 +82,7 @@ android_library( "//java/dagger/hilt/internal:generated_component", "//java/dagger/hilt/internal:generated_entry_point", "//java/dagger/hilt/internal/aggregatedroot", + "//java/dagger/hilt/internal/componenttreedeps", "//java/dagger/hilt/internal/processedrootsentinel", "//java/dagger/hilt/migration:disable_install_in_check", "@maven//:androidx_activity_activity", @@ -93,21 +99,6 @@ android_library( ) android_library( - name = "entry_point_accessors", - srcs = ["EntryPointAccessors.java"], - deps = [ - ":package_info", - "//java/dagger/hilt:entry_point", - "@google_bazel_common//third_party/java/jsr305_annotations", - "@maven//:androidx_activity_activity", - "@maven//:androidx_fragment_fragment", - "@maven//:androidx_lifecycle_lifecycle_common", - "@maven//:androidx_lifecycle_lifecycle_viewmodel", - "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", - ], -) - -android_library( name = "activity_retained_lifecycle", srcs = ["ActivityRetainedLifecycle.java"], deps = [ @@ -134,10 +125,11 @@ android_library( ":package_info", "//:dagger_with_compiler", "//java/dagger/hilt:entry_point", + "//java/dagger/hilt/android/internal", "//java/dagger/hilt/internal:component_manager", "//java/dagger/hilt/internal:preconditions", "//java/dagger/hilt/internal:test_singleton_component_manager", - "@google_bazel_common//third_party/java/jsr305_annotations", + "//third_party/java/jsr305_annotations", ], ) @@ -145,7 +137,7 @@ java_library( name = "package_info", srcs = ["package-info.java"], deps = [ - "@google_bazel_common//third_party/java/jsr305_annotations", + "//third_party/java/jsr305_annotations", ], ) @@ -159,6 +151,7 @@ android_library( ":hilt_android_app", ":package_info", "//java/dagger/hilt:artifact-core-lib", + "//java/dagger/hilt/android/migration:custom_inject", "//java/dagger/hilt/android/migration:optional_inject", "//java/dagger/lint:lint-android-artifact-lib", ], @@ -178,15 +171,19 @@ gen_maven_artifact( "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/components", "//java/dagger/hilt/android/components:package_info", + "//java/dagger/hilt/android/flags:fragment_get_context_fix", "//java/dagger/hilt/android/internal", "//java/dagger/hilt/android/internal/builders", "//java/dagger/hilt/android/internal/earlyentrypoint", + "//java/dagger/hilt/android/internal/legacy:aggregated_element_proxy", "//java/dagger/hilt/android/internal/lifecycle", "//java/dagger/hilt/android/internal/managers", "//java/dagger/hilt/android/internal/managers:component_supplier", + "//java/dagger/hilt/android/internal/migration:has_custom_inject", "//java/dagger/hilt/android/internal/migration:injected_by_hilt", "//java/dagger/hilt/android/internal/modules", "//java/dagger/hilt/android/lifecycle", + "//java/dagger/hilt/android/migration:custom_inject", "//java/dagger/hilt/android/migration:optional_inject", "//java/dagger/hilt/android/migration:package_info", "//java/dagger/hilt/android/qualifiers", @@ -198,6 +195,7 @@ gen_maven_artifact( "//java/dagger/hilt/internal:test_singleton_component_manager", "//java/dagger/hilt/internal/aggregatedroot:aggregatedroot", "//java/dagger/hilt/internal/processedrootsentinel:processedrootsentinel", + "//java/dagger/hilt/internal/componenttreedeps:componenttreedeps", ], artifact_target_maven_deps = [ "androidx.activity:activity", @@ -212,6 +210,7 @@ gen_maven_artifact( "com.google.dagger:dagger", "com.google.dagger:hilt-core", "javax.inject:javax.inject", + "org.jetbrains.kotlin:kotlin-stdlib", ], artifact_target_maven_deps_banned = [ "com.google.guava:guava", @@ -237,6 +236,20 @@ gen_maven_artifact( ], ) +compat_kt_android_library( + name = "entry_point_accessors", + srcs = ["EntryPointAccessors.kt"], + deps = [ + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt/android/internal", + "@maven//:androidx_activity_activity", + "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", + ], +) + filegroup( name = "srcs_filegroup", srcs = glob(["*"]), diff --git a/java/dagger/hilt/android/EarlyEntryPoints.java b/java/dagger/hilt/android/EarlyEntryPoints.java index e71401995..d6ebdd655 100644 --- a/java/dagger/hilt/android/EarlyEntryPoints.java +++ b/java/dagger/hilt/android/EarlyEntryPoints.java @@ -16,8 +16,10 @@ package dagger.hilt.android; +import android.app.Application; import android.content.Context; import dagger.hilt.EntryPoints; +import dagger.hilt.android.internal.Contexts; import dagger.hilt.internal.GeneratedComponentManagerHolder; import dagger.hilt.internal.Preconditions; import dagger.hilt.internal.TestSingletonComponentManager; @@ -43,13 +45,13 @@ public final class EarlyEntryPoints { // this method easier to use, since most code will use this with an Application or Context type. @Nonnull public static <T> T get(Context applicationContext, Class<T> entryPoint) { - applicationContext = applicationContext.getApplicationContext(); + Application application = Contexts.getApplication(applicationContext.getApplicationContext()); Preconditions.checkState( - applicationContext instanceof GeneratedComponentManagerHolder, + application instanceof GeneratedComponentManagerHolder, "Expected application context to implement GeneratedComponentManagerHolder. " + "Check that you're passing in an application context that uses Hilt."); Object componentManager = - ((GeneratedComponentManagerHolder) applicationContext).componentManager(); + ((GeneratedComponentManagerHolder) application).componentManager(); if (componentManager instanceof TestSingletonComponentManager) { Preconditions.checkState( hasAnnotationReflection(entryPoint, EarlyEntryPoint.class), @@ -62,7 +64,7 @@ public final class EarlyEntryPoints { // @EarlyEntryPoint only has an effect in test environment, so if this is not a test we // delegate to EntryPoints. - return EntryPoints.get(applicationContext, entryPoint); + return EntryPoints.get(application, entryPoint); } // Note: This method uses reflection but it should only be called in test environments. diff --git a/java/dagger/hilt/android/EntryPointAccessors.java b/java/dagger/hilt/android/EntryPointAccessors.java deleted file mode 100644 index 6145af1bc..000000000 --- a/java/dagger/hilt/android/EntryPointAccessors.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android; - -import android.app.Activity; -import android.content.Context; -import androidx.fragment.app.Fragment; -import android.view.View; -import dagger.hilt.EntryPoints; -import javax.annotation.Nonnull; - -/** Static utility methods for dealing with entry points for standard Android components. */ -public final class EntryPointAccessors { - - /** - * Returns the entry point interface from an application. The context can be any context derived - * from the application context. May only be used with entry point interfaces installed in the - * ApplicationComponent. - */ - @Nonnull - public static <T> T fromApplication(Context context, Class<T> entryPoint) { - return EntryPoints.get(context.getApplicationContext(), entryPoint); - } - - /** - * Returns the entry point interface from an activity. May only be used with entry point - * interfaces installed in the ActivityComponent. - */ - @Nonnull - public static <T> T fromActivity(Activity activity, Class<T> entryPoint) { - return EntryPoints.get(activity, entryPoint); - } - - /** - * Returns the entry point interface from a fragment. May only be used with entry point interfaces - * installed in the FragmentComponent. - */ - @Nonnull - public static <T> T fromFragment(Fragment fragment, Class<T> entryPoint) { - return EntryPoints.get(fragment, entryPoint); - } - - /** - * Returns the entry point interface from a view. May only be used with entry point interfaces - * installed in the ViewComponent or ViewNoFragmentComponent. - */ - @Nonnull - public static <T> T fromView(View view, Class<T> entryPoint) { - return EntryPoints.get(view, entryPoint); - } - - private EntryPointAccessors() {} -} diff --git a/java/dagger/hilt/android/EntryPointAccessors.kt b/java/dagger/hilt/android/EntryPointAccessors.kt new file mode 100644 index 000000000..8970c1e57 --- /dev/null +++ b/java/dagger/hilt/android/EntryPointAccessors.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021 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.hilt.android + +import android.app.Activity +import android.content.Context +import androidx.fragment.app.Fragment +import android.view.View +import dagger.hilt.EntryPoints +import dagger.hilt.android.internal.Contexts + +/** Utility functions for dealing with entry points for standard Android components. */ +object EntryPointAccessors { + /** + * Returns the entry point interface from an application. The context can be any context derived + * from the application context. May only be used with entry point interfaces installed in the + * SingletonComponent. + */ + @JvmStatic + fun <T> fromApplication(context: Context, entryPoint: Class<T>): T = + EntryPoints.get(Contexts.getApplication(context.applicationContext), entryPoint) + + /** + * Returns the entry point interface from an application. The context can be any context derived + * from the application context. May only be used with entry point interfaces installed in the + * SingletonComponent. + */ + inline fun <reified T> fromApplication(context: Context): T = + fromApplication(context, T::class.java) + + /** + * Returns the entry point interface from an activity. May only be used with entry point + * interfaces installed in the ActivityComponent. + */ + @JvmStatic + fun <T> fromActivity(activity: Activity, entryPoint: Class<T>): T = + EntryPoints.get(activity, entryPoint) + + /** + * Returns the entry point interface from an activity. May only be used with entry point + * interfaces installed in the ActivityComponent. + */ + inline fun <reified T> fromActivity(activity: Activity): T = + fromActivity(activity, T::class.java) + + /** + * Returns the entry point interface from a fragment. May only be used with entry point interfaces + * installed in the FragmentComponent. + */ + @JvmStatic + fun <T> fromFragment(fragment: Fragment, entryPoint: Class<T>): T = + EntryPoints.get(fragment, entryPoint) + + /** + * Returns the entry point interface from a fragment. May only be used with entry point interfaces + * installed in the FragmentComponent. + */ + inline fun <reified T> fromFragment(fragment: Fragment): T = + fromFragment(fragment, T::class.java) + + /** + * Returns the entry point interface from a view. May only be used with entry point interfaces + * installed in the ViewComponent or ViewNoFragmentComponent. + */ + @JvmStatic + fun <T> fromView(view: View, entryPoint: Class<T>): T = EntryPoints.get(view, entryPoint) + + /** + * Returns the entry point interface from a view. May only be used with entry point interfaces + * installed in the ViewComponent or ViewNoFragmentComponent. + */ + inline fun <reified T> fromView(view: View): T = + fromView(view, T::class.java) +} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/build.gradle b/java/dagger/hilt/android/example/gradle/simple/app/build.gradle deleted file mode 100644 index b3687ee63..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/build.gradle +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -apply plugin: 'com.android.application' -apply plugin: 'dagger.hilt.android.plugin' - -android { - compileSdkVersion 30 - buildToolsVersion "30.0.2" - - defaultConfig { - applicationId "dagger.hilt.android.example.gradle.simple" - minSdkVersion 15 - targetSdkVersion 30 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "dagger.hilt.android.example.gradle.simple.SimpleEmulatorTestRunner" - } - compileOptions { - sourceCompatibility 1.8 - targetCompatibility 1.8 - } - testOptions { - unitTests.includeAndroidResources = true - } - sourceSets { - String sharedTestDir = 'src/sharedTest/java' - test { - java.srcDirs += sharedTestDir - } - androidTest { - java.srcDirs += sharedTestDir - } - } -} - -dependencies { - implementation project(':feature') - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT' - annotationProcessor 'com.google.dagger:hilt-android-compiler:LOCAL-SNAPSHOT' - - testImplementation 'com.google.truth:truth:1.0.1' - testImplementation 'junit:junit:4.13' - testImplementation 'org.robolectric:robolectric:4.5-alpha-3' - testImplementation 'androidx.core:core:1.3.2' - testImplementation 'androidx.test.ext:junit:1.1.2' - testImplementation 'androidx.test:runner:1.3.0' - testImplementation 'com.google.dagger:hilt-android-testing:LOCAL-SNAPSHOT' - testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:LOCAL-SNAPSHOT' - - androidTestImplementation 'com.google.truth:truth:1.0.1' - androidTestImplementation 'junit:junit:4.13' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test:runner:1.3.0' - androidTestImplementation 'com.google.dagger:hilt-android-testing:LOCAL-SNAPSHOT' - androidTestAnnotationProcessor 'com.google.dagger:hilt-android-compiler:LOCAL-SNAPSHOT' - - // To help us catch usages of Guava APIs for Java 8 in the '-jre' variant. - annotationProcessor'com.google.guava:guava:28.1-android' - testAnnotationProcessor'com.google.guava:guava:28.1-android' - androidTestAnnotationProcessor'com.google.guava:guava:28.1-android' -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/androidTest/java/dagger/hilt/android/example/gradle/simple/SettingsActivityEmulatorTest.java b/java/dagger/hilt/android/example/gradle/simple/app/src/androidTest/java/dagger/hilt/android/example/gradle/simple/SettingsActivityEmulatorTest.java deleted file mode 100644 index 28a722302..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/androidTest/java/dagger/hilt/android/example/gradle/simple/SettingsActivityEmulatorTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import static com.google.common.truth.Truth.assertThat; - -import androidx.test.core.app.ActivityScenario; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import dagger.hilt.android.testing.BindValue; -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; -import dagger.hilt.android.testing.UninstallModules; -import javax.inject.Inject; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** A simple test using Hilt. */ -@UninstallModules(ModelModule.class) -@HiltAndroidTest -@RunWith(AndroidJUnit4.class) -public final class SettingsActivityEmulatorTest { - @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); - - @BindValue @Model String fakeModel = "FakeModel"; - - @Inject @Model String injectedModel; - - @Test - public void testInjectedModel() throws Exception { - assertThat(injectedModel).isNull(); - rule.inject(); - assertThat(injectedModel).isEqualTo("FakeModel"); - } - - @Test - public void testActivityInject() throws Exception { - try (ActivityScenario<SettingsActivity> scenario = - ActivityScenario.launch(SettingsActivity.class)) { - scenario.onActivity( - activity -> - assertThat(activity.greeter.greet()) - .isEqualTo("ProdUser, you are on build FakeModel.")); - } - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/androidTest/java/dagger/hilt/android/example/gradle/simple/SimpleActivityEmulatorTest.java b/java/dagger/hilt/android/example/gradle/simple/app/src/androidTest/java/dagger/hilt/android/example/gradle/simple/SimpleActivityEmulatorTest.java deleted file mode 100644 index cbf8f7f72..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/androidTest/java/dagger/hilt/android/example/gradle/simple/SimpleActivityEmulatorTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import static com.google.common.truth.Truth.assertThat; - -import androidx.test.core.app.ActivityScenario; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import dagger.hilt.android.testing.BindValue; -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; -import dagger.hilt.android.testing.UninstallModules; -import javax.inject.Inject; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** A simple test using Hilt. */ -@UninstallModules(UserNameModule.class) -@HiltAndroidTest -@RunWith(AndroidJUnit4.class) -public final class SimpleActivityEmulatorTest { - - @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); - - @BindValue @UserName String fakeUserName = "FakeUser"; - - @Inject @UserName String injectedUserName; - - @Test - public void testInjectedUserName() throws Exception { - assertThat(injectedUserName).isNull(); - rule.inject(); - assertThat(injectedUserName).isEqualTo("FakeUser"); - } - - @Test - public void testActivityInject() throws Exception { - try (ActivityScenario<SimpleActivity> scenario = - ActivityScenario.launch(SimpleActivity.class)) { - scenario.onActivity( - activity -> assertThat(activity.greeter.greet()).isEqualTo("Hello, FakeUser!")); - } - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/androidTest/java/dagger/hilt/android/example/gradle/simple/SimpleEmulatorTestRunner.java b/java/dagger/hilt/android/example/gradle/simple/app/src/androidTest/java/dagger/hilt/android/example/gradle/simple/SimpleEmulatorTestRunner.java deleted file mode 100644 index daf046b2e..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/androidTest/java/dagger/hilt/android/example/gradle/simple/SimpleEmulatorTestRunner.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import android.app.Application; -import android.content.Context; -import androidx.test.runner.AndroidJUnitRunner; -import dagger.hilt.android.testing.HiltTestApplication; - -/** A custom runner to setup the emulator application class for tests. */ -public final class SimpleEmulatorTestRunner extends AndroidJUnitRunner { - - @Override - public Application newApplication(ClassLoader cl, String className, Context context) - throws ClassNotFoundException, IllegalAccessException, InstantiationException { - return super.newApplication(cl, HiltTestApplication.class.getName(), context); - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/debug/AndroidManifest.xml b/java/dagger/hilt/android/example/gradle/simple/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 86460d834..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,33 +0,0 @@ -<!-- - ~ 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. - --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="dagger.hilt.android.example.gradle.simple"> - - <application> - <activity - android:name=".Injection1Test$TestActivity" - android:theme="@style/Theme.AppCompat.Light" - android:exported="false" /> - <activity - android:name=".Injection2Test$TestActivity" - android:theme="@style/Theme.AppCompat.Light" - android:exported="false"/> - <activity - android:name=".ActivityScenarioRuleTest$TestActivity" - android:theme="@style/Theme.AppCompat.Light" - android:exported="false"/> - </application> -</manifest> diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/AndroidManifest.xml b/java/dagger/hilt/android/example/gradle/simple/app/src/main/AndroidManifest.xml deleted file mode 100644 index 662c5b4c2..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,35 +0,0 @@ -<!-- - ~ 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. - --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="dagger.hilt.android.example.gradle.simple"> - - <application android:name=".SimpleApplication" android:label="@string/appName"> - <activity - android:name=".SimpleActivity" - android:theme="@style/Theme.AppCompat.Light" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - <activity - android:name=".SettingsActivity" - android:theme="@style/Theme.AppCompat.Light" - android:exported="false"> - </activity> - </application> -</manifest> diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SettingsActivity.java b/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SettingsActivity.java deleted file mode 100644 index f508e48a0..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SettingsActivity.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import android.os.Bundle; -import android.widget.TextView; -import androidx.appcompat.app.AppCompatActivity; -import dagger.hilt.android.AndroidEntryPoint; -import javax.inject.Inject; - -/** The settings activity of the application. */ -@AndroidEntryPoint -public class SettingsActivity extends AppCompatActivity { - private static final String TAG = SettingsActivity.class.getSimpleName(); - - @Inject SettingsGreeter greeter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_settings); - - ((TextView) findViewById(R.id.settings_greeting)).setText(greeter.greet()); - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SettingsGreeter.java b/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SettingsGreeter.java deleted file mode 100644 index 4f8ab14cd..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SettingsGreeter.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import android.app.Activity; -import javax.inject.Inject; - -/** A class that returns a greeting for {@link SettingsActivity}. */ -final class SettingsGreeter { - private final Activity activity; - private final String userName; - private final String model; - - @Inject - SettingsGreeter(Activity activity, @UserName String userName, @Model String model) { - this.activity = activity; - this.userName = userName; - this.model = model; - } - - public String greet() { - return activity.getResources().getString(R.string.settings_welcome, userName, model); - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SimpleActivity.java b/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SimpleActivity.java deleted file mode 100644 index 70e2e5775..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SimpleActivity.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import android.content.Intent; -import android.os.Bundle; -import android.widget.Button; -import android.widget.TextView; -import androidx.appcompat.app.AppCompatActivity; -import dagger.hilt.android.AndroidEntryPoint; -import dagger.hilt.android.example.gradle.simple.feature.FeatureActivity; -import javax.inject.Inject; - -/** The main activity of the application. */ -@AndroidEntryPoint -public class SimpleActivity extends AppCompatActivity { - private static final String TAG = SimpleActivity.class.getSimpleName(); - - @Inject SimpleGreeter greeter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_main); - - ((TextView) findViewById(R.id.greeting)).setText(greeter.greet()); - - Button featureButton = (Button) findViewById(R.id.goto_feature); - featureButton.setOnClickListener( - view -> startActivity(new Intent(this, FeatureActivity.class))); - - Button settingsButton = (Button) findViewById(R.id.goto_settings); - settingsButton.setOnClickListener( - view -> startActivity(new Intent(this, SettingsActivity.class))); - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/UserName.java b/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/UserName.java deleted file mode 100644 index 173b163b9..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/UserName.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import javax.inject.Qualifier; - -/** Qualifies bindings relating to the user name. */ -@Qualifier -@Retention(RUNTIME) -@Documented -@interface UserName {} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/UserNameModule.java b/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/UserNameModule.java deleted file mode 100644 index 3969b7335..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/UserNameModule.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import dagger.Module; -import dagger.Provides; -import dagger.hilt.InstallIn; -import dagger.hilt.android.components.ActivityComponent; - -@Module -@InstallIn(ActivityComponent.class) -final class UserNameModule { - @UserName - @Provides - static String provideUserName() { - return "ProdUser"; - } - - private UserNameModule() {} -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/res/layout/activity_main.xml b/java/dagger/hilt/android/example/gradle/simple/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 8a23af595..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/background_light"> - - <TextView - android:id="@+id/greeting" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:textColor="@android:color/primary_text_light" - /> - - <Button - android:id="@+id/goto_settings" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/greeting" - android:text="@string/navigateToSettings" - /> - - <Button - android:id="@+id/goto_feature" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/goto_settings" - android:text="@string/navigateToFeature" - /> -</RelativeLayout> diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/res/layout/activity_settings.xml b/java/dagger/hilt/android/example/gradle/simple/app/src/main/res/layout/activity_settings.xml deleted file mode 100644 index 466856c26..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/res/layout/activity_settings.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <TextView - android:id="@+id/settings_greeting" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/settings_welcome" /> -</FrameLayout> diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/res/values/strings.xml b/java/dagger/hilt/android/example/gradle/simple/app/src/main/res/values/strings.xml deleted file mode 100644 index f1b550cf9..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <!--The app name [CHAR_LIMIT=20]--> - <string name="appName">Simple Hilt Android</string> - - <!--The greeting message [CHAR_LIMIT=100]--> - <string name="welcome">Hello, %1$s!</string> - - <!--The feature button message [CHAR_LIMIT=100]--> - <string name="navigateToFeature">Navigate to a feature!</string> - - <!--The settings button message [CHAR_LIMIT=100]--> - <string name="navigateToSettings">Navigate to settings!</string> - - <!--The feature button message [CHAR_LIMIT=100]--> - <string name="settings_welcome">%1$s, you are on build %2$s.</string> -</resources> diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/ActivityScenarioRuleTest.java b/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/ActivityScenarioRuleTest.java deleted file mode 100644 index 7f1fcfcea..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/ActivityScenarioRuleTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import static androidx.lifecycle.Lifecycle.State.RESUMED; -import static com.google.common.truth.Truth.assertThat; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.test.ext.junit.rules.ActivityScenarioRule; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import dagger.hilt.android.AndroidEntryPoint; -import dagger.hilt.android.testing.BindValue; -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; -import javax.inject.Inject; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** Tests that {@link ActivityScenarioRule} works with Hilt tests. */ -@HiltAndroidTest -@RunWith(AndroidJUnit4.class) -public final class ActivityScenarioRuleTest { - private static final String STR_VALUE = "STR_VALUE"; - - /** An activity to test injection. */ - @AndroidEntryPoint - public static final class TestActivity extends AppCompatActivity { - @Inject String str; - } - - @Rule(order = 0) public HiltAndroidRule hiltRule = new HiltAndroidRule(this); - - @Rule(order = 1) - public ActivityScenarioRule<TestActivity> scenarioRule = - new ActivityScenarioRule<>(TestActivity.class); - - @BindValue String str = STR_VALUE; - - @Test - public void testState() { - assertThat(scenarioRule.getScenario().getState()).isEqualTo(RESUMED); - } - - @Test - public void testInjection() { - scenarioRule - .getScenario() - .onActivity(activity -> assertThat(activity.str).isEqualTo(STR_VALUE)); - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/BindValueTest.java b/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/BindValueTest.java deleted file mode 100644 index 7abe3c2ff..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/BindValueTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import static androidx.test.core.app.ApplicationProvider.getApplicationContext; -import static com.google.common.truth.Truth.assertThat; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.common.collect.ImmutableSet; -import dagger.MapKey; -import dagger.hilt.EntryPoint; -import dagger.hilt.EntryPoints; -import dagger.hilt.InstallIn; -import dagger.hilt.android.testing.BindElementsIntoSet; -import dagger.hilt.android.testing.BindValue; -import dagger.hilt.android.testing.BindValueIntoMap; -import dagger.hilt.android.testing.BindValueIntoSet; -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; -import dagger.hilt.components.SingletonComponent; -import java.util.Map; -import java.util.Set; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Provider; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** A simple test using Hilt. */ -@HiltAndroidTest -@RunWith(AndroidJUnit4.class) -public final class BindValueTest { - private static final String BIND_VALUE_STRING = "BIND_VALUE_STRING"; - private static final String TEST_QUALIFIER = "TEST_QUALIFIER"; - - private static final String SET_STRING_1 = "SetString1"; - private static final String SET_STRING_2 = "SetString2"; - private static final String SET_STRING_3 = "SetString3"; - - private static final String KEY_1 = "Key1"; - private static final String KEY_2 = "Key2"; - private static final String VALUE_1 = "Value1"; - private static final String VALUE_2 = "Value2"; - private static final String VALUE_3 = "Value3"; - - private static final Integer SET_INT_1 = 1; - private static final Integer SET_INT_2 = 2; - private static final Integer SET_INT_3 = 3; - - @EntryPoint - @InstallIn(SingletonComponent.class) - interface BindValueEntryPoint { - @Named(TEST_QUALIFIER) - String bindValueString(); - - Set<String> getStringSet(); - } - - @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); - - @BindValue - @Named(TEST_QUALIFIER) - String bindValueString = BIND_VALUE_STRING; - - @BindElementsIntoSet Set<String> bindElementsSet1 = ImmutableSet.of(SET_STRING_1); - @BindElementsIntoSet Set<String> bindElementsSet2 = ImmutableSet.of(SET_STRING_2); - - @BindValueIntoMap - @MyMapKey(KEY_1) - String boundValue1 = VALUE_1; - - @BindValueIntoMap - @MyMapKey(KEY_2) - String boundValue2 = VALUE_2; - - @BindValueIntoSet Integer bindValueSetInt1 = SET_INT_1; - @BindValueIntoSet Integer bindValueSetInt2 = SET_INT_2; - - @Inject Set<String> stringSet; - @Inject Provider<Set<String>> providedStringSet; - @Inject Provider<Map<String, String>> mapProvider; - @Inject Set<Integer> intSet; - @Inject Provider<Set<Integer>> providedIntSet; - - @Test - public void testBindValueFieldIsProvided() throws Exception { - assertThat(bindValueString).isEqualTo(BIND_VALUE_STRING); - assertThat(getBinding()).isEqualTo(BIND_VALUE_STRING); - } - - @Test - public void testBindValueIsMutable() throws Exception { - bindValueString = "newValue"; - assertThat(getBinding()).isEqualTo("newValue"); - } - - @Test - public void testElementsIntoSet() throws Exception { - rule.inject(); - // basic check that initial/default values are properly injected - assertThat(providedStringSet.get()).containsExactly(SET_STRING_1, SET_STRING_2); - // Test empty sets (something that cannot be done with @BindValueIntoSet) - bindElementsSet1 = ImmutableSet.of(); - bindElementsSet2 = ImmutableSet.of(); - assertThat(providedStringSet.get()).isEmpty(); - // Test multiple elements in set. - bindElementsSet1 = ImmutableSet.of(SET_STRING_1, SET_STRING_2, SET_STRING_3); - assertThat(providedStringSet.get()).containsExactly(SET_STRING_1, SET_STRING_2, SET_STRING_3); - } - - @Test - public void testBindValueIntoMap() throws Exception { - rule.inject(); - Map<String, String> oldMap = mapProvider.get(); - assertThat(oldMap).containsExactly(KEY_1, VALUE_1, KEY_2, VALUE_2); - boundValue1 = VALUE_3; - Map<String, String> newMap = mapProvider.get(); - assertThat(oldMap).containsExactly(KEY_1, VALUE_1, KEY_2, VALUE_2); - assertThat(newMap).containsExactly(KEY_1, VALUE_3, KEY_2, VALUE_2); - } - - @Test - public void testBindValueIntoSet() throws Exception { - rule.inject(); - // basic check that initial/default values are properly injected - assertThat(providedIntSet.get()).containsExactly(SET_INT_1, SET_INT_2); - bindValueSetInt1 = SET_INT_3; - // change the value for bindValueSetString from 1 to 3 - assertThat(providedIntSet.get()).containsExactly(SET_INT_2, SET_INT_3); - } - - @MapKey - @interface MyMapKey { - String value(); - } - - private static String getBinding() { - return EntryPoints.get(getApplicationContext(), BindValueEntryPoint.class).bindValueString(); - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/Injection1Test.java b/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/Injection1Test.java deleted file mode 100644 index 0ebd1506c..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/Injection1Test.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import static com.google.common.truth.Truth.assertThat; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.test.core.app.ActivityScenario; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import dagger.Module; -import dagger.Provides; -import dagger.hilt.InstallIn; -import dagger.hilt.android.AndroidEntryPoint; -import dagger.hilt.android.components.ActivityComponent; -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; -import dagger.hilt.components.SingletonComponent; -import javax.inject.Inject; -import javax.inject.Named; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** Tests basic injection APIs, and that bindings don't conflict with {@link Injection2Test}. */ -@HiltAndroidTest -@RunWith(AndroidJUnit4.class) -public final class Injection1Test { - private static final String APPLICATION_QUALIFIER = "APPLICATION_QUALIFIER"; - private static final String ACTIVITY_QUALIFIER = "ACTIVITY_QUALIFIER"; - private static final String APPLICATION_VALUE = "Injection1Test_ApplicationValue"; - private static final String ACTIVITY_VALUE = "Injection1Test_ActivityValue"; - - @Module - @InstallIn(SingletonComponent.class) - interface TestApplicationModule { - @Provides - @Named(APPLICATION_QUALIFIER) - static String provideString() { - return APPLICATION_VALUE; - } - } - - @Module - @InstallIn(ActivityComponent.class) - interface TestActivityModule { - @Provides - @Named(ACTIVITY_QUALIFIER) - static String provideString() { - return ACTIVITY_VALUE; - } - } - - /** Test activity used to test activity injection */ - @AndroidEntryPoint - public static final class TestActivity extends AppCompatActivity { - @Inject @Named(ACTIVITY_QUALIFIER) String activityValue; - } - - @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); - - @Inject @Named(APPLICATION_QUALIFIER) String applicationValue; - - @Test - public void testApplicationInjection() throws Exception { - assertThat(applicationValue).isNull(); - rule.inject(); - assertThat(applicationValue).isEqualTo(APPLICATION_VALUE); - } - - @Test - public void testActivityInjection() throws Exception { - try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { - scenario.onActivity(activity -> assertThat(activity.activityValue).isEqualTo(ACTIVITY_VALUE)); - } - } - - @Test - public void testSuperClassTransformation() { - try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { - scenario.onActivity( - activity -> - assertThat(activity.getClass().getSuperclass().getSimpleName()) - .isEqualTo("Hilt_Injection1Test_TestActivity")); - } - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/Injection2Test.java b/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/Injection2Test.java deleted file mode 100644 index 51d60b239..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/Injection2Test.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import static com.google.common.truth.Truth.assertThat; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.test.core.app.ActivityScenario; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import dagger.Module; -import dagger.Provides; -import dagger.hilt.InstallIn; -import dagger.hilt.android.AndroidEntryPoint; -import dagger.hilt.android.components.ActivityComponent; -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; -import dagger.hilt.components.SingletonComponent; -import javax.inject.Inject; -import javax.inject.Named; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** Tests basic injection APIs, and that bindings don't conflict with {@link Injection1Test}. */ -@HiltAndroidTest -@RunWith(AndroidJUnit4.class) -public final class Injection2Test { - private static final String APPLICATION_QUALIFIER = "APPLICATION_QUALIFIER"; - private static final String ACTIVITY_QUALIFIER = "ACTIVITY_QUALIFIER"; - private static final String APPLICATION_VALUE = "Injection2Test_ApplicationValue"; - private static final String ACTIVITY_VALUE = "Injection2Test_ActivityValue"; - - @Module - @InstallIn(SingletonComponent.class) - interface TestApplicationModule { - @Provides - @Named(APPLICATION_QUALIFIER) - static String provideString() { - return APPLICATION_VALUE; - } - } - - @Module - @InstallIn(ActivityComponent.class) - interface TestActivityModule { - @Provides - @Named(ACTIVITY_QUALIFIER) - static String provideString() { - return ACTIVITY_VALUE; - } - } - - /** Test activity used to test activity injection */ - @AndroidEntryPoint - public static final class TestActivity extends AppCompatActivity { - @Inject @Named(ACTIVITY_QUALIFIER) String activityValue; - } - - @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); - - @Inject @Named(APPLICATION_QUALIFIER) String applicationValue; - - @Test - public void testApplicationInjection() throws Exception { - assertThat(applicationValue).isNull(); - rule.inject(); - assertThat(applicationValue).isEqualTo(APPLICATION_VALUE); - } - - @Test - public void testActivityInjection() throws Exception { - try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { - scenario.onActivity(activity -> assertThat(activity.activityValue).isEqualTo(ACTIVITY_VALUE)); - } - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/ModuleTest.java b/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/ModuleTest.java deleted file mode 100644 index 73daf8fac..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/ModuleTest.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import static org.junit.Assert.assertEquals; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import dagger.Binds; -import dagger.Module; -import dagger.Provides; -import dagger.hilt.InstallIn; -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; -import dagger.hilt.components.SingletonComponent; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; -import javax.inject.Inject; -import javax.inject.Qualifier; -import javax.inject.Singleton; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** Tests basic functionality of using modules in test. */ -@HiltAndroidTest -@RunWith(AndroidJUnit4.class) -public final class ModuleTest { - @Rule public final HiltAndroidRule rules = new HiltAndroidRule(this); - - /** Qualifier for distinguishing test Strings, */ - @Qualifier - @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) - public @interface TestQualifier { - int value(); - } - - @Inject - @TestQualifier(1) - String testString1; - - @Inject - @TestQualifier(2) - String testString2; - - @Inject - @TestQualifier(3) - String testString3; - - @Inject - @TestQualifier(4) - String testString4; - - @Inject FooImpl fooImpl; - @Inject Foo foo; - - /** - * Module which is used to test if non-static test modules get registered in the component - * correctly. - */ - @Module - @InstallIn(SingletonComponent.class) - public final class NonStaticModuleNonStaticProvides { - @Provides - @TestQualifier(1) - String provideString() { - return "1"; - } - } - - /** - * Module which is used to test if static test modules get registered in the component correctly. - */ - @Module - @InstallIn(SingletonComponent.class) - public static final class StaticModuleStaticProvides { - @Provides - @TestQualifier(2) - static String provideString() { - return "2"; - } - - private StaticModuleStaticProvides() {} - } - - /** - * Module which is used to test if static test modules with a non-static methods get registered in - * the component correctly. - */ - @Module - @InstallIn(SingletonComponent.class) - public static final class StaticModuleNonStaticProvidesDefaultConstructor { - @Provides - @TestQualifier(3) - String provideString() { - return "3"; - } - } - - /** - * Module which is used to test if abstract test modules get registered in the component - * correctly. - */ - @Module - @InstallIn(SingletonComponent.class) - public abstract static class AbstractModuleStaticProvides { - @Provides - @TestQualifier(4) - static String provideString() { - return "4"; - } - - private AbstractModuleStaticProvides() {} - } - - /** - * Module which is used to test if abstract test modules with a binds method get registered in the - * component correctly. - */ - @Module - @InstallIn(SingletonComponent.class) - public abstract static class AbstractModuleBindsMethod { - @Binds - abstract Foo foo(FooImpl fooImpl); - } - - interface Foo {} - - @Singleton - static final class FooImpl implements Foo { - @Inject - FooImpl() {} - } - - @Test - public void testInjection() throws Exception { - rules.inject(); - assertEquals("1", testString1); - assertEquals("2", testString2); - assertEquals("3", testString3); - assertEquals("4", testString4); - assertEquals(fooImpl, foo); - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/test/java/dagger/hilt/android/example/gradle/simple/SettingsActivityTest.java b/java/dagger/hilt/android/example/gradle/simple/app/src/test/java/dagger/hilt/android/example/gradle/simple/SettingsActivityTest.java deleted file mode 100644 index f1e5f278e..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/test/java/dagger/hilt/android/example/gradle/simple/SettingsActivityTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Build; -import androidx.test.core.app.ActivityScenario; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import dagger.hilt.android.testing.BindValue; -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; -import dagger.hilt.android.testing.HiltTestApplication; -import dagger.hilt.android.testing.UninstallModules; -import javax.inject.Inject; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; - -/** A simple test using Hilt. */ -@UninstallModules(ModelModule.class) -@HiltAndroidTest -@RunWith(AndroidJUnit4.class) -// Robolectric requires Java9 to run API 29 and above, so use API 28 instead -@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) -public final class SettingsActivityTest { - @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); - - @BindValue @Model String fakeModel = "FakeModel"; - - @Inject @Model String injectedModel; - - @Test - public void testInjectedModel() throws Exception { - assertThat(injectedModel).isNull(); - rule.inject(); - assertThat(injectedModel).isEqualTo("FakeModel"); - } - - @Test - public void testActivityInject() throws Exception { - try (ActivityScenario<SettingsActivity> scenario = - ActivityScenario.launch(SettingsActivity.class)) { - scenario.onActivity( - activity -> - assertThat(activity.greeter.greet()) - .isEqualTo("ProdUser, you are on build FakeModel.")); - } - } - - @Test - public void testSuperClassTransformation() { - try (ActivityScenario<SettingsActivity> scenario = - ActivityScenario.launch(SettingsActivity.class)) { - scenario.onActivity( - activity -> - assertThat(activity.getClass().getSuperclass().getSimpleName()) - .isEqualTo("Hilt_SettingsActivity")); - } - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/test/java/dagger/hilt/android/example/gradle/simple/SimpleActivityTest.java b/java/dagger/hilt/android/example/gradle/simple/app/src/test/java/dagger/hilt/android/example/gradle/simple/SimpleActivityTest.java deleted file mode 100644 index 29b5f70ff..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/test/java/dagger/hilt/android/example/gradle/simple/SimpleActivityTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Build; -import androidx.test.core.app.ActivityScenario; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import dagger.hilt.android.testing.BindValue; -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; -import dagger.hilt.android.testing.HiltTestApplication; -import dagger.hilt.android.testing.UninstallModules; -import javax.inject.Inject; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; - -/** A simple test using Hilt. */ -@UninstallModules(UserNameModule.class) -@HiltAndroidTest -@RunWith(AndroidJUnit4.class) -// Robolectric requires Java9 to run API 29 and above, so use API 28 instead -@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) -public final class SimpleActivityTest { - @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); - - @BindValue @UserName String fakeUserName = "FakeUser"; - - @Inject @UserName String injectedUserName; - - @Test - public void testInjectedUserName() throws Exception { - assertThat(injectedUserName).isNull(); - rule.inject(); - assertThat(injectedUserName).isEqualTo("FakeUser"); - } - - @Test - public void testActivityInject() throws Exception { - try (ActivityScenario<SimpleActivity> scenario = - ActivityScenario.launch(SimpleActivity.class)) { - scenario.onActivity( - activity -> assertThat(activity.greeter.greet()).isEqualTo("Hello, FakeUser!")); - } - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/test/resources/dagger/hilt/android/example/gradle/simple/robolectric.properties b/java/dagger/hilt/android/example/gradle/simple/app/src/test/resources/dagger/hilt/android/example/gradle/simple/robolectric.properties deleted file mode 100644 index 0234ffe6f..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/test/resources/dagger/hilt/android/example/gradle/simple/robolectric.properties +++ /dev/null @@ -1,2 +0,0 @@ -sdk=28 -application=dagger.hilt.android.testing.HiltTestApplication
\ No newline at end of file diff --git a/java/dagger/hilt/android/example/gradle/simple/build.gradle b/java/dagger/hilt/android/example/gradle/simple/build.gradle deleted file mode 100644 index c0916a4d2..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -buildscript { - ext { - kotlin_version = '1.3.61' - agp_version = "4.2.0-beta04" - } - repositories { - google() - jcenter() - mavenLocal() - } - dependencies { - classpath "com.android.tools.build:gradle:$agp_version" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.google.dagger:hilt-android-gradle-plugin:LOCAL-SNAPSHOT' - } -} - -allprojects { - repositories { - google() - jcenter() - mavenCentral() - mavenLocal() - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/feature/src/main/AndroidManifest.xml b/java/dagger/hilt/android/example/gradle/simple/feature/src/main/AndroidManifest.xml deleted file mode 100644 index f7919a92f..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/feature/src/main/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="dagger.hilt.android.example.gradle.simple.feature"> - <application> - <activity - android:name=".FeatureActivity" - android:theme="@style/Theme.AppCompat.Light" - android:exported="true"/> - </application> -</manifest> diff --git a/java/dagger/hilt/android/example/gradle/simple/feature/src/main/java/dagger/hilt/android/example/gradle/simple/feature/FeatureActivity.kt b/java/dagger/hilt/android/example/gradle/simple/feature/src/main/java/dagger/hilt/android/example/gradle/simple/feature/FeatureActivity.kt deleted file mode 100644 index 03b9ae9f7..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/feature/src/main/java/dagger/hilt/android/example/gradle/simple/feature/FeatureActivity.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple.feature - -import android.os.Bundle -import android.widget.Button -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -class FeatureActivity : AppCompatActivity() { - @Inject lateinit var counter: FeatureCounter - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContentView(R.layout.activity_feature) - - findViewById<Button>(R.id.feature_button).setOnClickListener { - counter.count++ - updateCountText() - } - - updateCountText() - } - - private fun updateCountText() { - findViewById<TextView>(R.id.feature_count).text = "The count: ${counter.count}" - } -} diff --git a/java/dagger/hilt/android/example/gradle/simple/feature/src/main/java/dagger/hilt/android/example/gradle/simple/feature/FeatureModule.kt b/java/dagger/hilt/android/example/gradle/simple/feature/src/main/java/dagger/hilt/android/example/gradle/simple/feature/FeatureModule.kt deleted file mode 100644 index 84ca99a84..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/feature/src/main/java/dagger/hilt/android/example/gradle/simple/feature/FeatureModule.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simple.feature - -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityRetainedComponent -import dagger.hilt.android.scopes.ActivityRetainedScoped - -@Module -@InstallIn(ActivityRetainedComponent::class) -object FeatureModule { - @Provides - @ActivityRetainedScoped - fun provideData() = FeatureCounter(0) -} diff --git a/java/dagger/hilt/android/example/gradle/simple/feature/src/main/res/layout/activity_feature.xml b/java/dagger/hilt/android/example/gradle/simple/feature/src/main/res/layout/activity_feature.xml deleted file mode 100644 index bf524311d..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/feature/src/main/res/layout/activity_feature.xml +++ /dev/null @@ -1,38 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> - -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/background_light"> - - <TextView - android:id="@+id/feature_count" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:textColor="@android:color/primary_text_light" - /> - - <Button - android:id="@+id/feature_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@id/feature_count" - android:text="Count++" - /> -</RelativeLayout> diff --git a/java/dagger/hilt/android/example/gradle/simple/gradle.properties b/java/dagger/hilt/android/example/gradle/simple/gradle.properties deleted file mode 100644 index 646c51b97..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.useAndroidX=true -android.enableJetifier=true diff --git a/java/dagger/hilt/android/example/gradle/simple/settings.gradle b/java/dagger/hilt/android/example/gradle/simple/settings.gradle deleted file mode 100644 index 2ae0b0a70..000000000 --- a/java/dagger/hilt/android/example/gradle/simple/settings.gradle +++ /dev/null @@ -1,3 +0,0 @@ -rootProject.name='Simple Hilt Android' -include ':app' -include ':feature' diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/build.gradle b/java/dagger/hilt/android/example/gradle/simpleKotlin/app/build.gradle deleted file mode 100644 index bff479799..000000000 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/build.gradle +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'dagger.hilt.android.plugin' -apply plugin: 'kotlin-kapt' - -android { - compileSdkVersion 30 - buildToolsVersion "30.0.2" - - defaultConfig { - applicationId "dagger.hilt.android.example.gradle.simpleKotlin" - minSdkVersion 15 - targetSdkVersion 30 - versionCode 1 - versionName "1.0" - } - compileOptions { - sourceCompatibility 1.8 - targetCompatibility 1.8 - } - testOptions { - unitTests.includeAndroidResources = true - } -} - -dependencies { - implementation fileTree(dir: "libs", include: ["*.jar"]) - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.2.0' - - implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT' - kapt 'com.google.dagger:hilt-android-compiler:LOCAL-SNAPSHOT' - - testImplementation 'com.google.truth:truth:1.0.1' - testImplementation 'junit:junit:4.13' - testImplementation 'org.robolectric:robolectric:4.5-alpha-3' - testImplementation 'androidx.core:core:1.3.2' - // TODO(bcorso): This multidex dep shouldn't be required -- it's a dep for the generated code. - testImplementation 'androidx.multidex:multidex:2.0.0' - testImplementation 'com.google.dagger:hilt-android-testing:LOCAL-SNAPSHOT' - kaptTest 'com.google.dagger:hilt-android-compiler:LOCAL-SNAPSHOT' -} diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/AndroidManifest.xml b/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/AndroidManifest.xml deleted file mode 100644 index 2e274ab0b..000000000 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2020 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. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="dagger.hilt.android.example.gradle.simpleKotlin"> - - <application - android:name=".KotlinApplication" - android:allowBackup="true" - android:label="@string/app_name" - android:supportsRtl="true" - android:theme="@style/Theme.AppCompat.Light"> - <activity android:name=".MainActivity" android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/ActivityModule.kt b/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/ActivityModule.kt deleted file mode 100644 index 8a85040ab..000000000 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/ActivityModule.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simpleKotlin - -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityComponent - -@Module -@InstallIn(ActivityComponent::class) -object ActivityModule { - @UserName - @Provides - fun provideUserName(): String { - return "Android User" - } -} diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/ApplicationModule.kt b/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/ApplicationModule.kt deleted file mode 100644 index 9ee904d52..000000000 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/ApplicationModule.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simpleKotlin - -import android.os.Build -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent - -@Module -@InstallIn(SingletonComponent::class) -object ApplicationModule { - @Provides - @Model - fun provideModel(): String { - return Build.MODEL - } -} diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/MainActivity.kt b/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/MainActivity.kt deleted file mode 100644 index 564f05ab1..000000000 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/MainActivity.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simpleKotlin - -import android.os.Bundle -import android.view.View -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject - -@AndroidEntryPoint -class MainActivity : AppCompatActivity() { - // Shows that we can inject Application/Activity bindings into an activity. - @JvmField - @Model - @Inject - var model: String? = null - - @JvmField - @UserName - @Inject - var name: String? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - val greeting = findViewById<View>(R.id.greeting) as TextView - val text = resources.getString(R.string.welcome, name, model) - greeting.text = text - } -} diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/res/layout/activity_main.xml b/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 0d7637d42..000000000 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2020 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. - --> - -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/background_light"> - - <TextView - android:id="@+id/greeting" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:textColor="@android:color/primary_text_light" - /> -</RelativeLayout> diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/test/java/dagger/hilt/android/example/gradle/simpleKotlin/SimpleTest.kt b/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/test/java/dagger/hilt/android/example/gradle/simpleKotlin/SimpleTest.kt deleted file mode 100644 index d52ff716d..000000000 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/test/java/dagger/hilt/android/example/gradle/simpleKotlin/SimpleTest.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.example.gradle.simpleKotlin - -import android.os.Build -import androidx.appcompat.app.AppCompatActivity -import androidx.test.core.app.ActivityScenario -import com.google.common.truth.Truth.assertThat -import dagger.hilt.android.AndroidEntryPoint -import dagger.hilt.android.testing.HiltAndroidRule -import dagger.hilt.android.testing.HiltAndroidTest -import dagger.hilt.android.testing.HiltTestApplication -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@HiltAndroidTest -@RunWith(RobolectricTestRunner::class) -// Robolectric requires Java9 to run API 29 and above, so use API 28 instead -@Config(sdk = [Build.VERSION_CODES.P], application = HiltTestApplication::class) -class SimpleTest { - @Rule - @JvmField - var rule = HiltAndroidRule(this) - - @AndroidEntryPoint - class TestActivity : AppCompatActivity() - - @Test - fun verifyMainActivity() { - ActivityScenario.launch(MainActivity::class.java).use { scenario -> - scenario.onActivity { activity -> - assertThat(activity::class.java.getSuperclass()?.getSimpleName()) - .isEqualTo("Hilt_MainActivity") - assertThat(activity.model).isNotNull() - assertThat(activity.name).isNotNull() - } - } - } - - @Test - fun verifyTestActivity() { - ActivityScenario.launch(TestActivity::class.java).use { scenario -> - scenario.onActivity { activity -> - assertThat(activity::class.java.getSuperclass()?.getSimpleName()) - .isEqualTo("Hilt_SimpleTest_TestActivity") - } - } - } -} diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/gradle.properties b/java/dagger/hilt/android/example/gradle/simpleKotlin/gradle.properties deleted file mode 100644 index 646c51b97..000000000 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.useAndroidX=true -android.enableJetifier=true diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/gradle/wrapper/gradle-wrapper.jar b/java/dagger/hilt/android/example/gradle/simpleKotlin/gradle/wrapper/gradle-wrapper.jar Binary files differdeleted file mode 100644 index 5c2d1cf01..000000000 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/gradle/wrapper/gradle-wrapper.jar +++ /dev/null diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/gradle/wrapper/gradle-wrapper.properties b/java/dagger/hilt/android/example/gradle/simpleKotlin/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 4d9ca1649..000000000 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/gradlew b/java/dagger/hilt/android/example/gradle/simpleKotlin/gradlew deleted file mode 100755 index b0d6d0ab5..000000000 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/gradlew +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or 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. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/settings.gradle b/java/dagger/hilt/android/example/gradle/simpleKotlin/settings.gradle deleted file mode 100644 index e42f9d1ea..000000000 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name='Simple Kotlin Hilt Android' -include ':app' diff --git a/java/dagger/hilt/android/flags/BUILD b/java/dagger/hilt/android/flags/BUILD new file mode 100644 index 000000000..7bd9010a3 --- /dev/null +++ b/java/dagger/hilt/android/flags/BUILD @@ -0,0 +1,40 @@ +# Copyright (C) 2021 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. + +# Description: +# Runtime flags to control Hilt behavior for rollout of changes. These flags are usually +# meant to be temporary and so defaults may change with releases and then these flags +# may eventually be removed, just like compiler options with similar purposes. + +package(default_visibility = ["//:src"]) + +android_library( + name = "fragment_get_context_fix", + srcs = [ + "FragmentGetContextFix.java", + ], + deps = [ + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:entry_point_accessors", + "//java/dagger/hilt/android/components", + "//java/dagger/hilt/internal:preconditions", + ], +) + +filegroup( + name = "srcs_filegroup", + srcs = glob(["*"]), +) diff --git a/java/dagger/hilt/android/flags/FragmentGetContextFix.java b/java/dagger/hilt/android/flags/FragmentGetContextFix.java new file mode 100644 index 000000000..d85d39b82 --- /dev/null +++ b/java/dagger/hilt/android/flags/FragmentGetContextFix.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021 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.hilt.android.flags; + +import android.content.Context; +import dagger.Module; +import dagger.hilt.EntryPoint; +import dagger.hilt.InstallIn; +import dagger.hilt.android.EntryPointAccessors; +import dagger.hilt.components.SingletonComponent; +import dagger.hilt.internal.Preconditions; +import dagger.multibindings.Multibinds; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import java.util.Set; +import javax.inject.Qualifier; + +/** + * Runtime flag for the Fragment.getContext() fix. See https://github.com/google/dagger/pull/2620 + * for this change. Controls if fragment code should use the fixed getContext() behavior where it + * correctly returns null after a fragment is removed. This fixed behavior matches the behavior of a + * regular, non-Hilt fragment and can help catch issues where a removed or leaked fragment is + * incorrectly used. + * + * <p>In order to set the flag, bind a boolean value qualified with + * {@link DisableFragmentGetContextFix} into a set in the {@code SingletonComponent}. A set is used + * instead of an optional binding to avoid a dependency on Guava. Only one value may be bound into + * the set within a given app. If this is not set, the default is to not use the fix. Example for + * binding the value: + * + * <pre><code> + * {@literal @}Module + * {@literal @}InstallIn(SingletonComponent.class) + * public final class DisableFragmentGetContextFixModule { + * {@literal @}Provides + * {@literal @}IntoSet + * {@literal @}FragmentGetContextFix.DisableFragmentGetContextFix + * static Boolean provideDisableFragmentGetContextFix() { + * return // true or false depending on some rollout logic for your app + * } + * } + * </code></pre> + */ +public final class FragmentGetContextFix { + + /** Qualifier annotation to bind disable the Fragment.getContext() fix at runtime. */ + @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) + @Qualifier + public @interface DisableFragmentGetContextFix {} + + public static boolean isFragmentGetContextFixDisabled(Context context) { + // Use a set here instead of an optional to avoid the Guava dependency + Set<Boolean> flagSet = EntryPointAccessors.fromApplication( + context, FragmentGetContextFixEntryPoint.class).getDisableFragmentGetContextFix(); + + // TODO(b/199927963): Consider adding a plugin to check this at compile time + Preconditions.checkState(flagSet.size() <= 1, + "Cannot bind the flag @DisableFragmentGetContextFix more than once."); + + if (flagSet.isEmpty()) { + return true; + } else { + return flagSet.iterator().next(); + } + } + + /** Entry point for getting the flag. */ + @EntryPoint + @InstallIn(SingletonComponent.class) + public interface FragmentGetContextFixEntryPoint { + @DisableFragmentGetContextFix Set<Boolean> getDisableFragmentGetContextFix(); + } + + /** Declare the empty flag set. */ + @Module + @InstallIn(SingletonComponent.class) + abstract static class FragmentGetContextFixModule { + @Multibinds + @DisableFragmentGetContextFix + abstract Set<Boolean> disableFragmentGetContextFix(); + } + + private FragmentGetContextFix() { + } +} diff --git a/java/dagger/hilt/android/flags/package-info.java b/java/dagger/hilt/android/flags/package-info.java new file mode 100644 index 000000000..1a39fc29a --- /dev/null +++ b/java/dagger/hilt/android/flags/package-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 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. + */ + +/** + * Runtime flags to control Hilt behavior for rollout of changes. These flags are usually meant to + * be temporary and so defaults may change with releases and then these flags may eventually be + * removed, just like compiler options with similar purposes. + * + * @see <a href="https://dagger.dev/hilt">Hilt Developer Docs</a> + */ +@ParametersAreNonnullByDefault +package dagger.hilt.android.flags; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/java/dagger/hilt/android/internal/BUILD b/java/dagger/hilt/android/internal/BUILD index 57c84e3eb..ae8a06692 100644 --- a/java/dagger/hilt/android/internal/BUILD +++ b/java/dagger/hilt/android/internal/BUILD @@ -19,7 +19,10 @@ package(default_visibility = ["//:src"]) android_library( name = "internal", - srcs = ["ThreadUtil.java"], + srcs = [ + "Contexts.java", + "ThreadUtil.java", + ], ) filegroup( diff --git a/java/dagger/hilt/android/internal/Contexts.java b/java/dagger/hilt/android/internal/Contexts.java new file mode 100644 index 000000000..b0a6cf8a6 --- /dev/null +++ b/java/dagger/hilt/android/internal/Contexts.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 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.hilt.android.internal; + +import android.app.Application; +import android.content.Context; +import android.content.ContextWrapper; + +/** + * Utility methods for dealing with contexts. + */ +public final class Contexts { + + /** Finds the android Application from a context. */ + public static Application getApplication(Context context) { + if (context instanceof Application) { + return (Application) context; + } + + Context unwrapContext = context; + while (unwrapContext instanceof ContextWrapper) { + unwrapContext = ((ContextWrapper) unwrapContext).getBaseContext(); + if (unwrapContext instanceof Application) { + return (Application) unwrapContext; + } + } + + throw new IllegalStateException( + "Could not find an Application in the given context: " + context); + } + + private Contexts() {} +} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/Model.java b/java/dagger/hilt/android/internal/legacy/AggregatedElementProxy.java index b4aec95d4..e88de29cb 100644 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/Model.java +++ b/java/dagger/hilt/android/internal/legacy/AggregatedElementProxy.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2021 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. @@ -14,16 +14,21 @@ * limitations under the License. */ -package dagger.hilt.android.example.gradle.simple; +package dagger.hilt.android.internal.legacy; -import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.lang.annotation.RetentionPolicy.CLASS; -import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; -import javax.inject.Qualifier; +import java.lang.annotation.Target; -/** Qualifies bindings relating to {@link android.os.Build#MODEL}. */ -@Qualifier -@Retention(RUNTIME) -@Documented -@interface Model {} +/** + * Annotation for marking public proxies that reference package-private aggregating elements from + * pre-stable versions of Hilt (version < 2.35). + */ +@Target(ElementType.TYPE) +@Retention(CLASS) +public @interface AggregatedElementProxy { + /** A reference to the legacy package-private aggregating class. */ + Class<?> value(); +} diff --git a/java/dagger/hilt/android/internal/legacy/BUILD b/java/dagger/hilt/android/internal/legacy/BUILD new file mode 100644 index 000000000..0814af06d --- /dev/null +++ b/java/dagger/hilt/android/internal/legacy/BUILD @@ -0,0 +1,23 @@ +# Copyright (C) 2021 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. + +# Description: +# Internal Hilt libraries for legacy code. + +package(default_visibility = ["//:src"]) + +java_library( + name = "aggregated_element_proxy", + srcs = ["AggregatedElementProxy.java"], +) diff --git a/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java b/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java index 448847cd3..3fb4f19ab 100644 --- a/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java +++ b/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java @@ -17,12 +17,12 @@ package dagger.hilt.android.internal.lifecycle; import android.app.Application; -import androidx.lifecycle.SavedStateViewModelFactory; -import androidx.lifecycle.ViewModelProvider; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.activity.ComponentActivity; +import androidx.lifecycle.SavedStateViewModelFactory; +import androidx.lifecycle.ViewModelProvider; import androidx.savedstate.SavedStateRegistryOwner; import dagger.Module; import dagger.hilt.EntryPoint; diff --git a/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java b/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java index 443894127..4e2e0fa8b 100644 --- a/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java +++ b/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java @@ -17,13 +17,13 @@ package dagger.hilt.android.internal.lifecycle; import android.app.Activity; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.lifecycle.AbstractSavedStateViewModelFactory; import androidx.lifecycle.SavedStateHandle; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.savedstate.SavedStateRegistryOwner; import dagger.Module; import dagger.hilt.EntryPoint; diff --git a/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java b/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java index 0af3dcd11..dc8d7e052 100644 --- a/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java +++ b/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java @@ -16,19 +16,20 @@ package dagger.hilt.android.internal.managers; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; -import androidx.lifecycle.ViewModelStoreOwner; import android.content.Context; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.activity.ComponentActivity; +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelStoreOwner; import dagger.Binds; import dagger.Module; import dagger.hilt.EntryPoint; import dagger.hilt.EntryPoints; import dagger.hilt.InstallIn; import dagger.hilt.android.ActivityRetainedLifecycle; +import dagger.hilt.android.EntryPointAccessors; import dagger.hilt.android.components.ActivityRetainedComponent; import dagger.hilt.android.internal.ThreadUtil; import dagger.hilt.android.internal.builders.ActivityRetainedComponentBuilder; @@ -84,11 +85,11 @@ final class ActivityRetainedComponentManager private final Object componentLock = new Object(); ActivityRetainedComponentManager(ComponentActivity activity) { - this.viewModelProvider = getViewModelProvider(activity, activity.getApplication()); + this.viewModelProvider = getViewModelProvider(activity, activity); } private ViewModelProvider getViewModelProvider( - ViewModelStoreOwner owner, Context applicationContext) { + ViewModelStoreOwner owner, Context context) { return new ViewModelProvider( owner, new ViewModelProvider.Factory() { @@ -97,8 +98,8 @@ final class ActivityRetainedComponentManager @SuppressWarnings("unchecked") public <T extends ViewModel> T create(@NonNull Class<T> aClass) { ActivityRetainedComponent component = - EntryPoints.get( - applicationContext, ActivityRetainedComponentBuilderEntryPoint.class) + EntryPointAccessors.fromApplication( + context, ActivityRetainedComponentBuilderEntryPoint.class) .retainedComponentBuilder() .build(); return (T) new ActivityRetainedComponentViewModel(component); diff --git a/java/dagger/hilt/android/internal/managers/BUILD b/java/dagger/hilt/android/internal/managers/BUILD index 76613101e..e9831a238 100644 --- a/java/dagger/hilt/android/internal/managers/BUILD +++ b/java/dagger/hilt/android/internal/managers/BUILD @@ -39,6 +39,7 @@ android_library( "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:activity_retained_lifecycle", + "//java/dagger/hilt/android:entry_point_accessors", "//java/dagger/hilt/android/components", "//java/dagger/hilt/android/internal", "//java/dagger/hilt/android/internal/builders", diff --git a/java/dagger/hilt/android/internal/managers/BroadcastReceiverComponentManager.java b/java/dagger/hilt/android/internal/managers/BroadcastReceiverComponentManager.java index 471c31c91..bc9f9adfd 100644 --- a/java/dagger/hilt/android/internal/managers/BroadcastReceiverComponentManager.java +++ b/java/dagger/hilt/android/internal/managers/BroadcastReceiverComponentManager.java @@ -18,6 +18,7 @@ package dagger.hilt.android.internal.managers; import android.app.Application; import android.content.Context; +import dagger.hilt.android.internal.Contexts; import dagger.hilt.internal.GeneratedComponentManager; import dagger.hilt.internal.Preconditions; @@ -27,9 +28,9 @@ import dagger.hilt.internal.Preconditions; * <p>A manager for the creation of components that live in the BroadcastReceiver. */ public final class BroadcastReceiverComponentManager { - @SuppressWarnings("unchecked") + public static Object generatedComponent(Context context) { - Application application = (Application) context.getApplicationContext(); + Application application = Contexts.getApplication(context.getApplicationContext()); Preconditions.checkArgument( application instanceof GeneratedComponentManager, diff --git a/java/dagger/hilt/android/internal/managers/ViewComponentManager.java b/java/dagger/hilt/android/internal/managers/ViewComponentManager.java index 78614950a..c209f5511 100644 --- a/java/dagger/hilt/android/internal/managers/ViewComponentManager.java +++ b/java/dagger/hilt/android/internal/managers/ViewComponentManager.java @@ -16,19 +16,20 @@ package dagger.hilt.android.internal.managers; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleEventObserver; -import androidx.lifecycle.LifecycleOwner; import android.content.Context; import android.content.ContextWrapper; import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleEventObserver; +import androidx.lifecycle.LifecycleOwner; import dagger.hilt.EntryPoint; import dagger.hilt.EntryPoints; import dagger.hilt.InstallIn; import dagger.hilt.android.components.ActivityComponent; import dagger.hilt.android.components.FragmentComponent; +import dagger.hilt.android.internal.Contexts; import dagger.hilt.android.internal.builders.ViewComponentBuilder; import dagger.hilt.android.internal.builders.ViewWithFragmentComponentBuilder; import dagger.hilt.internal.GeneratedComponentManager; @@ -143,8 +144,8 @@ public final class ViewComponentManager implements GeneratedComponentManager<Obj private Context getParentContext(Class<?> parentType, boolean allowMissing) { Context context = unwrap(view.getContext(), parentType); - if (context == unwrap(context.getApplicationContext(), GeneratedComponentManager.class)) { - // If we searched for a type but ended up on the application context, just return null + if (context == Contexts.getApplication(context.getApplicationContext())) { + // If we searched for a type but ended up on the application, just return null // as this is never what we are looking for Preconditions.checkState( allowMissing, diff --git a/java/dagger/hilt/android/internal/migration/BUILD b/java/dagger/hilt/android/internal/migration/BUILD index b877f6f48..c2549fa7e 100644 --- a/java/dagger/hilt/android/internal/migration/BUILD +++ b/java/dagger/hilt/android/internal/migration/BUILD @@ -18,6 +18,11 @@ package(default_visibility = ["//:src"]) android_library( + name = "has_custom_inject", + srcs = ["HasCustomInject.java"], +) + +android_library( name = "injected_by_hilt", srcs = ["InjectedByHilt.java"], ) diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/UserName.kt b/java/dagger/hilt/android/internal/migration/HasCustomInject.java index a061ab587..ebcac9307 100644 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/UserName.kt +++ b/java/dagger/hilt/android/internal/migration/HasCustomInject.java @@ -14,11 +14,12 @@ * limitations under the License. */ -package dagger.hilt.android.example.gradle.simpleKotlin +package dagger.hilt.android.internal.migration; -import javax.inject.Qualifier - -/** Qualifies bindings relating to the user name. */ -@Qualifier -@Retention(AnnotationRetention.RUNTIME) -internal annotation class UserName +/** + * Do not use except in Hilt generated code. Internal interface for application's using + * {@code CustomInject}. + */ +public interface HasCustomInject { + void customInject(); +} diff --git a/java/dagger/hilt/android/internal/modules/ApplicationContextModule.java b/java/dagger/hilt/android/internal/modules/ApplicationContextModule.java index af4ebc250..00f6e2402 100644 --- a/java/dagger/hilt/android/internal/modules/ApplicationContextModule.java +++ b/java/dagger/hilt/android/internal/modules/ApplicationContextModule.java @@ -21,6 +21,7 @@ import android.content.Context; import dagger.Module; import dagger.Provides; import dagger.hilt.InstallIn; +import dagger.hilt.android.internal.Contexts; import dagger.hilt.android.qualifiers.ApplicationContext; import dagger.hilt.components.SingletonComponent; @@ -42,6 +43,6 @@ public final class ApplicationContextModule { @Provides Application provideApplication() { - return (Application) applicationContext.getApplicationContext(); + return Contexts.getApplication(applicationContext); } } diff --git a/java/dagger/hilt/android/internal/modules/BUILD b/java/dagger/hilt/android/internal/modules/BUILD index c1b639dda..5967743a5 100644 --- a/java/dagger/hilt/android/internal/modules/BUILD +++ b/java/dagger/hilt/android/internal/modules/BUILD @@ -24,6 +24,7 @@ android_library( "//:dagger_with_compiler", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android/components", + "//java/dagger/hilt/android/internal", "//java/dagger/hilt/android/qualifiers", "@maven//:androidx_activity_activity", "@maven//:androidx_annotation_annotation", diff --git a/java/dagger/hilt/android/internal/testing/BUILD b/java/dagger/hilt/android/internal/testing/BUILD index 98a273d12..899ab94c8 100644 --- a/java/dagger/hilt/android/internal/testing/BUILD +++ b/java/dagger/hilt/android/internal/testing/BUILD @@ -86,6 +86,7 @@ android_library( deps = [ ":test_application_component_manager", ":test_application_component_manager_holder", + "//java/dagger/hilt/android/internal", "//java/dagger/hilt/internal:component_manager", "//java/dagger/hilt/internal:preconditions", "@maven//:androidx_test_core", diff --git a/java/dagger/hilt/android/internal/testing/EarlySingletonComponentCreator.java b/java/dagger/hilt/android/internal/testing/EarlySingletonComponentCreator.java index 8e61e4aff..1dbb73ab1 100644 --- a/java/dagger/hilt/android/internal/testing/EarlySingletonComponentCreator.java +++ b/java/dagger/hilt/android/internal/testing/EarlySingletonComponentCreator.java @@ -23,6 +23,11 @@ public abstract class EarlySingletonComponentCreator { private static final String EARLY_SINGLETON_COMPONENT_CREATOR_IMPL = "dagger.hilt.android.internal.testing.EarlySingletonComponentCreatorImpl"; + private static final String ERROR_MSG = + "The EarlyComponent was requested but does not exist. Check that you have annotated " + + "your test class with @HiltAndroidTest and that the processor is running over your " + + "test."; + static Object createComponent() { try { return Class.forName(EARLY_SINGLETON_COMPONENT_CREATOR_IMPL) @@ -30,16 +35,19 @@ public abstract class EarlySingletonComponentCreator { .getDeclaredConstructor() .newInstance() .create(); - } catch (ClassNotFoundException - | NoSuchMethodException - | IllegalAccessException - | InstantiationException - | InvocationTargetException e) { - throw new RuntimeException( - "The EarlyComponent was requested but does not exist. Check that you have annotated " - + "your test class with @HiltAndroidTest and that the processor is running over your " - + "test.", - e); + // We catch each individual exception rather than using a multicatch because multi-catch will + // get compiled to the common but new super type ReflectiveOperationException, which is not + // allowed on API < 19. See b/187826710. + } catch (ClassNotFoundException e) { + throw new RuntimeException(ERROR_MSG, e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(ERROR_MSG, e); + } catch (IllegalAccessException e) { + throw new RuntimeException(ERROR_MSG, e); + } catch (InstantiationException e) { + throw new RuntimeException(ERROR_MSG, e); + } catch (InvocationTargetException e) { + throw new RuntimeException(ERROR_MSG, e); } } diff --git a/java/dagger/hilt/android/internal/testing/MarkThatRulesRanRule.java b/java/dagger/hilt/android/internal/testing/MarkThatRulesRanRule.java index 17c40b308..192d30ae3 100644 --- a/java/dagger/hilt/android/internal/testing/MarkThatRulesRanRule.java +++ b/java/dagger/hilt/android/internal/testing/MarkThatRulesRanRule.java @@ -19,8 +19,9 @@ package dagger.hilt.android.internal.testing; import static dagger.hilt.internal.Preconditions.checkNotNull; import static dagger.hilt.internal.Preconditions.checkState; -import android.content.Context; +import android.app.Application; import androidx.test.core.app.ApplicationProvider; +import dagger.hilt.android.internal.Contexts; import dagger.hilt.internal.GeneratedComponentManager; import java.lang.annotation.Annotation; import java.util.concurrent.atomic.AtomicBoolean; @@ -38,7 +39,8 @@ public final class MarkThatRulesRanRule implements TestRule { private static final String HILT_ANDROID_APP = "dagger.hilt.android.HiltAndroidApp"; private static final String HILT_ANDROID_TEST = "dagger.hilt.android.testing.HiltAndroidTest"; - private final Context context = ApplicationProvider.getApplicationContext(); + private final Application application = Contexts.getApplication( + ApplicationProvider.getApplicationContext()); private final Object testInstance; private final boolean autoAddModule; @@ -52,19 +54,19 @@ public final class MarkThatRulesRanRule implements TestRule { "Expected %s to be annotated with @HiltAndroidTest.", testInstance.getClass().getName()); checkState( - context instanceof GeneratedComponentManager, + application instanceof GeneratedComponentManager, "Hilt test, %s, must use a Hilt test application but found %s. To fix, configure the test " + "to use HiltTestApplication or a custom Hilt test application generated with " + "@CustomTestApplication.", testInstance.getClass().getName(), - context.getClass().getName()); + application.getClass().getName()); checkState( - !hasAnnotation(context, HILT_ANDROID_APP), + !hasAnnotation(application, HILT_ANDROID_APP), "Hilt test, %s, cannot use a @HiltAndroidApp application but found %s. To fix, configure " + "the test to use HiltTestApplication or a custom Hilt test application generated " + "with @CustomTestApplication.", testInstance.getClass().getName(), - context.getClass().getName()); + application.getClass().getName()); } public void delayComponentReady() { @@ -114,10 +116,11 @@ public final class MarkThatRulesRanRule implements TestRule { private TestApplicationComponentManager getTestApplicationComponentManager() { checkState( - context instanceof TestApplicationComponentManagerHolder, - "The context is not an instance of TestApplicationComponentManagerHolder: %s", - context); - Object componentManager = ((TestApplicationComponentManagerHolder) context).componentManager(); + application instanceof TestApplicationComponentManagerHolder, + "The application is not an instance of TestApplicationComponentManagerHolder: %s", + application); + Object componentManager = + ((TestApplicationComponentManagerHolder) application).componentManager(); checkState( componentManager instanceof TestApplicationComponentManager, "Expected TestApplicationComponentManagerHolder to return an instance of" diff --git a/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java b/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java index c2051170d..9d08db72d 100644 --- a/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java +++ b/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java @@ -95,7 +95,7 @@ public final class TestApplicationComponentManager if (component.get() == null) { Preconditions.checkState( hasHiltTestRule(), - "The component was not created. Check that you have added the HiltAndroidRule."); + "The component was not created. Check that you have added the HiltAndroidRule."); if (!registeredModules.keySet().containsAll(requiredModules())) { Set<Class<?>> difference = new HashSet<>(requiredModules()); difference.removeAll(registeredModules.keySet()); @@ -284,8 +284,8 @@ public final class TestApplicationComponentManager void setAutoAddModule(boolean autoAddModule) { Preconditions.checkState( - autoAddModuleEnabled.get() == null, "autoAddModuleEnabled is already set!"); - autoAddModuleEnabled.set(autoAddModule); + autoAddModuleEnabled.compareAndSet(null, autoAddModule), + "autoAddModuleEnabled is already set!"); } private Set<Class<?>> requiredModules() { @@ -318,9 +318,7 @@ public final class TestApplicationComponentManager } private Class<?> testClass() { - Preconditions.checkState( - hasHiltTestRule(), - "Test must have an HiltAndroidRule."); + Preconditions.checkState(hasHiltTestRule(), "Test must have a HiltAndroidRule."); return hasHiltTestRule.get().getTestClass(); } diff --git a/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java b/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java index b6f8b0b45..b1234ddb0 100644 --- a/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java +++ b/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java @@ -32,22 +32,31 @@ public abstract class TestComponentDataSupplier { .getDeclaredConstructor() .newInstance() .get(); - } catch (ClassNotFoundException - | NoSuchMethodException - | IllegalAccessException - | InstantiationException - | InvocationTargetException e) { - throw new RuntimeException( - String.format( - "Hilt test, %s, is missing generated file: %s. Check that the test class is " - + " annotated with @HiltAndroidTest and that the processor is running over your" - + " test.", - testClass.getSimpleName(), - generatedClassName), - e); + // We catch each individual exception rather than using a multicatch because multi-catch will + // get compiled to the common but new super type ReflectiveOperationException, which is not + // allowed on API < 19. See b/187826710. + } catch (ClassNotFoundException e) { + throw new RuntimeException(errorMessage(testClass, generatedClassName), e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(errorMessage(testClass, generatedClassName), e); + } catch (IllegalAccessException e) { + throw new RuntimeException(errorMessage(testClass, generatedClassName), e); + } catch (InstantiationException e) { + throw new RuntimeException(errorMessage(testClass, generatedClassName), e); + } catch (InvocationTargetException e) { + throw new RuntimeException(errorMessage(testClass, generatedClassName), e); } } + private static String errorMessage(Class<?> testClass, String generatedClassName) { + return String.format( + "Hilt test, %s, is missing generated file: %s. Check that the test class is " + + " annotated with @HiltAndroidTest and that the processor is running over your" + + " test.", + testClass.getSimpleName(), + generatedClassName); + } + private static String getEnclosedClassName(Class<?> testClass) { StringBuilder sb = new StringBuilder(); Class<?> currClass = testClass; diff --git a/java/dagger/hilt/android/lifecycle/proguard-rules.pro b/java/dagger/hilt/android/lifecycle/proguard-rules.pro index edd3d3d79..6c647f105 100644 --- a/java/dagger/hilt/android/lifecycle/proguard-rules.pro +++ b/java/dagger/hilt/android/lifecycle/proguard-rules.pro @@ -1,2 +1,2 @@ # Keep class names of Hilt injected ViewModels since their name are used as a multibinding map key. --keepnames @dagger.hilt.android.lifecycle.HiltViewModel class * extends androidx.lifecycle.ViewModel +-keepnames @dagger.hilt.android.lifecycle.HiltViewModel class * extends androidx.lifecycle.ViewModel
\ No newline at end of file diff --git a/java/dagger/hilt/android/migration/BUILD b/java/dagger/hilt/android/migration/BUILD index a42f0ac65..3694560b6 100644 --- a/java/dagger/hilt/android/migration/BUILD +++ b/java/dagger/hilt/android/migration/BUILD @@ -39,11 +39,28 @@ android_library( ], ) +android_library( + name = "custom_inject", + srcs = [ + "CustomInject.java", + "CustomInjection.java", + ], + exports = [ + "//java/dagger/hilt/android/internal/migration:has_custom_inject", + ], + deps = [ + ":package_info", + "//java/dagger/hilt/android/internal/migration:has_custom_inject", + "//java/dagger/hilt/internal:preconditions", + "@maven//:androidx_annotation_annotation", + ], +) + java_library( name = "package_info", srcs = ["package-info.java"], deps = [ - "@google_bazel_common//third_party/java/jsr305_annotations", + "//third_party/java/jsr305_annotations", ], ) diff --git a/java/dagger/hilt/android/migration/CustomInject.java b/java/dagger/hilt/android/migration/CustomInject.java new file mode 100644 index 000000000..e142609bb --- /dev/null +++ b/java/dagger/hilt/android/migration/CustomInject.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 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.hilt.android.migration; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * When used on a {@link dagger.hilt.android.HiltAndroidApp}-annotated application, this causes the + * application to no longer inject itself in onCreate and instead allows it to be injected at some + * other time. + * + * <p>When using this annotation, you can use {@link CustomInjection#inject} to inject the + * application class. Additionally, this annotation will also cause a method, {@code customInject} + * to be generated in the Hilt base class as well, that behaves the same as + * {@link CustomInjection#inject}. The method is available to users that extend the Hilt base class + * directly and don't use the Gradle plugin. + * + * <p> Example usage: + * + * <pre><code> + * {@literal @}CustomInject + * {@literal @}HiltAndroidApp(Application.class) + * public final class MyApplication extends Hilt_MyApplication { + * + * {@literal @}Inject Foo foo; + * + * {@literal @}Override + * public void onCreate() { + * // Injection would normally happen in this super.onCreate() call, but won't now because this + * // is using CustomInject. + * super.onCreate(); + * doSomethingBeforeInjection(); + * // This call now injects the fields in the Application, like the foo field above. + * CustomInject.inject(this); + * } + * } + * </code></pre> + */ +@Target(ElementType.TYPE) +public @interface CustomInject {} diff --git a/java/dagger/hilt/android/migration/CustomInjection.java b/java/dagger/hilt/android/migration/CustomInjection.java new file mode 100644 index 000000000..e3d7a0c7d --- /dev/null +++ b/java/dagger/hilt/android/migration/CustomInjection.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 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.hilt.android.migration; + +import android.app.Application; +import androidx.annotation.NonNull; +import dagger.hilt.android.internal.migration.HasCustomInject; +import dagger.hilt.internal.Preconditions; + +/** + * Utility methods for injecting the application when using {@link CustomInject}. + * + * @see OptionalInject + */ +public final class CustomInjection { + + /** Injects the passed in application. */ + public static void inject(@NonNull Application app) { + Preconditions.checkNotNull(app); + Preconditions.checkArgument( + app instanceof HasCustomInject, + "'%s' is not a custom inject application. Check that you have annotated" + + " the application with both @HiltAndroidApp and @CustomInject.", + app.getClass()); + ((HasCustomInject) app).customInject(); + } + + private CustomInjection() {} +} diff --git a/java/dagger/hilt/android/migration/OptionalInjectCheck.java b/java/dagger/hilt/android/migration/OptionalInjectCheck.java index f9d0ad8f2..6c6d30bfd 100644 --- a/java/dagger/hilt/android/migration/OptionalInjectCheck.java +++ b/java/dagger/hilt/android/migration/OptionalInjectCheck.java @@ -18,10 +18,10 @@ package dagger.hilt.android.migration; import android.app.Service; import android.content.BroadcastReceiver; -import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import android.view.View; import androidx.activity.ComponentActivity; +import androidx.annotation.NonNull; import dagger.hilt.android.internal.migration.InjectedByHilt; import dagger.hilt.internal.Preconditions; diff --git a/java/dagger/hilt/android/plugin/BUILD b/java/dagger/hilt/android/plugin/BUILD index 7fd2c8377..ef68b63d2 100644 --- a/java/dagger/hilt/android/plugin/BUILD +++ b/java/dagger/hilt/android/plugin/BUILD @@ -17,6 +17,14 @@ package(default_visibility = ["//:src"]) +genrule( + name = "import-shared-lib", + srcs = ["//java/dagger/hilt/processor/internal/root/ir"], + outs = ["processor-shared-lib.jar"], + cmd = "find $(SRCS) -type f -name '*.jar' -exec cp {} $@ \\;", + local = True, +) + filegroup( name = "srcs_filegroup", srcs = glob( diff --git a/java/dagger/hilt/android/plugin/build.gradle b/java/dagger/hilt/android/plugin/build.gradle index 480c6f0a4..c481c41ac 100644 --- a/java/dagger/hilt/android/plugin/build.gradle +++ b/java/dagger/hilt/android/plugin/build.gradle @@ -15,45 +15,70 @@ */ buildscript { + ext { + kotlin_version = "1.5.32" + agp_version = System.getenv('AGP_VERSION') ?: "7.2.0-alpha06" + pluginArtifactId = 'hilt-android-gradle-plugin' + pluginId = 'com.google.dagger.hilt.android' + } repositories { google() mavenCentral() - jcenter() } } plugins { - id 'org.jetbrains.kotlin.jvm' version '1.4.20' + id 'org.jetbrains.kotlin.jvm' version "$kotlin_version" id 'java-gradle-plugin' id 'maven-publish' + // Use shadow version >= 7.1.1 to get log4j vulnerability patches: + // https://github.com/johnrengelman/shadow/releases/tag/7.1.1 + id 'com.github.johnrengelman.shadow' version '7.1.1' } repositories { google() mavenCentral() - jcenter() } configurations { - additionalTestPlugin { + // Config for dependencies that will be shadowed / jarjared + shadowed + // Make all shadowed dependencies be compileOnly dependencies to not affect + // main compilation / configuration + compileOnly.extendsFrom(shadowed) + // Make all shadowed dependencies be included in the plugin test classpath + // since they are compileOnly in the main configuration + testPluginCompile.extendsFrom(shadowed) + // Config for plugin classpath to be used during tests + testPluginCompile { canBeConsumed = false canBeResolved = true - extendsFrom implementation } } +// Renames default jar to avoid using it in publications. +jar { + archiveClassifier = "before-jarjar" +} +shadowJar { + archiveClassifier = "" + configurations = [project.configurations.shadowed] +} + dependencies { + shadowed fileTree(dir: 'libs', include: '*.jar') implementation gradleApi() - compileOnly 'com.android.tools.build:gradle:4.2.0-beta04' - // TODO(danysantiago): Make compileOnly to avoid dep for non-Kotlin projects. - implementation 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.20' + compileOnly "com.android.tools.build:gradle:$agp_version" + compileOnly "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" implementation 'org.javassist:javassist:3.26.0-GA' implementation 'org.ow2.asm:asm:9.0' + implementation "com.squareup:javapoet:1.13.0" testImplementation gradleTestKit() testImplementation 'junit:junit:4.12' testImplementation 'com.google.truth:truth:1.0.1' - additionalTestPlugin 'com.android.tools.build:gradle:4.2.0-beta04' + testPluginCompile 'com.android.tools.build:gradle:4.2.0' } // Configure the generating task of plugin-under-test-metadata.properties to @@ -61,21 +86,91 @@ dependencies { // are not present in the main runtime dependencies. This allows us to test // the desired AGP version while keeping a compileOnly dep on the main source. tasks.withType(PluginUnderTestMetadata.class).named("pluginUnderTestMetadata").configure { - it.pluginClasspath.from(configurations.additionalTestPlugin) + it.pluginClasspath.from(configurations.testPluginCompile) } compileKotlin { kotlinOptions { jvmTarget = "1.8" + allWarningsAsErrors = true + } +} + +java { + sourceCompatibility = "1.8" + targetCompatibility = "1.8" +} + +// Imports a shared library from the main project. The library and its classes +// will be shadowed in the plugin's artifact. +tasks.register("importSharedLib").configure { + def outputDir = file("${project.projectDir}/libs") + outputs.dir(outputDir) + doLast { + def buildCmd = 'bazel' + def buildDir = 'bazel-bin' + def findGenFilesParent + findGenFilesParent = { File dir -> + if (dir == null || !dir.isDirectory()) { + return null + } + if (new File(dir, buildDir).exists()) { + return dir + } else { + return findGenFilesParent(dir.parentFile) + } + } + // Build shared lib + def bazelOutput = new ByteArrayOutputStream() + def buildResult = exec { + commandLine buildCmd, 'build', 'import-shared-lib' + standardOutput = bazelOutput + errorOutput = bazelOutput + } + buildResult.assertNormalExitValue() + // Find shared lib Jar in build directory. + def genFilesDir = findGenFilesParent(project.buildFile.parentFile) + if (genFilesDir == null) { + throw new GradleException("Couldn't find build folder '$buildDir'") + } + def libPath = bazelOutput.toString().split('\n') + .find { line -> line.contains("$buildDir/") }.trim() + def inputFile = file("$genFilesDir/$libPath") + def outputFile = file("$outputDir/${inputFile.name}") + outputFile << inputFile.newInputStream() + } +} +tasks.getByName('compileKotlin').dependsOn('importSharedLib') + +// Task that generates a top-level property containing the version of the +// project so that it can be used in code and at runtime. +def pluginVersionOutDir = file("$buildDir/generated/source/plugin-version/") +tasks.register("generatePluginVersionSource").configure { + def version = getPublishVersion() + inputs.property('version', version) + outputs.dir(pluginVersionOutDir) + doLast { + def versionFile = + file("$pluginVersionOutDir/dagger/hilt/android/plugin/Version.kt") + versionFile.parentFile.mkdirs() + versionFile.text = """ + // Generated file. Do not edit! + package dagger.hilt.android.plugin + + val HILT_VERSION = "${version}" + """.stripIndent() } } +sourceSets.main.java.srcDir pluginVersionOutDir +tasks.getByName('compileKotlin').dependsOn('generatePluginVersionSource') // Create sources Jar from main kotlin sources tasks.register("sourcesJar", Jar).configure { group = JavaBasePlugin.DOCUMENTATION_GROUP description = "Assembles sources JAR" - classifier = "sources" + archiveClassifier.set("sources") from(sourceSets["main"].allSource) + dependsOn('generatePluginVersionSource') } // Create javadoc Jar. The jar is empty since we don't really have docs @@ -84,7 +179,7 @@ tasks.register("sourcesJar", Jar).configure { tasks.register("javadocJar", Jar).configure { group = JavaBasePlugin.DOCUMENTATION_GROUP description = "Assembles javadoc JAR" - classifier = "javadoc" + archiveClassifier.set("javadoc") } // Disable Gradle metadata publication. @@ -96,53 +191,29 @@ tasks.withType(GenerateModuleMetadata) { publishing { publications { plugin(MavenPublication) { - artifactId = 'hilt-android-gradle-plugin' - def publishVersion = findProperty("PublishVersion") - version = (publishVersion != null) ? publishVersion : "LOCAL-SNAPSHOT" + artifactId = pluginArtifactId + version = getPublishVersion() from components.kotlin + artifact(shadowJar) artifact(sourcesJar) artifact(javadocJar) pom { - name = 'Hilt Android Gradle Plugin' - description = 'A fast dependency injector for Android and Java.' - url = 'https://github.com/google/dagger' - scm { - url = 'https://github.com/google/dagger/' - connection = 'scm:git:git://github.com/google/dagger.git' - developerConnection = 'scm:git:ssh://git@github.com/google/dagger.git' - tag = 'HEAD' - } - issueManagement { - system = 'GitHub Issues' - url = 'https://github.com/google/dagger/issues' - } - licenses { - license { - name = 'Apache 2.0' - url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - organization { - name = 'Google, Inc.' - url = 'https://www.google.com' - } + addPomTemplate(owner) + } + } + // https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers + pluginMarker(MavenPublication) { + groupId = pluginId + artifactId = "${pluginId}.gradle.plugin" + version = getPublishVersion() + pom { + addPomTemplate(owner) withXml { - def projectNode = asNode() - // Adds: - // <parent> - // <groupId>org.sonatype.oss</groupId> - // <artifactId>oss-parent</artifactId> - // <version>7</version> - // </parent> - def parentNode = projectNode.appendNode('parent') - parentNode.appendNode('groupId', 'org.sonatype.oss') - parentNode.appendNode('artifactId', 'oss-parent') - parentNode.appendNode('version', '7') - // Adds scm->tag because for some reason the DSL API does not. - // <scm> - // <tag>HEAD</tag> - // </scm> - projectNode.get('scm').first().appendNode('tag', 'HEAD') + def dependencyNode = + asNode().appendNode("dependencies").appendNode("dependency") + dependencyNode.appendNode("groupId", group) + dependencyNode.appendNode("artifactId", pluginArtifactId) + dependencyNode.appendNode("version", getPublishVersion()) } } } @@ -155,4 +226,54 @@ publishing { } } -group='com.google.dagger' +group = 'com.google.dagger' + +// TODO(danysantiago): Use POM template in tools/ to avoid duplicating lines. +def addPomTemplate(pom) { + pom.name = 'Hilt Android Gradle Plugin' + pom.description = 'A fast dependency injector for Android and Java.' + pom.url = 'https://github.com/google/dagger' + pom.scm { + url = 'https://github.com/google/dagger/' + connection = 'scm:git:git://github.com/google/dagger.git' + developerConnection = 'scm:git:ssh://git@github.com/google/dagger.git' + tag = 'HEAD' + } + pom.issueManagement { + system = 'GitHub Issues' + url = 'https://github.com/google/dagger/issues' + } + pom.licenses { + license { + name = 'Apache 2.0' + url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + pom.organization { + name = 'Google, Inc.' + url = 'https://www.google.com' + } + pom.withXml { + def projectNode = asNode() + // Adds: + // <parent> + // <groupId>org.sonatype.oss</groupId> + // <artifactId>oss-parent</artifactId> + // <version>7</version> + // </parent> + def parentNode = projectNode.appendNode('parent') + parentNode.appendNode('groupId', 'org.sonatype.oss') + parentNode.appendNode('artifactId', 'oss-parent') + parentNode.appendNode('version', '7') + // Adds scm->tag because for some reason the DSL API does not. + // <scm> + // <tag>HEAD</tag> + // </scm> + projectNode.get('scm').first().appendNode('tag', 'HEAD') + } +} + +def getPublishVersion() { + def publishVersion = findProperty("PublishVersion") + return (publishVersion != null) ? publishVersion : "LOCAL-SNAPSHOT" +} diff --git a/java/dagger/hilt/android/plugin/gradle/wrapper/gradle-wrapper.properties b/java/dagger/hilt/android/plugin/gradle/wrapper/gradle-wrapper.properties index 4d9ca1649..ffed3a254 100644 --- a/java/dagger/hilt/android/plugin/gradle/wrapper/gradle-wrapper.properties +++ b/java/dagger/hilt/android/plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt index 3bbf1e190..e9f667f3c 100644 --- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt @@ -189,6 +189,12 @@ internal class AndroidEntryPointClassTransformer( if (currentClassRef != oldSuperclassName) { return@forEachInstruction } + // If the method reference of the instruction is a constructor, then we should not + // rewrite it since its an instantiation and not a `super()` call. + val methodRefName = constantPool.getMethodrefName(methodRef) + if (methodRefName == "<init>") { + return@forEachInstruction + } val nameAndTypeRef = constantPool.getMethodrefNameAndType(methodRef) val newSuperclassRef = constantPool.addClassInfo(newSuperclassName) val newMethodRef = constantPool.addMethodrefInfo(newSuperclassRef, nameAndTypeRef) diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt index 015bb7623..fec331817 100644 --- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt @@ -6,7 +6,7 @@ import com.android.build.api.instrumentation.ClassData import com.android.build.api.instrumentation.InstrumentationParameters import java.io.File import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.FieldVisitor @@ -17,18 +17,19 @@ import org.objectweb.asm.Opcodes * ASM Adapter that transforms @AndroidEntryPoint-annotated classes to extend the Hilt * generated android class, including the @HiltAndroidApp application class. */ -@Suppress("UnstableApiUsage") class AndroidEntryPointClassVisitor( private val apiVersion: Int, nextClassVisitor: ClassVisitor, private val additionalClasses: File ) : ClassVisitor(apiVersion, nextClassVisitor) { + @Suppress("UnstableApiUsage") // ASM Pipeline APIs interface AndroidEntryPointParams : InstrumentationParameters { - @get:Input + @get:Internal val additionalClassesDir: Property<File> } + @Suppress("UnstableApiUsage") // ASM Pipeline APIs abstract class Factory : AsmClassVisitorFactory<AndroidEntryPointParams> { override fun createClassVisitor( classContext: ClassContext, @@ -69,7 +70,8 @@ class AndroidEntryPointClassVisitor( newSuperclassName = packageName + "/Hilt_" + className.replace("$", "_") oldSuperclassName = superName ?: error { "Superclass of $name is null!" } - super.visit(version, access, name, signature, newSuperclassName, interfaces) + val newSignature = signature?.replaceFirst(oldSuperclassName, newSuperclassName) + super.visit(version, access, name, newSignature, newSuperclassName, interfaces) } override fun visitMethod( @@ -80,7 +82,11 @@ class AndroidEntryPointClassVisitor( exceptions: Array<out String>? ): MethodVisitor { val nextMethodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions) - val invokeSpecialVisitor = InvokeSpecialAdapter(apiVersion, nextMethodVisitor) + val invokeSpecialVisitor = InvokeSpecialAdapter( + apiVersion = apiVersion, + nextClassVisitor = nextMethodVisitor, + isConstructor = name == "<init>" + ) if (name == ON_RECEIVE_METHOD_NAME && descriptor == ON_RECEIVE_METHOD_DESCRIPTOR && hasOnReceiveBytecodeInjectionMarker() @@ -104,16 +110,22 @@ class AndroidEntryPointClassVisitor( * However, it has been observed that on APIs 19 to 22 the Android Runtime (ART) jumps over the * direct superclass and into the method reference class, causing unexpected behaviours. * Therefore, this method performs the additional transformation to rewrite direct super call - * invocations to use a method reference whose class in the pool is the new superclass. Note that - * this is not necessary for constructor calls since the Javassist library takes care of those. + * invocations to use a method reference whose class in the pool is the new superclass. * * @see: https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html#jvms-6.5.invokespecial * @see: https://source.android.com/devices/tech/dalvik/dalvik-bytecode */ inner class InvokeSpecialAdapter( apiVersion: Int, - nextClassVisitor: MethodVisitor + nextClassVisitor: MethodVisitor, + private val isConstructor: Boolean ) : MethodVisitor(apiVersion, nextClassVisitor) { + + // Flag to know that we have visited the first invokespecial instruction in a constructor call + // which corresponds to the `super()` constructor call required as the first statement of an + // overridden constructor body. + private var visitedSuperConstructorInvokeSpecial = false + override fun visitMethodInsn( opcode: Int, owner: String, @@ -122,13 +134,29 @@ class AndroidEntryPointClassVisitor( isInterface: Boolean ) { if (opcode == Opcodes.INVOKESPECIAL && owner == oldSuperclassName) { - // Update the owner of all INVOKESPECIAL instructions, including those found in - // constructors. - super.visitMethodInsn(opcode, newSuperclassName, name, descriptor, isInterface) + // Update the owner of INVOKESPECIAL instructions, including those found in constructors. + super.visitMethodInsn(opcode, getAdaptedOwner(name) ?: owner, name, descriptor, isInterface) } else { super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) } } + + // Gets the updated owner of an INVOKESPECIAL found in the method being visited. + private fun getAdaptedOwner(methodRefName: String): String? { + // If the method reference is a constructor and we are visiting a constructor then only the + // first INVOKESPECIAL instruction found should be transformed since that correponds to the + // super constructor call. + if (methodRefName == "<init>" && isConstructor && !visitedSuperConstructorInvokeSpecial) { + visitedSuperConstructorInvokeSpecial = true + return newSuperclassName + } + // If the method reference is not a constructor then the instruction for a super call that + // should be transformed. + if (methodRefName != "<init>") { + return newSuperclassName + } + return null + } } /** diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointTransform.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointTransform.kt index 9bb5160b6..c9c6708b6 100644 --- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointTransform.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointTransform.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION") // com.android.build.api.transform is deprecated + package dagger.hilt.android.plugin import com.android.build.api.transform.DirectoryInput diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt index 7a33836dc..bbfe2926f 100644 --- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt @@ -15,9 +15,7 @@ */ package dagger.hilt.android.plugin -/** - * Configuration options for the Hilt Gradle Plugin - */ +/** Configuration options for the Hilt Gradle Plugin */ interface HiltExtension { /** @@ -42,9 +40,28 @@ interface HiltExtension { * deprecated in a future version. */ var enableTransformForLocalTests: Boolean + + /** + * If set to `true`, Hilt will perform module and entry points aggregation in a task instead of an + * aggregating annotation processor. Enabling this flag improves incremental build times. + * + * When this flag is enabled, 'enableExperimentalClasspathAggregation' has no effect since + * classpath aggregation will be done by default. + */ + var enableAggregatingTask: Boolean + + /** + * If set to `true`, Hilt will disable cross compilation root validation. + * + * See [documentation](https://dagger.dev/hilt/flags#disable-cross-compilation-root-validation) + * for more information. + */ + var disableCrossCompilationRootValidation: Boolean } internal open class HiltExtensionImpl : HiltExtension { override var enableExperimentalClasspathAggregation: Boolean = false override var enableTransformForLocalTests: Boolean = false + override var enableAggregatingTask: Boolean = true + override var disableCrossCompilationRootValidation: Boolean = false } diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt index a82826ed8..d872d66a3 100644 --- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt @@ -16,8 +16,8 @@ package dagger.hilt.android.plugin -import com.android.build.api.component.Component -import com.android.build.api.extension.AndroidComponentsExtension +import com.android.build.api.attributes.BuildTypeAttr +import com.android.build.api.attributes.ProductFlavorAttr import com.android.build.api.instrumentation.FramesComputationMode import com.android.build.api.instrumentation.InstrumentationScope import com.android.build.gradle.AppExtension @@ -26,18 +26,26 @@ import com.android.build.gradle.LibraryExtension import com.android.build.gradle.TestExtension import com.android.build.gradle.TestedExtension import com.android.build.gradle.api.AndroidBasePlugin -import com.android.build.gradle.api.BaseVariant -import com.android.build.gradle.api.TestVariant -import com.android.build.gradle.api.UnitTestVariant +import dagger.hilt.android.plugin.task.AggregateDepsTask +import dagger.hilt.android.plugin.task.HiltTransformTestClassesTask +import dagger.hilt.android.plugin.util.AggregatedPackagesTransform +import dagger.hilt.android.plugin.util.AndroidComponentsExtensionCompat.Companion.getAndroidComponentsExtension +import dagger.hilt.android.plugin.util.ComponentCompat import dagger.hilt.android.plugin.util.CopyTransform import dagger.hilt.android.plugin.util.SimpleAGPVersion +import dagger.hilt.android.plugin.util.capitalize +import dagger.hilt.android.plugin.util.getSdkPath import java.io.File import javax.inject.Inject +import org.gradle.api.JavaVersion import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.attributes.Attribute +import org.gradle.api.attributes.Usage import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.process.CommandLineArgumentProvider /** * A Gradle plugin that checks if the project is an Android project and if so, registers a @@ -70,82 +78,92 @@ class HiltGradlePlugin @Inject constructor( val hiltExtension = project.extensions.create( HiltExtension::class.java, "hilt", HiltExtensionImpl::class.java ) + configureDependencyTransforms(project) configureCompileClasspath(project, hiltExtension) if (SimpleAGPVersion.ANDROID_GRADLE_PLUGIN_VERSION < SimpleAGPVersion(4, 2)) { // Configures bytecode transform using older APIs pre AGP 4.2 - configureTransform(project, hiltExtension) + configureBytecodeTransform(project, hiltExtension) } else { // Configures bytecode transform using AGP 4.2 ASM pipeline. - configureTransformASM(project, hiltExtension) + configureBytecodeTransformASM(project, hiltExtension) + } + configureAggregatingTask(project, hiltExtension) + configureProcessorFlags(project, hiltExtension) + } + + // Configures Gradle dependency transforms. + private fun configureDependencyTransforms(project: Project) = project.dependencies.apply { + registerTransform(CopyTransform::class.java) { spec -> + // Java/Kotlin library projects offer an artifact of type 'jar'. + spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, "jar") + // Android library projects (with or without Kotlin) offer an artifact of type + // 'processed-jar', which AGP can offer as a jar. + spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, "processed-jar") + spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, DAGGER_ARTIFACT_TYPE_VALUE) + } + registerTransform(CopyTransform::class.java) { spec -> + // File Collection dependencies might be an artifact of type 'directory', e.g. when + // adding as a dep the destination directory of the JavaCompile task. + spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, "directory") + spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, DAGGER_ARTIFACT_TYPE_VALUE) + } + registerTransform(AggregatedPackagesTransform::class.java) { spec -> + spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, DAGGER_ARTIFACT_TYPE_VALUE) + spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, AGGREGATED_HILT_ARTIFACT_TYPE_VALUE) } - configureProcessorFlags(project) } private fun configureCompileClasspath(project: Project, hiltExtension: HiltExtension) { val androidExtension = project.extensions.findByType(BaseExtension::class.java) ?: throw error("Android BaseExtension not found.") - when (androidExtension) { + androidExtension.forEachRootVariant { variant -> + configureVariantCompileClasspath(project, hiltExtension, androidExtension, variant) + } + } + + // Invokes the [block] function for each Android variant that is considered a Hilt root, where + // dependencies are aggregated and components are generated. + private fun BaseExtension.forEachRootVariant( + @Suppress("DEPRECATION") block: (variant: com.android.build.gradle.api.BaseVariant) -> Unit + ) { + when (this) { is AppExtension -> { - // For an app project we configure the app variant and both androidTest and test variants, - // Hilt components are generated in all of them. - androidExtension.applicationVariants.all { - configureVariantCompileClasspath(project, hiltExtension, androidExtension, it) - } - androidExtension.testVariants.all { - configureVariantCompileClasspath(project, hiltExtension, androidExtension, it) - } - androidExtension.unitTestVariants.all { - configureVariantCompileClasspath(project, hiltExtension, androidExtension, it) - } + // For an app project we configure the app variant and both androidTest and unitTest + // variants, Hilt components are generated in all of them. + applicationVariants.all { block(it) } + testVariants.all { block(it) } + unitTestVariants.all { block(it) } } is LibraryExtension -> { - // For a library project, only the androidTest and test variant are configured since + // For a library project, only the androidTest and unitTest variant are configured since // Hilt components are not generated in a library. - androidExtension.testVariants.all { - configureVariantCompileClasspath(project, hiltExtension, androidExtension, it) - } - androidExtension.unitTestVariants.all { - configureVariantCompileClasspath(project, hiltExtension, androidExtension, it) - } + testVariants.all { block(it) } + unitTestVariants.all { block(it) } } is TestExtension -> { - androidExtension.applicationVariants.all { - configureVariantCompileClasspath(project, hiltExtension, androidExtension, it) - } - } - else -> error( - "Hilt plugin is unable to configure the compile classpath for project with extension " + - "'$androidExtension'" - ) - } - - project.dependencies.apply { - registerTransform(CopyTransform::class.java) { spec -> - // Java/Kotlin library projects offer an artifact of type 'jar'. - spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, "jar") - // Android library projects (with or without Kotlin) offer an artifact of type - // 'processed-jar', which AGP can offer as a jar. - spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, "processed-jar") - spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, DAGGER_ARTIFACT_TYPE_VALUE) + applicationVariants.all { block(it) } } + else -> error("Hilt plugin does not know how to configure '$this'") } } - @Suppress("UnstableApiUsage") private fun configureVariantCompileClasspath( project: Project, hiltExtension: HiltExtension, androidExtension: BaseExtension, - variant: BaseVariant + @Suppress("DEPRECATION") variant: com.android.build.gradle.api.BaseVariant ) { - if (!hiltExtension.enableExperimentalClasspathAggregation) { + if ( + !hiltExtension.enableExperimentalClasspathAggregation || hiltExtension.enableAggregatingTask + ) { // Option is not enabled, don't configure compile classpath. Note that the option can't be // checked earlier (before iterating over the variants) since it would have been too early for // the value to be populated from the build file. return } - if (androidExtension.lintOptions.isCheckReleaseBuilds && + if ( + androidExtension.lintOptions.isCheckReleaseBuilds && SimpleAGPVersion.ANDROID_GRADLE_PLUGIN_VERSION < SimpleAGPVersion(7, 0) ) { // Sadly we have to ask users to disable lint when enableExperimentalClasspathAggregation is @@ -160,19 +178,22 @@ class HiltGradlePlugin @Inject constructor( if ( listOf( - "android.injected.build.model.only", // Sent by AS 1.0 only - "android.injected.build.model.only.advanced", // Sent by AS 1.1+ - "android.injected.build.model.only.versioned", // Sent by AS 2.4+ - "android.injected.build.model.feature.full.dependencies", // Sent by AS 2.4+ - "android.injected.build.model.v2", // Sent by AS 4.2+ - ).any { providers.gradleProperty(it).forUseAtConfigurationTime().isPresent } + "android.injected.build.model.only", // Sent by AS 1.0 only + "android.injected.build.model.only.advanced", // Sent by AS 1.1+ + "android.injected.build.model.only.versioned", // Sent by AS 2.4+ + "android.injected.build.model.feature.full.dependencies", // Sent by AS 2.4+ + "android.injected.build.model.v2", // Sent by AS 4.2+ + ).any { + providers.gradleProperty(it).forUseAtConfigurationTime().isPresent + } ) { // Do not configure compile classpath when AndroidStudio is building the model (syncing) // otherwise it will cause a freeze. return } - val runtimeConfiguration = if (variant is TestVariant) { + @Suppress("DEPRECATION") // Older variant API is deprecated + val runtimeConfiguration = if (variant is com.android.build.gradle.api.TestVariant) { // For Android test variants, the tested runtime classpath is used since the test app has // tested dependencies removed. variant.testedVariant.runtimeConfiguration @@ -199,10 +220,11 @@ class HiltGradlePlugin @Inject constructor( // debugUnitTest -> testDebugCompileOnly // release -> releaseCompileOnly // releaseUnitTest -> testReleaseCompileOnly + @Suppress("DEPRECATION") // Older variant API is deprecated val compileOnlyConfigName = when (variant) { - is TestVariant -> + is com.android.build.gradle.api.TestVariant -> "androidTest${variant.name.substringBeforeLast("AndroidTest").capitalize()}CompileOnly" - is UnitTestVariant -> + is com.android.build.gradle.api.UnitTestVariant -> "test${variant.name.substringBeforeLast("UnitTest").capitalize()}CompileOnly" else -> "${variant.name}CompileOnly" @@ -210,10 +232,10 @@ class HiltGradlePlugin @Inject constructor( project.dependencies.add(compileOnlyConfigName, artifactView.files) } - @Suppress("UnstableApiUsage") - private fun configureTransformASM(project: Project, hiltExtension: HiltExtension) { + @Suppress("UnstableApiUsage") // ASM Pipeline APIs + private fun configureBytecodeTransformASM(project: Project, hiltExtension: HiltExtension) { var warnAboutLocalTestsFlag = false - fun registerTransform(androidComponent: Component) { + fun registerTransform(androidComponent: ComponentCompat) { if (hiltExtension.enableTransformForLocalTests && !warnAboutLocalTestsFlag) { project.logger.warn( "The Hilt configuration option 'enableTransformForLocalTests' is no longer necessary " + @@ -233,14 +255,10 @@ class HiltGradlePlugin @Inject constructor( FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS ) } - - val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) - androidComponents.onVariants { registerTransform(it) } - androidComponents.androidTests { registerTransform(it) } - androidComponents.unitTests { registerTransform(it) } + getAndroidComponentsExtension(project).onAllVariants { registerTransform(it) } } - private fun configureTransform(project: Project, hiltExtension: HiltExtension) { + private fun configureBytecodeTransform(project: Project, hiltExtension: HiltExtension) { val androidExtension = project.extensions.findByType(BaseExtension::class.java) ?: throw error("Android BaseExtension not found.") androidExtension.registerTransform(AndroidEntryPointTransform()) @@ -256,17 +274,189 @@ class HiltGradlePlugin @Inject constructor( } } - private fun configureProcessorFlags(project: Project) { + private fun configureAggregatingTask(project: Project, hiltExtension: HiltExtension) { val androidExtension = project.extensions.findByType(BaseExtension::class.java) ?: throw error("Android BaseExtension not found.") - // Pass annotation processor flag to disable @AndroidEntryPoint superclass validation. - androidExtension.defaultConfig.apply { - javaCompileOptions.apply { - annotationProcessorOptions.apply { - PROCESSOR_OPTIONS.forEach { (key, value) -> argument(key, value) } + androidExtension.forEachRootVariant { variant -> + configureVariantAggregatingTask(project, hiltExtension, androidExtension, variant) + } + } + + private fun configureVariantAggregatingTask( + project: Project, + hiltExtension: HiltExtension, + androidExtension: BaseExtension, + @Suppress("DEPRECATION") variant: com.android.build.gradle.api.BaseVariant + ) { + if (!hiltExtension.enableAggregatingTask) { + // Option is not enabled, don't configure aggregating task. + return + } + + val hiltCompileConfiguration = project.configurations.create( + "hiltCompileOnly${variant.name.capitalize()}" + ).apply { + // The runtime config of the test APK differs from the tested one. + @Suppress("DEPRECATION") // Older variant API is deprecated + if (variant is com.android.build.gradle.api.TestVariant) { + extendsFrom(variant.testedVariant.runtimeConfiguration) + } + extendsFrom(variant.runtimeConfiguration) + isCanBeConsumed = false + isCanBeResolved = true + attributes { attrContainer -> + attrContainer.attribute( + Usage.USAGE_ATTRIBUTE, + project.objects.named(Usage::class.java, Usage.JAVA_RUNTIME) + ) + attrContainer.attribute( + BuildTypeAttr.ATTRIBUTE, + project.objects.named(BuildTypeAttr::class.java, variant.buildType.name) + ) + variant.productFlavors.forEach { flavor -> + attrContainer.attribute( + Attribute.of(flavor.dimension!!, ProductFlavorAttr::class.java), + project.objects.named(ProductFlavorAttr::class.java, flavor.name) + ) } } } + // Add the JavaCompile task classpath and output dir to the config, the task's classpath + // will contain: + // * compileOnly dependencies + // * KAPT and Kotlinc generated bytecode + // * R.jar + // * Tested classes if the variant is androidTest + project.dependencies.add( + hiltCompileConfiguration.name, + project.files(variant.javaCompileProvider.map { it.classpath }) + ) + project.dependencies.add( + hiltCompileConfiguration.name, + project.files(variant.javaCompileProvider.map {it.destinationDirectory.get() }) + ) + + fun getInputClasspath(artifactAttributeValue: String) = + hiltCompileConfiguration.incoming.artifactView { view -> + view.attributes.attribute(ARTIFACT_TYPE_ATTRIBUTE, artifactAttributeValue) + }.files + + val aggregatingTask = project.tasks.register( + "hiltAggregateDeps${variant.name.capitalize()}", + AggregateDepsTask::class.java + ) { + it.compileClasspath.setFrom(getInputClasspath(AGGREGATED_HILT_ARTIFACT_TYPE_VALUE)) + it.outputDir.set( + project.file(project.buildDir.resolve("generated/hilt/component_trees/${variant.name}/")) + ) + @Suppress("DEPRECATION") // Older variant API is deprecated + it.testEnvironment.set( + variant is com.android.build.gradle.api.TestVariant || + variant is com.android.build.gradle.api.UnitTestVariant + ) + it.crossCompilationRootValidationDisabled.set( + hiltExtension.disableCrossCompilationRootValidation + ) + } + + val componentClasses = project.files( + project.buildDir.resolve("intermediates/hilt/component_classes/${variant.name}/") + ) + val componentsJavaCompileTask = project.tasks.register( + "hiltJavaCompile${variant.name.capitalize()}", + JavaCompile::class.java + ) { compileTask -> + compileTask.source = aggregatingTask.map { it.outputDir.asFileTree }.get() + // Configure the input classpath based on Java 9 compatibility, specifically for Java 9 the + // android.jar is now included in the input classpath instead of the bootstrapClasspath. + // See: com/android/build/gradle/tasks/JavaCompileUtils.kt + val mainBootstrapClasspath = + variant.javaCompileProvider.map { it.options.bootstrapClasspath ?: project.files() }.get() + if ( + JavaVersion.current().isJava9Compatible && + androidExtension.compileOptions.targetCompatibility.isJava9Compatible + ) { + compileTask.classpath = + getInputClasspath(DAGGER_ARTIFACT_TYPE_VALUE).plus(mainBootstrapClasspath) + // Copies argument providers from original task, which should contain the JdkImageInput + variant.javaCompileProvider.get().let { originalCompileTask -> + originalCompileTask.options.compilerArgumentProviders.forEach { + compileTask.options.compilerArgumentProviders.add(it) + } + } + compileTask.options.compilerArgs.add("-XDstringConcat=inline") + } else { + compileTask.classpath = getInputClasspath(DAGGER_ARTIFACT_TYPE_VALUE) + compileTask.options.bootstrapClasspath = mainBootstrapClasspath + } + compileTask.destinationDirectory.set(componentClasses.singleFile) + compileTask.options.apply { + annotationProcessorPath = project.configurations.create( + "hiltAnnotationProcessor${variant.name.capitalize()}" + ).also { config -> + // TODO: Consider finding the hilt-compiler dep from the user config and using it here. + project.dependencies.add(config.name, "com.google.dagger:hilt-compiler:$HILT_VERSION") + } + generatedSourceOutputDirectory.set( + project.file( + project.buildDir.resolve("generated/hilt/component_sources/${variant.name}/") + ) + ) + if ( + JavaVersion.current().isJava8Compatible && + androidExtension.compileOptions.targetCompatibility.isJava8Compatible + ) { + compilerArgs.add("-parameters") + } + compilerArgs.add("-Adagger.fastInit=enabled") + compilerArgs.add("-Adagger.hilt.internal.useAggregatingRootProcessor=false") + compilerArgs.add("-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true") + encoding = androidExtension.compileOptions.encoding + } + compileTask.sourceCompatibility = + androidExtension.compileOptions.sourceCompatibility.toString() + compileTask.targetCompatibility = + androidExtension.compileOptions.targetCompatibility.toString() + } + componentClasses.builtBy(componentsJavaCompileTask) + + variant.registerPostJavacGeneratedBytecode(componentClasses) + } + + private fun getAndroidJar(project: Project, compileSdkVersion: String) = + project.files(File(project.getSdkPath(), "platforms/$compileSdkVersion/android.jar")) + + private fun configureProcessorFlags(project: Project, hiltExtension: HiltExtension) { + val androidExtension = project.extensions.findByType(BaseExtension::class.java) + ?: throw error("Android BaseExtension not found.") + androidExtension.defaultConfig.javaCompileOptions.annotationProcessorOptions.apply { + // Pass annotation processor flag to enable Dagger's fast-init, the best mode for Hilt. + argument("dagger.fastInit", "enabled") + // Pass annotation processor flag to disable @AndroidEntryPoint superclass validation. + argument("dagger.hilt.android.internal.disableAndroidSuperclassValidation", "true") + // Pass certain annotation processor flags via a CommandLineArgumentProvider so that plugin + // options defined in the extension are populated from the user's build file. Checking the + // option too early would make it seem like it is never set. + compilerArgumentProvider( + // Suppress due to https://docs.gradle.org/7.2/userguide/validation_problems.html#implementation_unknown + @Suppress("ObjectLiteralToLambda") + object : CommandLineArgumentProvider { + override fun asArguments() = mutableListOf<String>().apply { + // Pass annotation processor flag to disable the aggregating processor if aggregating + // task is enabled. + if (hiltExtension.enableAggregatingTask) { + add("-Adagger.hilt.internal.useAggregatingRootProcessor=false") + } + // Pass annotation processor flag to disable cross compilation root validation. + // The plugin option duplicates the processor flag because it is an input of the + // aggregating task. + if (hiltExtension.disableCrossCompilationRootValidation) { + add("-Adagger.hilt.disableCrossCompilationRootValidation=true") + } + } + } + ) + } } private fun verifyDependencies(project: Project) { @@ -280,7 +470,8 @@ class HiltGradlePlugin @Inject constructor( if (!dependencies.contains(LIBRARY_GROUP to "hilt-android")) { error(missingDepError("$LIBRARY_GROUP:hilt-android")) } - if (!dependencies.contains(LIBRARY_GROUP to "hilt-android-compiler") && + if ( + !dependencies.contains(LIBRARY_GROUP to "hilt-android-compiler") && !dependencies.contains(LIBRARY_GROUP to "hilt-compiler") ) { error(missingDepError("$LIBRARY_GROUP:hilt-compiler")) @@ -290,12 +481,10 @@ class HiltGradlePlugin @Inject constructor( companion object { val ARTIFACT_TYPE_ATTRIBUTE = Attribute.of("artifactType", String::class.java) const val DAGGER_ARTIFACT_TYPE_VALUE = "jar-for-dagger" + const val AGGREGATED_HILT_ARTIFACT_TYPE_VALUE = "aggregated-jar-for-hilt" const val LIBRARY_GROUP = "com.google.dagger" - val PROCESSOR_OPTIONS = listOf( - "dagger.fastInit" to "enabled", - "dagger.hilt.android.internal.disableAndroidSuperclassValidation" to "true" - ) + val missingDepError: (String) -> String = { depCoordinate -> "The Hilt Android Gradle plugin is applied but no $depCoordinate dependency was found." } diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/AggregatedAnnotation.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/AggregatedAnnotation.kt new file mode 100644 index 000000000..e08b6f1ae --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/AggregatedAnnotation.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 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.hilt.android.plugin.root + +// Annotations used for aggregating dependencies by the annotation processors. +internal enum class AggregatedAnnotation( + private val descriptor: String, + private val aggregatedPackage: String +) { + AGGREGATED_ROOT( + "Ldagger/hilt/internal/aggregatedroot/AggregatedRoot;", + "dagger/hilt/internal/aggregatedroot/codegen" + ), + PROCESSED_ROOT_SENTINEL( + "Ldagger/hilt/internal/processedrootsentinel/ProcessedRootSentinel;", + "dagger/hilt/internal/processedrootsentinel/codegen" + ), + DEFINE_COMPONENT( + "Ldagger/hilt/internal/definecomponent/DefineComponentClasses;", + "dagger/hilt/processor/internal/definecomponent/codegen" + ), + ALIAS_OF( + "Ldagger/hilt/internal/aliasof/AliasOfPropagatedData;", + "dagger/hilt/processor/internal/aliasof/codegen" + ), + AGGREGATED_DEP( + "Ldagger/hilt/processor/internal/aggregateddeps/AggregatedDeps;", + "hilt_aggregated_deps" + ), + AGGREGATED_DEP_PROXY( + "Ldagger/hilt/android/internal/legacy/AggregatedElementProxy;", + "", // Proxies share the same package name as the elements they are proxying. + ), + AGGREGATED_UNINSTALL_MODULES( + "Ldagger/hilt/android/internal/uninstallmodules/AggregatedUninstallModules;", + "dagger/hilt/android/internal/uninstallmodules/codegen" + ), + AGGREGATED_EARLY_ENTRY_POINT( + "Ldagger/hilt/android/internal/earlyentrypoint/AggregatedEarlyEntryPoint;", + "dagger/hilt/android/internal/earlyentrypoint/codegen" + ), + NONE("", ""); + + companion object { + fun fromString(str: String) = values().firstOrNull { it.descriptor == str } ?: NONE + + val AGGREGATED_PACKAGES = values().map { it.aggregatedPackage }.filter { it.isNotEmpty() } + } +} diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/AggregatedElementProxyGenerator.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/AggregatedElementProxyGenerator.kt new file mode 100644 index 000000000..248976ef4 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/AggregatedElementProxyGenerator.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 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.hilt.android.plugin.root + +import com.squareup.javapoet.AnnotationSpec +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.JavaFile +import com.squareup.javapoet.TypeSpec +import dagger.hilt.processor.internal.root.ir.AggregatedElementProxyIr +import java.io.File +import javax.lang.model.element.Modifier + +internal class AggregatedElementProxyGenerator( + private val outputDir: File, +) { + + fun generate(aggregatedElementProxy: AggregatedElementProxyIr) { + val typeSpec = TypeSpec.classBuilder(aggregatedElementProxy.fqName) + .addAnnotation( + AnnotationSpec.builder(AGGREGATED_ELEMENT_PROXY_ANNOTATION) + .addMember("value", "\$T.class", aggregatedElementProxy.value) + .build() + ) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .build() + JavaFile.builder(aggregatedElementProxy.fqName.packageName(), typeSpec) + .build() + .writeTo(outputDir) + } + + companion object { + val AGGREGATED_ELEMENT_PROXY_ANNOTATION = + ClassName.get("dagger.hilt.android.internal.legacy", "AggregatedElementProxy") + } +} diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/Aggregator.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/Aggregator.kt new file mode 100644 index 000000000..b3cb737e6 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/Aggregator.kt @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2021 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.hilt.android.plugin.task + +import com.squareup.javapoet.ClassName +import dagger.hilt.android.plugin.root.AggregatedAnnotation +import dagger.hilt.android.plugin.util.forEachZipEntry +import dagger.hilt.android.plugin.util.isClassFile +import dagger.hilt.android.plugin.util.isJarFile +import dagger.hilt.processor.internal.root.ir.AggregatedDepsIr +import dagger.hilt.processor.internal.root.ir.AggregatedEarlyEntryPointIr +import dagger.hilt.processor.internal.root.ir.AggregatedElementProxyIr +import dagger.hilt.processor.internal.root.ir.AggregatedRootIr +import dagger.hilt.processor.internal.root.ir.AggregatedUninstallModulesIr +import dagger.hilt.processor.internal.root.ir.AliasOfPropagatedDataIr +import dagger.hilt.processor.internal.root.ir.DefineComponentClassesIr +import dagger.hilt.processor.internal.root.ir.ProcessedRootSentinelIr +import java.io.File +import java.io.InputStream +import java.util.zip.ZipInputStream +import org.objectweb.asm.AnnotationVisitor +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import org.slf4j.Logger + +/** Aggregates Hilt dependencies. */ +internal class Aggregator private constructor( + private val logger: Logger, + private val asmApiVersion: Int, +) { + private val classVisitor = AggregatedDepClassVisitor(logger, asmApiVersion) + + val aggregatedRoots: Set<AggregatedRootIr> + get() = classVisitor.aggregatedRoots + + val processedRoots: Set<ProcessedRootSentinelIr> + get() = classVisitor.processedRoots + + val defineComponentDeps: Set<DefineComponentClassesIr> + get() = classVisitor.defineComponentDeps + + val aliasOfDeps: Set<AliasOfPropagatedDataIr> + get() = classVisitor.aliasOfDeps + + val aggregatedDeps: Set<AggregatedDepsIr> + get() = classVisitor.aggregatedDeps + + val aggregatedDepProxies: Set<AggregatedElementProxyIr> + get() = classVisitor.aggregatedDepProxies + + val allAggregatedDepProxies: Set<AggregatedElementProxyIr> + get() = classVisitor.allAggregatedDepProxies + + val uninstallModulesDeps: Set<AggregatedUninstallModulesIr> + get() = classVisitor.uninstallModulesDeps + + val earlyEntryPointDeps: Set<AggregatedEarlyEntryPointIr> + get() = classVisitor.earlyEntryPointDeps + + private class AggregatedDepClassVisitor( + private val logger: Logger, + private val asmApiVersion: Int, + ) : ClassVisitor(asmApiVersion) { + + val aggregatedRoots = mutableSetOf<AggregatedRootIr>() + val processedRoots = mutableSetOf<ProcessedRootSentinelIr>() + val defineComponentDeps = mutableSetOf<DefineComponentClassesIr>() + val aliasOfDeps = mutableSetOf<AliasOfPropagatedDataIr>() + val aggregatedDeps = mutableSetOf<AggregatedDepsIr>() + val aggregatedDepProxies = mutableSetOf<AggregatedElementProxyIr>() + val allAggregatedDepProxies = mutableSetOf<AggregatedElementProxyIr>() + val uninstallModulesDeps = mutableSetOf<AggregatedUninstallModulesIr>() + val earlyEntryPointDeps = mutableSetOf<AggregatedEarlyEntryPointIr>() + + var accessCode: Int = Opcodes.ACC_PUBLIC + lateinit var annotatedClassName: ClassName + + override fun visit( + version: Int, + access: Int, + name: String, + signature: String?, + superName: String?, + interfaces: Array<out String>? + ) { + accessCode = access + annotatedClassName = Type.getObjectType(name).toClassName() + super.visit(version, access, name, signature, superName, interfaces) + } + + override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? { + val nextAnnotationVisitor = super.visitAnnotation(descriptor, visible) + val aggregatedAnnotation = AggregatedAnnotation.fromString(descriptor) + val isHiltAnnotated = aggregatedAnnotation != AggregatedAnnotation.NONE + // For non-public deps, a proxy might be needed, make a note of it. + if (isHiltAnnotated && (accessCode and Opcodes.ACC_PUBLIC) != Opcodes.ACC_PUBLIC) { + allAggregatedDepProxies.add( + AggregatedElementProxyIr( + fqName = annotatedClassName.peerClass("_" + annotatedClassName.simpleName()), + value = annotatedClassName + ) + ) + } + when (aggregatedAnnotation) { + AggregatedAnnotation.AGGREGATED_ROOT -> { + return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) { + lateinit var rootClass: String + lateinit var originatingRootClass: String + lateinit var rootAnnotationClassName: Type + + override fun visit(name: String, value: Any?) { + when (name) { + "root" -> rootClass = value as String + "originatingRoot" -> originatingRootClass = value as String + "rootAnnotation" -> rootAnnotationClassName = (value as Type) + } + super.visit(name, value) + } + + override fun visitEnd() { + aggregatedRoots.add( + AggregatedRootIr( + fqName = annotatedClassName, + root = rootClass.toClassName(), + originatingRoot = originatingRootClass.toClassName(), + rootAnnotation = rootAnnotationClassName.toClassName() + ) + ) + super.visitEnd() + } + } + } + AggregatedAnnotation.PROCESSED_ROOT_SENTINEL -> { + return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) { + val rootClasses = mutableListOf<String>() + + override fun visitArray(name: String): AnnotationVisitor? { + return when (name) { + "roots" -> visitValue { value -> rootClasses.add(value as String) } + else -> super.visitArray(name) + } + } + + override fun visitEnd() { + processedRoots.add( + ProcessedRootSentinelIr( + fqName = annotatedClassName, + roots = rootClasses.map { it.toClassName() } + ) + ) + super.visitEnd() + } + } + } + AggregatedAnnotation.DEFINE_COMPONENT -> { + return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) { + lateinit var componentClass: String + + override fun visit(name: String, value: Any?) { + when (name) { + "component", "builder" -> componentClass = value as String + } + super.visit(name, value) + } + + override fun visitEnd() { + defineComponentDeps.add( + DefineComponentClassesIr( + fqName = annotatedClassName, + component = componentClass.toClassName() + ) + ) + super.visitEnd() + } + } + } + AggregatedAnnotation.ALIAS_OF -> { + return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) { + val defineComponentScopeClassNames = mutableSetOf<Type>() + lateinit var aliasClassName: Type + + // visit() handles both array and non-array values. + // For array values, each value in the array will be visited individually. + override fun visit(name: String, value: Any?) { + when (name) { + // Older versions of AliasOfPropagatedData only passed a single defineComponentScope + // class value. Fall back on reading the single value if we get old propagated data. + "defineComponentScope", + "defineComponentScopes" -> defineComponentScopeClassNames.add(value as Type) + "alias" -> aliasClassName = (value as Type) + } + super.visit(name, value) + } + + override fun visitEnd() { + aliasOfDeps.add( + AliasOfPropagatedDataIr( + fqName = annotatedClassName, + defineComponentScopes = + defineComponentScopeClassNames.map({ it.toClassName() }).toList(), + alias = aliasClassName.toClassName(), + ) + ) + super.visitEnd() + } + } + } + AggregatedAnnotation.AGGREGATED_DEP -> { + return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) { + val componentClasses = mutableListOf<String>() + var testClass: String? = null + val replacesClasses = mutableListOf<String>() + var moduleClass: String? = null + var entryPoint: String? = null + var componentEntryPoint: String? = null + + override fun visit(name: String, value: Any?) { + when (name) { + "test" -> testClass = value as String + } + super.visit(name, value) + } + + override fun visitArray(name: String): AnnotationVisitor? { + return when (name) { + "components" -> + visitValue { value -> componentClasses.add(value as String) } + "replaces" -> + visitValue { value -> replacesClasses.add(value as String) } + "modules" -> + visitValue { value -> moduleClass = value as String } + "entryPoints" -> + visitValue { value -> entryPoint = value as String } + "componentEntryPoints" -> + visitValue { value -> componentEntryPoint = value as String } + else -> super.visitArray(name) + } + } + + override fun visitEnd() { + aggregatedDeps.add( + AggregatedDepsIr( + fqName = annotatedClassName, + components = componentClasses.map { it.toClassName() }, + test = testClass?.toClassName(), + replaces = replacesClasses.map { it.toClassName() }, + module = moduleClass?.toClassName(), + entryPoint = entryPoint?.toClassName(), + componentEntryPoint = componentEntryPoint?.toClassName() + ) + ) + super.visitEnd() + } + } + } + AggregatedAnnotation.AGGREGATED_DEP_PROXY -> { + return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) { + lateinit var valueClassName: Type + + override fun visit(name: String, value: Any?) { + when (name) { + "value" -> valueClassName = (value as Type) + } + super.visit(name, value) + } + + override fun visitEnd() { + aggregatedDepProxies.add( + AggregatedElementProxyIr( + fqName = annotatedClassName, + value = valueClassName.toClassName(), + ) + ) + super.visitEnd() + } + } + } + AggregatedAnnotation.AGGREGATED_UNINSTALL_MODULES -> { + return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) { + lateinit var testClass: String + val uninstallModulesClasses = mutableListOf<String>() + + override fun visit(name: String, value: Any?) { + when (name) { + "test" -> testClass = value as String + } + super.visit(name, value) + } + + override fun visitArray(name: String): AnnotationVisitor? { + return when (name) { + "uninstallModules" -> + visitValue { value -> uninstallModulesClasses.add(value as String) } + else -> super.visitArray(name) + } + } + + override fun visitEnd() { + uninstallModulesDeps.add( + AggregatedUninstallModulesIr( + fqName = annotatedClassName, + test = testClass.toClassName(), + uninstallModules = uninstallModulesClasses.map { it.toClassName() } + ) + ) + super.visitEnd() + } + } + } + AggregatedAnnotation.AGGREGATED_EARLY_ENTRY_POINT -> { + return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) { + lateinit var earlyEntryPointClass: String + + override fun visit(name: String, value: Any?) { + when (name) { + "earlyEntryPoint" -> earlyEntryPointClass = value as String + } + super.visit(name, value) + } + + override fun visitEnd() { + earlyEntryPointDeps.add( + AggregatedEarlyEntryPointIr( + fqName = annotatedClassName, + earlyEntryPoint = earlyEntryPointClass.toClassName() + ) + ) + super.visitEnd() + } + } + } + else -> { + logger.warn("Found an unknown annotation in Hilt aggregated packages: $descriptor") + } + } + return nextAnnotationVisitor + } + + fun visitValue(block: (value: Any) -> Unit) = + object : AnnotationVisitor(asmApiVersion) { + override fun visit(nullName: String?, value: Any) { + block(value) + } + } + } + + private fun process(files: Iterable<File>) { + files.forEach { file -> + when { + file.isFile -> visitFile(file) + file.isDirectory -> file.walkTopDown().filter { it.isFile }.forEach { visitFile(it) } + else -> logger.warn("Can't process file/directory that doesn't exist: $file") + } + } + } + + private fun visitFile(file: File) { + when { + file.isJarFile() -> ZipInputStream(file.inputStream()).forEachZipEntry { inputStream, entry -> + if (entry.isClassFile()) { + visitClass(inputStream) + } + } + file.isClassFile() -> file.inputStream().use { visitClass(it) } + else -> logger.debug("Don't know how to process file: $file") + } + } + + private fun visitClass(classFileInputStream: InputStream) { + ClassReader(classFileInputStream).accept( + classVisitor, + ClassReader.SKIP_CODE and ClassReader.SKIP_DEBUG and ClassReader.SKIP_FRAMES + ) + } + + companion object { + fun from( + logger: Logger, + asmApiVersion: Int, + input: Iterable<File> + ) = Aggregator(logger, asmApiVersion).apply { process(input) } + + // Converts this Type to a ClassName, used instead of ClassName.bestGuess() because ASM class + // names are based off descriptors and uses 'reflection' naming, i.e. inner classes are split + // by '$' instead of '.' + fun Type.toClassName(): ClassName { + val binaryName = this.className + val packageNameEndIndex = binaryName.lastIndexOf('.') + val packageName = if (packageNameEndIndex != -1) { + binaryName.substring(0, packageNameEndIndex) + } else { + "" + } + val shortNames = binaryName.substring(packageNameEndIndex + 1).split('$') + return ClassName.get(packageName, shortNames.first(), *shortNames.drop(1).toTypedArray()) + } + + // Converts this String representing the canonical name of a class to a ClassName. + fun String.toClassName(): ClassName { + return ClassName.bestGuess(this) + } + } +} diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/ComponentTreeDepsGenerator.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/ComponentTreeDepsGenerator.kt new file mode 100644 index 000000000..705528a87 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/ComponentTreeDepsGenerator.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2021 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.hilt.android.plugin.root + +import com.squareup.javapoet.AnnotationSpec +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.JavaFile +import com.squareup.javapoet.TypeSpec +import dagger.hilt.processor.internal.root.ir.ComponentTreeDepsIr +import java.io.File +import javax.lang.model.element.Modifier + +/** Generates @ComponentTreeDeps annotated sources. */ +internal class ComponentTreeDepsGenerator( + private val proxies: Map<ClassName, ClassName>, + private val outputDir: File, +) { + fun generate(componentTree: ComponentTreeDepsIr) { + val typeSpec = TypeSpec.classBuilder(componentTree.name) + .addAnnotation( + AnnotationSpec.builder(COMPONENT_TREE_DEPS_ANNOTATION).apply { + componentTree.rootDeps.toMaybeProxies().forEach { + addMember("rootDeps", "\$T.class", it) + } + componentTree.defineComponentDeps.toMaybeProxies().forEach { + addMember("defineComponentDeps", "\$T.class", it) + } + componentTree.aliasOfDeps.toMaybeProxies().forEach { + addMember("aliasOfDeps", "\$T.class", it) + } + componentTree.aggregatedDeps.toMaybeProxies().forEach { + addMember("aggregatedDeps", "\$T.class", it) + } + componentTree.uninstallModulesDeps.toMaybeProxies().forEach { + addMember("uninstallModulesDeps", "\$T.class", it) + } + componentTree.earlyEntryPointDeps.toMaybeProxies().forEach { + addMember("earlyEntryPointDeps", "\$T.class", it) + } + }.build() + ) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .build() + JavaFile.builder(componentTree.name.packageName(), typeSpec) + .build() + .writeTo(outputDir) + } + + private fun Collection<ClassName>.toMaybeProxies() = + sorted().map { fqName -> proxies[fqName] ?: fqName } + + companion object { + val COMPONENT_TREE_DEPS_ANNOTATION: ClassName = + ClassName.get("dagger.hilt.internal.componenttreedeps", "ComponentTreeDeps") + } +} diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/ProcessedRootSentinelGenerator.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/ProcessedRootSentinelGenerator.kt new file mode 100644 index 000000000..931b424ed --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/root/ProcessedRootSentinelGenerator.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 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.hilt.android.plugin.root + +import com.squareup.javapoet.AnnotationSpec +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.JavaFile +import com.squareup.javapoet.TypeSpec +import java.io.File +import javax.lang.model.element.Modifier + +internal class ProcessedRootSentinelGenerator( + private val outputDir: File, +) { + + fun generate(processedRootName: ClassName) { + val className = ClassName.get( + PROCESSED_ROOT_SENTINEL_GEN_PACKAGE, + "_" + processedRootName.toString().replace('.', '_') + ) + val typeSpec = TypeSpec.classBuilder(className) + .addAnnotation( + AnnotationSpec.builder(PROCESSED_ROOT_SENTINEL_ANNOTATION) + .addMember("roots", "\$S", processedRootName) + .build() + ) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .build() + JavaFile.builder(PROCESSED_ROOT_SENTINEL_GEN_PACKAGE, typeSpec) + .build() + .writeTo(outputDir) + } + + companion object { + val PROCESSED_ROOT_SENTINEL_GEN_PACKAGE = "dagger.hilt.internal.processedrootsentinel.codegen" + val PROCESSED_ROOT_SENTINEL_ANNOTATION = + ClassName.get("dagger.hilt.internal.processedrootsentinel", "ProcessedRootSentinel") + } +} diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/task/AggregateDepsTask.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/task/AggregateDepsTask.kt new file mode 100644 index 000000000..f3939fc49 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/task/AggregateDepsTask.kt @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2021 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.hilt.android.plugin.task + +import dagger.hilt.android.plugin.root.AggregatedElementProxyGenerator +import dagger.hilt.android.plugin.root.ComponentTreeDepsGenerator +import dagger.hilt.android.plugin.root.ProcessedRootSentinelGenerator +import dagger.hilt.processor.internal.root.ir.AggregatedRootIrValidator +import dagger.hilt.processor.internal.root.ir.ComponentTreeDepsIrCreator +import javax.inject.Inject +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import org.gradle.work.InputChanges +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters +import org.gradle.workers.WorkerExecutor +import org.objectweb.asm.Opcodes +import org.slf4j.LoggerFactory + +/** + * Aggregates Hilt component dependencies from the compile classpath and outputs Java sources + * with shareable component trees. + * + * The [compileClasspath] input is expected to contain jars or classes transformed by + * [dagger.hilt.android.plugin.util.AggregatedPackagesTransform]. + */ +@CacheableTask +abstract class AggregateDepsTask @Inject constructor( + private val workerExecutor: WorkerExecutor +) : DefaultTask() { + + // TODO(danysantiago): Make @Incremental and try to use @CompileClasspath + @get:Classpath + abstract val compileClasspath: ConfigurableFileCollection + + @get:Input + @get:Optional + abstract val asmApiVersion: Property<Int> + + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + @get:Input + abstract val testEnvironment: Property<Boolean> + + @get:Input + abstract val crossCompilationRootValidationDisabled: Property<Boolean> + + @TaskAction + internal fun taskAction(@Suppress("UNUSED_PARAMETER") inputs: InputChanges) { + workerExecutor.noIsolation().submit(WorkerAction::class.java) { + it.compileClasspath.from(compileClasspath) + it.asmApiVersion.set(asmApiVersion) + it.outputDir.set(outputDir) + it.testEnvironment.set(testEnvironment) + it.crossCompilationRootValidationDisabled.set(crossCompilationRootValidationDisabled) + } + } + + internal interface Parameters : WorkParameters { + val compileClasspath: ConfigurableFileCollection + val asmApiVersion: Property<Int> + val outputDir: DirectoryProperty + val testEnvironment: Property<Boolean> + val crossCompilationRootValidationDisabled: Property<Boolean> + } + + abstract class WorkerAction : WorkAction<Parameters> { + override fun execute() { + // Logger is not an injectable service yet: https://github.com/gradle/gradle/issues/16991 + val logger = LoggerFactory.getLogger(AggregateDepsTask::class.java) + val aggregator = Aggregator.from( + logger = logger, + asmApiVersion = parameters.asmApiVersion.getOrNull() ?: Opcodes.ASM7, + input = parameters.compileClasspath + ) + val rootsToProcess = AggregatedRootIrValidator.rootsToProcess( + isCrossCompilationRootValidationDisabled = + parameters.crossCompilationRootValidationDisabled.get(), + processedRoots = aggregator.processedRoots, + aggregatedRoots = aggregator.aggregatedRoots + ) + if (rootsToProcess.isEmpty()) { + return + } + val componentTrees = ComponentTreeDepsIrCreator.components( + isTest = parameters.testEnvironment.get(), + isSharedTestComponentsEnabled = true, + aggregatedRoots = rootsToProcess, + defineComponentDeps = aggregator.defineComponentDeps, + aliasOfDeps = aggregator.aliasOfDeps, + aggregatedDeps = aggregator.aggregatedDeps, + aggregatedUninstallModulesDeps = aggregator.uninstallModulesDeps, + aggregatedEarlyEntryPointDeps = aggregator.earlyEntryPointDeps, + ) + ComponentTreeDepsGenerator( + proxies = aggregator.allAggregatedDepProxies.associate { it.value to it.fqName }, + outputDir = parameters.outputDir.get().asFile + ).let { generator -> + componentTrees.forEach { generator.generate(it) } + } + AggregatedElementProxyGenerator(parameters.outputDir.get().asFile).let { generator -> + (aggregator.allAggregatedDepProxies - aggregator.aggregatedDepProxies).forEach { + generator.generate(it) + } + } + ProcessedRootSentinelGenerator(parameters.outputDir.get().asFile).let { generator -> + rootsToProcess.map { it.root }.forEach { generator.generate(it) } + } + } + } +} diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltTransformTestClassesTask.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/task/HiltTransformTestClassesTask.kt index 84b35b1c7..e2e4e7c56 100644 --- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltTransformTestClassesTask.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/task/HiltTransformTestClassesTask.kt @@ -13,9 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dagger.hilt.android.plugin +package dagger.hilt.android.plugin.task -import com.android.build.gradle.api.UnitTestVariant +import dagger.hilt.android.plugin.AndroidEntryPointClassTransformer +import dagger.hilt.android.plugin.HiltExtension +import dagger.hilt.android.plugin.util.capitalize +import dagger.hilt.android.plugin.util.getCompileKotlin import dagger.hilt.android.plugin.util.isClassFile import dagger.hilt.android.plugin.util.isJarFile import java.io.File @@ -31,18 +34,15 @@ import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskProvider -import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.testing.Test import org.gradle.workers.WorkAction import org.gradle.workers.WorkParameters import org.gradle.workers.WorkerExecutor import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile /** * Task that transform classes used by host-side unit tests. See b/37076369 */ -@Suppress("UnstableApiUsage") abstract class HiltTransformTestClassesTask @Inject constructor( private val workerExecutor: WorkerExecutor ) : DefaultTask() { @@ -113,7 +113,7 @@ abstract class HiltTransformTestClassesTask @Inject constructor( fun create( project: Project, - unitTestVariant: UnitTestVariant, + @Suppress("DEPRECATION") unitTestVariant: com.android.build.gradle.api.UnitTestVariant, extension: HiltExtension ) { if (!extension.enableTransformForLocalTests) { @@ -126,24 +126,18 @@ abstract class HiltTransformTestClassesTask @Inject constructor( // registerPreJavacGeneratedBytecode() API that would have otherwise given us a key to get // a classpath up to the generated bytecode associated with the key. val inputClasspath = - project.objects.fileCollection().from(unitTestVariant.getCompileClasspath(null)) + project.files(unitTestVariant.getCompileClasspath(null)) // Find the test sources Java compile task and add its output directory into our input // classpath file collection. This also makes the transform task depend on the test compile // task. - @Suppress("UNCHECKED_CAST") - val testCompileTaskProvider = project.tasks.named( - "compile${unitTestVariant.name.capitalize()}JavaWithJavac" - ) as TaskProvider<JavaCompile> + val testCompileTaskProvider = unitTestVariant.javaCompileProvider inputClasspath.from(testCompileTaskProvider.map { it.destinationDirectory }) // Similarly, if the Kotlin plugin is configured, find the test sources Kotlin compile task // and add its output directory to our input classpath file collection. project.plugins.withType(KotlinBasePluginWrapper::class.java) { - @Suppress("UNCHECKED_CAST") - val kotlinCompileTaskProvider = project.tasks.named( - "compile${unitTestVariant.name.capitalize()}Kotlin" - ) as TaskProvider<KotlinCompile> + val kotlinCompileTaskProvider = getCompileKotlin(unitTestVariant, project) inputClasspath.from(kotlinCompileTaskProvider.map { it.destinationDirectory }) } @@ -157,11 +151,12 @@ abstract class HiltTransformTestClassesTask @Inject constructor( ) // Map the transform task's output to a file collection. val outputFileCollection = - project.objects.fileCollection().from(hiltTransformProvider.map { it.outputDir }) + project.files(hiltTransformProvider.map { it.outputDir }) // Configure test classpath by appending the transform output file collection to the start of // the test classpath so they override the original ones. This also makes test task (the one // that runs the tests) depend on the transform task. + @Suppress("UNCHECKED_CAST") val testTaskProvider = project.tasks.named( "test${unitTestVariant.name.capitalize()}" diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/AggregatedPackagesTransform.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/AggregatedPackagesTransform.kt new file mode 100644 index 000000000..6dfa84264 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/AggregatedPackagesTransform.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 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.hilt.android.plugin.util + +import dagger.hilt.android.plugin.root.AggregatedAnnotation +import java.io.ByteArrayOutputStream +import java.io.File +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream +import org.gradle.api.artifacts.transform.CacheableTransform +import org.gradle.api.artifacts.transform.InputArtifact +import org.gradle.api.artifacts.transform.TransformAction +import org.gradle.api.artifacts.transform.TransformOutputs +import org.gradle.api.artifacts.transform.TransformParameters +import org.gradle.api.file.FileSystemLocation +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Classpath + +/** + * A transform that outputs classes and jars containing only classes in key aggregating Hilt + * packages that are used to pass dependencies between compilation units. + */ +@CacheableTransform +abstract class AggregatedPackagesTransform : TransformAction<TransformParameters.None> { + // TODO(danysantiago): Make incremental by using InputChanges and try to use @CompileClasspath + @get:Classpath + @get:InputArtifact + abstract val inputArtifactProvider: Provider<FileSystemLocation> + + override fun transform(outputs: TransformOutputs) { + val input = inputArtifactProvider.get().asFile + when { + input.isFile -> transformFile(outputs, input) + input.isDirectory -> input.walkTopDown().filter { it.isFile }.forEach { + transformFile(outputs, it) + } + else -> error("File/directory does not exist: ${input.absolutePath}") + } + } + + private fun transformFile(outputs: TransformOutputs, file: File) { + if (file.isJarFile()) { + var atLeastOneEntry = false + // TODO(danysantiago): This is an in-memory buffer stream, consider using a temp file. + val tmpOutputStream = ByteArrayOutputStream() + ZipOutputStream(tmpOutputStream).use { outputStream -> + ZipInputStream(file.inputStream()).forEachZipEntry { inputStream, inputEntry -> + if (inputEntry.isClassFile()) { + val parentDirectory = inputEntry.name.substringBeforeLast('/') + val match = AggregatedAnnotation.AGGREGATED_PACKAGES.any { aggregatedPackage -> + parentDirectory.endsWith(aggregatedPackage) + } + if (match) { + outputStream.putNextEntry(ZipEntry(inputEntry.name)) + inputStream.copyTo(outputStream) + outputStream.closeEntry() + atLeastOneEntry = true + } + } + } + } + if (atLeastOneEntry) { + outputs.file(JAR_NAME).outputStream().use { tmpOutputStream.writeTo(it) } + } + } else if (file.isClassFile()) { + // If transforming a file, check if the parent directory matches one of the known aggregated + // packages structure. File and Path APIs are used to avoid OS-specific issues when comparing + // paths. + val parentDirectory: File = file.parentFile + val match = AggregatedAnnotation.AGGREGATED_PACKAGES.any { aggregatedPackage -> + parentDirectory.endsWith(aggregatedPackage) + } + if (match) { + outputs.file(file) + } + } + } + + companion object { + // The output file name containing classes in the aggregated packages. + val JAR_NAME = "hiltAggregated.jar" + } +} diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/AndroidGradleCompat.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/AndroidGradleCompat.kt new file mode 100644 index 000000000..209005ad3 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/AndroidGradleCompat.kt @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2021 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.hilt.android.plugin.util + +import com.android.build.api.AndroidPluginVersion +import com.android.build.api.instrumentation.AsmClassVisitorFactory +import com.android.build.api.instrumentation.FramesComputationMode +import com.android.build.api.instrumentation.InstrumentationParameters +import com.android.build.api.instrumentation.InstrumentationScope +import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.api.variant.ApplicationVariant +import com.android.build.api.variant.Component +import com.android.build.api.variant.LibraryVariant +import com.android.build.api.variant.Variant +import org.gradle.api.Project + +/** + * Compatibility version of [com.android.build.api.variant.AndroidComponentsExtension] + * - In AGP 4.2 its package is 'com.android.build.api.extension' + * - In AGP 7.0 its packages is 'com.android.build.api.variant' + */ +sealed class AndroidComponentsExtensionCompat { + + /** + * A combined compatibility function of + * [com.android.build.api.variant.AndroidComponentsExtension.onVariants] that includes also + * [AndroidTest] and [UnitTest] variants. + */ + abstract fun onAllVariants(block: (ComponentCompat) -> Unit) + + class Api70Impl( + private val actual: AndroidComponentsExtension<*, *, *> + ) : AndroidComponentsExtensionCompat() { + + private val componentInit: (component: Component) -> ComponentCompat = { + if (actual.pluginVersion < AndroidPluginVersion(7, 2)) { + ComponentCompat.Api70Impl(it) + } else { + ComponentCompat.Api72Impl(it) + } + } + + override fun onAllVariants(block: (ComponentCompat) -> Unit) { + actual.onVariants { variant -> + // Use reflection to get the AndroidTest component out of the variant because a binary + // incompatible change was introduced in AGP 7.0-beta05 that changed the return type of the + // method. + fun ApplicationVariant.getAndroidTest() = + this::class.java.getDeclaredMethod("getAndroidTest").invoke(this) as? Component + fun LibraryVariant.getAndroidTest() = + this::class.java.getDeclaredMethod("getAndroidTest").invoke(this) as? Component + block.invoke(componentInit(variant)) + when (variant) { + is ApplicationVariant -> variant.getAndroidTest() + is LibraryVariant -> variant.getAndroidTest() + else -> null + }?.let { block.invoke(componentInit(it)) } + // Use reflection too to get the UnitTest component since in 7.2 + // com.android.build.api.component.UnitTest was removed and replaced by + // com.android.build.api.variant.UnitTest causing the return type of Variant#getUnitTest() + // to change and break ABI. + fun Variant.getUnitTest() = + this::class.java.getDeclaredMethod("getUnitTest").invoke(this) as? Component + variant.getUnitTest()?.let { block.invoke(componentInit(it)) } + } + } + } + + class Api42Impl(private val actual: Any) : AndroidComponentsExtensionCompat() { + + private val extensionClazz = + Class.forName("com.android.build.api.extension.AndroidComponentsExtension") + + private val variantSelectorClazz = + Class.forName("com.android.build.api.extension.VariantSelector") + + override fun onAllVariants(block: (ComponentCompat) -> Unit) { + val selector = extensionClazz.getDeclaredMethod("selector").invoke(actual) + val allSelector = variantSelectorClazz.getDeclaredMethod("all").invoke(selector) + val wrapFunction: (Any) -> Unit = { + block.invoke(ComponentCompat.Api42Impl(it)) + } + listOf("onVariants", "androidTests", "unitTests").forEach { methodName -> + extensionClazz.getDeclaredMethod( + methodName, variantSelectorClazz, Function1::class.java + ).invoke(actual, allSelector, wrapFunction) + } + } + } + + companion object { + fun getAndroidComponentsExtension(project: Project): AndroidComponentsExtensionCompat { + return if ( + findClass("com.android.build.api.variant.AndroidComponentsExtension") != null + ) { + val actualExtension = project.extensions.getByType(AndroidComponentsExtension::class.java) + Api70Impl(actualExtension) + } else { + val actualExtension = project.extensions.getByType( + Class.forName("com.android.build.api.extension.AndroidComponentsExtension") + ) + Api42Impl(actualExtension) + } + } + } +} + +/** + * Compatibility version of [com.android.build.api.variant.Component] + * - In AGP 4.2 its package is 'com.android.build.api.component' + * - In AGP 7.0 its packages is 'com.android.build.api.variant' + */ +@Suppress("UnstableApiUsage") // ASM Pipeline APIs +sealed class ComponentCompat { + + /** + * Redeclaration of [com.android.build.api.variant.ComponentIdentity.name] + */ + abstract val name: String + + /** + * Redeclaration of [com.android.build.api.variant.Component.transformClassesWith] + */ + abstract fun <ParamT : InstrumentationParameters> transformClassesWith( + classVisitorFactoryImplClass: Class<out AsmClassVisitorFactory<ParamT>>, + scope: InstrumentationScope, + instrumentationParamsConfig: (ParamT) -> Unit + ) + + /** + * Redeclaration of [com.android.build.api.variant.Component.setAsmFramesComputationMode] + */ + abstract fun setAsmFramesComputationMode(mode: FramesComputationMode) + + class Api72Impl(private val component: Component) : ComponentCompat() { + + override val name: String + get() = component.name + + override fun <ParamT : InstrumentationParameters> transformClassesWith( + classVisitorFactoryImplClass: Class<out AsmClassVisitorFactory<ParamT>>, + scope: InstrumentationScope, + instrumentationParamsConfig: (ParamT) -> Unit + ) { + component.instrumentation.transformClassesWith( + classVisitorFactoryImplClass, scope, instrumentationParamsConfig + ) + } + + override fun setAsmFramesComputationMode(mode: FramesComputationMode) { + component.instrumentation.setAsmFramesComputationMode(mode) + } + } + + class Api70Impl(private val component: Component) : ComponentCompat() { + + override val name: String + get() = component.name + + override fun <ParamT : InstrumentationParameters> transformClassesWith( + classVisitorFactoryImplClass: Class<out AsmClassVisitorFactory<ParamT>>, + scope: InstrumentationScope, + instrumentationParamsConfig: (ParamT) -> Unit + ) { + Component::class.java.getDeclaredMethod( + "transformClassesWith", + Class::class.java, + InstrumentationScope::class.java, + Function1::class.java + ).invoke(component, classVisitorFactoryImplClass, scope, instrumentationParamsConfig) + } + + override fun setAsmFramesComputationMode(mode: FramesComputationMode) { + Component::class.java.getDeclaredMethod( + "setAsmFramesComputationMode", + FramesComputationMode::class.java + ).invoke(component, mode) + } + } + + class Api42Impl(private val actual: Any) : ComponentCompat() { + + private val componentClazz = Class.forName("com.android.build.api.component.Component") + + override val name: String + get() = componentClazz.getMethod("getName").invoke(actual) as String + + override fun <ParamT : InstrumentationParameters> transformClassesWith( + classVisitorFactoryImplClass: Class<out AsmClassVisitorFactory<ParamT>>, + scope: InstrumentationScope, + instrumentationParamsConfig: (ParamT) -> Unit + ) { + componentClazz.getDeclaredMethod( + "transformClassesWith", + Class::class.java, InstrumentationScope::class.java, Function1::class.java + ).invoke(actual, classVisitorFactoryImplClass, scope, instrumentationParamsConfig) + } + + override fun setAsmFramesComputationMode(mode: FramesComputationMode) { + componentClazz.getDeclaredMethod( + "setAsmFramesComputationMode", FramesComputationMode::class.java + ).invoke(actual, mode) + } + } +} + +fun findClass(fqName: String) = try { + Class.forName(fqName) +} catch (ex: ClassNotFoundException) { + null +} diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/CopyTransform.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/CopyTransform.kt index 39a2d3a89..1b1b58342 100644 --- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/CopyTransform.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/CopyTransform.kt @@ -9,9 +9,10 @@ import org.gradle.api.file.FileSystemLocation import org.gradle.api.provider.Provider import org.gradle.api.tasks.Classpath -// A transform that registers the input jar file as an output and thus changing from one artifact -// type to another. -// TODO: Improve to only copy classes that need to be visible by Hilt & Dagger. +/** + * A transform that registers the input file (usually a jar or a class) as an output and thus + * changing from one artifact type to another. + */ @CacheableTransform abstract class CopyTransform : TransformAction<TransformParameters.None> { @get:Classpath diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/Files.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/Files.kt index e5a101e34..c83d6354d 100644 --- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/Files.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/Files.kt @@ -1,14 +1,59 @@ package dagger.hilt.android.plugin.util -import com.android.SdkConstants import java.io.File +import java.io.InputStream +import java.util.Properties import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream +import org.gradle.api.Project /* Checks if a file is a .class file. */ -fun File.isClassFile() = this.isFile && this.extension == SdkConstants.EXT_CLASS +fun File.isClassFile() = this.isFile && this.extension == "class" /* Checks if a Zip entry is a .class file. */ -fun ZipEntry.isClassFile() = !this.isDirectory && this.name.endsWith(SdkConstants.DOT_CLASS) +fun ZipEntry.isClassFile() = !this.isDirectory && this.name.endsWith(".class") -/* CHecks if a file is a .jar file. */ -fun File.isJarFile() = this.isFile && this.extension == SdkConstants.EXT_JAR +/* Checks if a file is a .jar file. */ +fun File.isJarFile() = this.isFile && this.extension == "jar" + +/* Executes the given [block] function over each [ZipEntry] in this [ZipInputStream]. */ +fun ZipInputStream.forEachZipEntry(block: (InputStream, ZipEntry) -> Unit) = use { + var inputEntry = nextEntry + while (inputEntry != null) { + block(this, inputEntry) + inputEntry = nextEntry + } +} + +/* Gets the Android Sdk Path. */ +fun Project.getSdkPath(): File { + val localPropsFile = rootProject.projectDir.resolve("local.properties") + if (localPropsFile.exists()) { + val localProps = Properties() + localPropsFile.inputStream().use { localProps.load(it) } + val localSdkDir = localProps["sdk.dir"]?.toString() + if (localSdkDir != null) { + val sdkDirectory = File(localSdkDir) + if (sdkDirectory.isDirectory) { + return sdkDirectory + } + } + } + return getSdkPathFromEnvironmentVariable() +} + +private fun getSdkPathFromEnvironmentVariable(): File { + // Check for environment variables, in the order AGP checks. + listOf("ANDROID_HOME", "ANDROID_SDK_ROOT").forEach { + val envValue = System.getenv(it) + if (envValue != null) { + val sdkDirectory = File(envValue) + if (sdkDirectory.isDirectory) { + return sdkDirectory + } + } + } + // Only print the error for SDK ROOT since ANDROID_HOME is deprecated but we first check + // it because it is prioritized according to the documentation. + error("ANDROID_SDK_ROOT environment variable is not set") +} diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt index 339c83e1a..cb442b234 100644 --- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt @@ -46,11 +46,5 @@ internal data class SimpleAGPVersion( return SimpleAGPVersion(parts[0].toInt(), parts[1].toInt()) } - - private fun findClass(fqName: String) = try { - Class.forName(fqName) - } catch (ex: ClassNotFoundException) { - null - } } } diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/Model.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/Strings.kt index 4fe168db8..82acc26ec 100644 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/Model.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/Strings.kt @@ -14,11 +14,14 @@ * limitations under the License. */ -package dagger.hilt.android.example.gradle.simpleKotlin +package dagger.hilt.android.plugin.util -import javax.inject.Qualifier +import java.util.Locale -/** Qualifies bindings relating to [android.os.Build.MODEL]. */ -@Qualifier -@Retention(AnnotationRetention.RUNTIME) -internal annotation class Model +fun String.capitalize( + locale: Locale = Locale.getDefault() +): String = if (isNotEmpty() && this[0].isLowerCase()) { + substring(0, 1).uppercase(locale) + substring(1) +} else { + this +} diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/Tasks.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/Tasks.kt new file mode 100644 index 000000000..b46c3d60a --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/Tasks.kt @@ -0,0 +1,16 @@ +package dagger.hilt.android.plugin.util + +import org.gradle.api.Project +import org.gradle.api.tasks.TaskProvider +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +/** + * Gets [KotlinCompile] task of an Android variant. + */ +@Suppress("UNCHECKED_CAST") +internal fun getCompileKotlin( + @Suppress("DEPRECATION") variant: com.android.build.gradle.api.BaseVariant, + project: Project +) = project.tasks.named( + "compile${variant.name.capitalize()}Kotlin" +) as TaskProvider<KotlinCompile> diff --git a/java/dagger/hilt/android/plugin/src/main/resources/META-INF/gradle-plugins/com.google.dagger.hilt.android.properties b/java/dagger/hilt/android/plugin/src/main/resources/META-INF/gradle-plugins/com.google.dagger.hilt.android.properties new file mode 100644 index 000000000..5d2b9dfc8 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/main/resources/META-INF/gradle-plugins/com.google.dagger.hilt.android.properties @@ -0,0 +1 @@ +implementation-class=dagger.hilt.android.plugin.HiltGradlePlugin
\ No newline at end of file diff --git a/java/dagger/hilt/android/plugin/src/test/data/flavored-project/app/build.gradle b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/app/build.gradle new file mode 100644 index 000000000..c42caf9c4 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/app/build.gradle @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 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. + */ + +plugins { + id 'com.android.application' + id 'dagger.hilt.android.plugin' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.2" + + flavorDimensions 'api', 'version' + productFlavors { + demo { + dimension 'version' + } + full { + dimension 'version' + } + minApi24 { + dimension 'api' + minSdkVersion '24' + versionNameSuffix "-minApi24" + } + minApi21 { + dimension "api" + minSdkVersion '21' + versionNameSuffix "-minApi21" + } + } + + defaultConfig { + applicationId "simple.app" + minSdkVersion 21 + targetSdkVersion 30 + } + + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } +} + +dependencies { + implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT' + annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT' + implementation project(':feature') +} + +hilt { + enableAggregatingTask = true +}
\ No newline at end of file diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/res/values/strings.xml b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/app/src/main/AndroidManifest.xml index c1e4f272f..0ce231537 100644 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/res/values/strings.xml +++ b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ <!-- - ~ Copyright (C) 2020 The Dagger Authors. + ~ Copyright (C) 2021 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. @@ -13,11 +13,8 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<resources> - <!--The app name [CHAR_LIMIT=40]--> - <string name="app_name">Simple Hilt Kotlin Android App</string> - - <!--The greeting message [CHAR_LIMIT=100]--> - <string name="welcome">Hello, %1$s! You are on build %2$s.</string> -</resources> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="simple.app"> + <application android:name=".SimpleApp" android:label="Flavored App"> + </application> +</manifest>
\ No newline at end of file diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/java/dagger/hilt/android/gradleConfigCache/App.kt b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/app/src/main/java/simple/app/SimpleApp.java index 1cbd4b062..39cf48ecf 100644 --- a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/java/dagger/hilt/android/gradleConfigCache/App.kt +++ b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/app/src/main/java/simple/app/SimpleApp.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2021 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. @@ -14,10 +14,12 @@ * limitations under the License. */ -package dagger.hilt.android.gradleConfigCache +package simple.app; -import androidx.multidex.MultiDexApplication -import dagger.hilt.android.HiltAndroidApp +import android.app.Application; +import dagger.hilt.android.HiltAndroidApp; +/** Just an application. */ @HiltAndroidApp -class App : MultiDexApplication() +public class SimpleApp extends Application { +}
\ No newline at end of file diff --git a/java/dagger/example/gradle/android/simple/build.gradle b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/build.gradle index 50f8f0baa..ec95fa9dd 100644 --- a/java/dagger/example/gradle/android/simple/build.gradle +++ b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Dagger Authors. + * Copyright (C) 2021 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. @@ -17,19 +17,14 @@ buildscript { repositories { google() - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + mavenCentral() } } allprojects { repositories { - google() - jcenter() - mavenCentral() - mavenLocal() + mavenLocal() + google() + mavenCentral() } -} - +}
\ No newline at end of file diff --git a/java/dagger/hilt/android/plugin/src/test/data/flavored-project/feature/build.gradle b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/feature/build.gradle new file mode 100644 index 000000000..218a22434 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/feature/build.gradle @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021 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. + */ + +plugins { + id 'com.android.library' + id 'dagger.hilt.android.plugin' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.2" + + flavorDimensions 'api', 'version' + productFlavors { + demo { + dimension 'version' + } + full { + dimension 'version' + } + minApi24 { + dimension 'api' + minSdkVersion '24' + versionNameSuffix "-minApi24" + } + minApi21 { + dimension "api" + minSdkVersion '21' + versionNameSuffix "-minApi21" + } + } + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 30 + } + + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } +} + +dependencies { + implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT' + annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT' +}
\ No newline at end of file diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/res/values/strings.xml b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/feature/src/main/AndroidManifest.xml index 05afdbf55..fc219c877 100644 --- a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/res/values/strings.xml +++ b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/feature/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ <!-- - ~ Copyright (C) 2020 The Dagger Authors. + ~ Copyright (C) 2021 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. @@ -13,8 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<resources> - <!--The app name [CHAR_LIMIT=40]--> - <string name="app_name">Gradle Configuration Cache App</string> -</resources> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="simple.library"> +</manifest>
\ No newline at end of file diff --git a/java/dagger/hilt/android/example/gradle/simple/feature/src/main/java/dagger/hilt/android/example/gradle/simple/feature/FeatureCounter.kt b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/feature/src/main/java/simple/library/LibraryCode.java index 0fd3db369..e3bb9dd80 100644 --- a/java/dagger/hilt/android/example/gradle/simple/feature/src/main/java/dagger/hilt/android/example/gradle/simple/feature/FeatureCounter.kt +++ b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/feature/src/main/java/simple/library/LibraryCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2021 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. @@ -14,6 +14,8 @@ * limitations under the License. */ -package dagger.hilt.android.example.gradle.simple.feature +package simple.library; -class FeatureCounter(var count: Int) +/** Just some 'Library' code */ +public class LibraryCode { +}
\ No newline at end of file diff --git a/java/dagger/hilt/android/plugin/src/test/data/flavored-project/gradle.properties b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/gradle.properties new file mode 100644 index 000000000..5bac8ac50 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/java/dagger/hilt/android/plugin/src/test/data/flavored-project/settings.gradle b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/settings.gradle new file mode 100644 index 000000000..d3cc2b0ae --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/test/data/flavored-project/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name='Flavored Project' +include ':app' +include ':feature'
\ No newline at end of file diff --git a/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/main/java/simple/Activity1.java b/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/main/java/simple/Activity1.java index a6e5d936d..4c7ba88d3 100644 --- a/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/main/java/simple/Activity1.java +++ b/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/main/java/simple/Activity1.java @@ -21,8 +21,8 @@ import dagger.hilt.android.AndroidEntryPoint; import javax.inject.Inject; /** Just an activity. */ -@AndroidEntryPoint(AppCompatActivity.class) -public class Activity1 extends Hilt_Activity1 { +@AndroidEntryPoint +public class Activity1 extends AppCompatActivity { @Inject String data; // Insert-change diff --git a/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/main/java/simple/Activity2.java b/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/main/java/simple/Activity2.java index f7793be08..9c271c673 100644 --- a/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/main/java/simple/Activity2.java +++ b/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/main/java/simple/Activity2.java @@ -21,8 +21,8 @@ import dagger.hilt.android.AndroidEntryPoint; import javax.inject.Inject; /** Just an activity. */ -@AndroidEntryPoint(AppCompatActivity.class) -public class Activity2 extends Hilt_Activity2 { +@AndroidEntryPoint +public class Activity2 extends AppCompatActivity { @Inject String data; // Insert-change diff --git a/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/main/java/simple/SimpleApp.java b/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/main/java/simple/SimpleApp.java index 5a9224194..ded0b7dd6 100644 --- a/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/main/java/simple/SimpleApp.java +++ b/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/main/java/simple/SimpleApp.java @@ -20,7 +20,7 @@ import android.app.Application; import dagger.hilt.android.HiltAndroidApp; /** Just an application. */ -@HiltAndroidApp(Application.class) -public class SimpleApp extends Hilt_SimpleApp { +@HiltAndroidApp +public class SimpleApp extends Application { // Insert-change } diff --git a/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/test/java/simple/Test1.java b/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/test/java/simple/Test1.java new file mode 100644 index 000000000..0ed15c753 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/test/java/simple/Test1.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 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 simple; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.components.SingletonComponent; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** Just a test. */ +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +@Config(application = HiltTestApplication.class) +public class Test1 { + + @Rule + public HiltAndroidRule hiltRule = new HiltAndroidRule(this); + + @Test + public void emptyTest() { + + } + + // Insert-change + + /** An inner test module. */ + @Module + @InstallIn(SingletonComponent.class) + public static final class TestModule { + @Provides + public static double provideDouble() { + return 0.0; + } + } + +}
\ No newline at end of file diff --git a/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/test/java/simple/Test2.java b/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/test/java/simple/Test2.java new file mode 100644 index 000000000..a812ed667 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/test/data/simple-project/src/test/java/simple/Test2.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 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 simple; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.components.SingletonComponent; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** Just a test. */ +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +@Config(application = HiltTestApplication.class) +public class Test2 { + + @Rule + public HiltAndroidRule hiltRule = new HiltAndroidRule(this); + + @Test + public void emptyTest() { + + } + + /** An inner test module. */ + @Module + @InstallIn(SingletonComponent.class) + public static final class TestModule { + @Provides + public static double provideDouble() { + return 0.0; + } + } +}
\ No newline at end of file diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/BuildCacheTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/BuildCacheTest.kt new file mode 100644 index 000000000..517032d24 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/BuildCacheTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020 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. + */ + +import java.util.UUID +import org.gradle.testkit.runner.TaskOutcome.FROM_CACHE +import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +// Test that verifies the hilt class transform does not break the Gradle's remote build cache. +@RunWith(Parameterized::class) +class BuildCacheTest(private val enableAggregatingTask: Boolean) { + @get:Rule val gradleHomeFolder = TemporaryFolder() + + @get:Rule val firstProjectDir = TemporaryFolder() + lateinit var firstGradleRunner: GradleTestRunner + + @get:Rule val secondProjectDir = TemporaryFolder() + lateinit var secondGradleRunner: GradleTestRunner + + private val testId = UUID.randomUUID().toString() + + @Before + fun setup() { + firstGradleRunner = createGradleRunner(firstProjectDir) + secondGradleRunner = createGradleRunner(secondProjectDir) + } + + private fun createGradleRunner(folder: TemporaryFolder): GradleTestRunner { + val gradleRunner = GradleTestRunner(folder) + gradleRunner.addDependencies( + "implementation 'androidx.appcompat:appcompat:1.1.0'", + "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'", + "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'", + ) + gradleRunner.runAdditionalTasks("--build-cache") + gradleRunner.addSrc( + srcPath = "minimal/MyApp.java", + srcContent = + """ + package minimal; + + import android.app.Application; + + @dagger.hilt.android.HiltAndroidApp + public class MyApp extends Application { + // random id inserted into the code to ensure that the first is never a cache hit and the + // second run always is + public static String RANDOM_ID = "$testId"; + } + """.trimIndent() + ) + gradleRunner.setAppClassName(".MyApp") + gradleRunner.addHiltOption("enableAggregatingTask = $enableAggregatingTask") + return gradleRunner + } + + // Verifies that library B and library C injected classes are available in the root classpath. + @Test + fun test_buildCacheHitOnRelocatedProject() { + val firstResult = firstGradleRunner.build() + assertEquals(firstResult.getTask(":transformDebugClassesWithAsm").outcome, SUCCESS) + + val secondResult = secondGradleRunner.build() + val cacheableTasks: List<String> = mutableListOf<String>().apply { + add(":checkDebugAarMetadata") + add(":checkDebugDuplicateClasses") + add(":compileDebugJavaWithJavac") + add(":compressDebugAssets") + add(":extractDeepLinksDebug") + add(":generateDebugBuildConfig") + add(":generateDebugResValues") + // When aggregating task is enabled, the plugin adds two more tasks that should be + // cacheable. + if (enableAggregatingTask) { + add(":hiltAggregateDepsDebug") + add(":hiltJavaCompileDebug") + } + add(":javaPreCompileDebug") + add(":mergeDebugAssets") + add(":mergeDebugJavaResource") + add(":mergeDebugJniLibFolders") + add(":mergeDebugNativeLibs") + add(":mergeDebugShaders") + add(":mergeExtDexDebug") + add(":mergeLibDexDebug") + add(":mergeProjectDexDebug") + add(":processDebugManifestForPackage") + add(":transformDebugClassesWithAsm") + add(":validateSigningDebug") + add(":writeDebugAppMetadata") + add(":writeDebugSigningConfigVersions") + } + + val tasksFromCache = + secondResult.tasks.filter { it.outcome == FROM_CACHE }.map { it.path }.sorted() + assertEquals(cacheableTasks, tasksFromCache) + } + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "enableAggregatingTask = {0}") + fun parameters() = listOf(false, true) + } +} diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/CompileClasspathTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/CompileClasspathTest.kt index 614125081..d61b37011 100644 --- a/java/dagger/hilt/android/plugin/src/test/kotlin/CompileClasspathTest.kt +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/CompileClasspathTest.kt @@ -21,8 +21,12 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import org.junit.runners.Parameterized -class CompileClasspathTest { +// Test that verifies compile classpath aggregation done by the plugin. +@RunWith(Parameterized::class) +class CompileClasspathTest(private val pluginFlagName: String) { @get:Rule val testProjectDir = TemporaryFolder() @@ -31,11 +35,13 @@ class CompileClasspathTest { @Before fun setup() { gradleRunner = GradleTestRunner(testProjectDir) - gradleRunner.addAndroidOption( - "lintOptions.checkReleaseBuilds = false" - ) + if (pluginFlagName == "enableExperimentalClasspathAggregation") { + gradleRunner.addAndroidOption( + "lintOptions.checkReleaseBuilds = false" + ) + } gradleRunner.addHiltOption( - "enableExperimentalClasspathAggregation = true" + "$pluginFlagName = true" ) gradleRunner.addDependencies( "implementation 'androidx.appcompat:appcompat:1.1.0'", @@ -108,4 +114,13 @@ class CompileClasspathTest { val assembleTask = result.getTask(":assembleDebug") assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome) } + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun parameters() = listOf( + "enableExperimentalClasspathAggregation", + "enableAggregatingTask" + ) + } } diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/CrossCompilationRootValidationTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/CrossCompilationRootValidationTest.kt new file mode 100644 index 000000000..e0848dd56 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/CrossCompilationRootValidationTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2021 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. + */ + +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class CrossCompilationRootValidationTest { + @get:Rule + val testProjectDir = TemporaryFolder() + + lateinit var gradleRunner: GradleTestRunner + + @Before + fun setup() { + gradleRunner = GradleTestRunner(testProjectDir) + gradleRunner.addHiltOption( + "enableAggregatingTask = true" + ) + gradleRunner.addDependencies( + "implementation 'androidx.appcompat:appcompat:1.1.0'", + "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'", + "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'", + "testImplementation 'com.google.dagger:hilt-android-testing:LOCAL-SNAPSHOT'", + "testAnnotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'", + ) + gradleRunner.addSrc( + srcPath = "minimal/MyApp.java", + srcContent = + """ + package minimal; + + import android.app.Application; + + @dagger.hilt.android.HiltAndroidApp + public class MyApp extends Application { } + """.trimIndent() + ) + gradleRunner.setAppClassName(".MyApp") + } + + @Test + fun multipleAppRootsFailure() { + gradleRunner.addSrc( + srcPath = "minimal/MyApp2.java", + srcContent = + """ + package minimal; + + import android.app.Application; + + @dagger.hilt.android.HiltAndroidApp + public class MyApp2 extends Application { } + """.trimIndent() + ) + + val result = gradleRunner.buildAndFail() + assertThat(result.getOutput()).contains( + "Cannot process multiple app roots in the same compilation unit: " + + "minimal.MyApp, minimal.MyApp2" + ) + } + + @Test + fun testRootsAndAppRootsFailure() { + gradleRunner.addTestSrc( + srcPath = "minimal/MyTest.java", + srcContent = + """ + package minimal; + + @dagger.hilt.android.testing.HiltAndroidTest + public class MyTest { } + """.trimIndent() + ) + gradleRunner.addTestSrc( + srcPath = "minimal/BadApp.java", + srcContent = + """ + package minimal; + + import android.app.Application; + + @dagger.hilt.android.HiltAndroidApp + public class BadApp extends Application { } + """.trimIndent() + ) + + gradleRunner.runAdditionalTasks("testDebug") + val result = gradleRunner.buildAndFail() + assertThat(result.getOutput()).contains( + "Cannot process test roots and app roots in the same compilation unit:" + ) + assertThat(result.getOutput()).contains( + "App root in this compilation unit: minimal.BadApp" + ) + assertThat(result.getOutput()).contains( + "Test roots in this compilation unit: minimal.MyTest" + ) + } +} diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/GradleTestRunner.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/GradleTestRunner.kt index 2d58766d8..d32b40eac 100644 --- a/java/dagger/hilt/android/plugin/src/test/kotlin/GradleTestRunner.kt +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/GradleTestRunner.kt @@ -16,6 +16,7 @@ import java.io.File import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.BuildTask import org.gradle.testkit.runner.GradleRunner import org.junit.rules.TemporaryFolder @@ -31,9 +32,11 @@ class GradleTestRunner(val tempFolder: TemporaryFolder) { private var buildFile: File? = null private var gradlePropertiesFile: File? = null private var manifestFile: File? = null + private var additionalTasks = mutableListOf<String>() init { tempFolder.newFolder("src", "main", "java", "minimal") + tempFolder.newFolder("src", "test", "java", "minimal") tempFolder.newFolder("src", "main", "res") } @@ -68,6 +71,12 @@ class GradleTestRunner(val tempFolder: TemporaryFolder) { return tempFolder.newFile("/src/main/java/$srcPath").apply { writeText(srcContent) } } + // Adds a test source file to the project. The source path is relative to 'src/test/java'. + fun addTestSrc(srcPath: String, srcContent: String): File { + File(tempFolder.root, "src/test/java/${srcPath.substringBeforeLast(File.separator)}").mkdirs() + return tempFolder.newFile("/src/test/java/$srcPath").apply { writeText(srcContent) } + } + // Adds a resource file to the project. The source path is relative to 'src/main/res'. fun addRes(resPath: String, resContent: String): File { File(tempFolder.root, "src/main/res/${resPath.substringBeforeLast(File.separator)}").mkdirs() @@ -78,6 +87,10 @@ class GradleTestRunner(val tempFolder: TemporaryFolder) { appClassName = name } + fun runAdditionalTasks(taskName: String) { + additionalTasks.add(taskName) + } + // Executes a Gradle builds and expects it to succeed. fun build(): Result { setupFiles() @@ -104,10 +117,10 @@ class GradleTestRunner(val tempFolder: TemporaryFolder) { buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.2.0-beta04' + classpath 'com.android.tools.build:gradle:4.2.0' } } @@ -137,7 +150,7 @@ class GradleTestRunner(val tempFolder: TemporaryFolder) { repositories { mavenLocal() google() - jcenter() + mavenCentral() } } @@ -184,7 +197,7 @@ class GradleTestRunner(val tempFolder: TemporaryFolder) { private fun createRunner() = GradleRunner.create() .withProjectDir(tempFolder.root) - .withArguments("assembleDebug", "--stacktrace") + .withArguments(listOf("--stacktrace", "assembleDebug") + additionalTasks) .withPluginClasspath() // .withDebug(true) // Add this line to enable attaching a debugger to the gradle test invocation .forwardOutput() @@ -194,6 +207,9 @@ class GradleTestRunner(val tempFolder: TemporaryFolder) { private val projectRoot: File, private val buildResult: BuildResult ) { + + val tasks: List<BuildTask> get() = buildResult.tasks + // Finds a task by name. fun getTask(name: String) = buildResult.task(name) ?: error("Task '$name' not found.") diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt index cab50aab0..0b60bfe4f 100644 --- a/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt @@ -23,13 +23,16 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith +import org.junit.runners.Parameterized /** * Tests to verify Gradle annotation processor incremental compilation. * * To run these tests first deploy artifacts to local maven via util/install-local-snapshot.sh. */ -class IncrementalProcessorTest { +@RunWith(Parameterized::class) +class IncrementalProcessorTest(private val incapMode: String) { @get:Rule val testProjectDir = TemporaryFolder() @@ -43,6 +46,8 @@ class IncrementalProcessorTest { private lateinit var srcActivity2: File private lateinit var srcModule1: File private lateinit var srcModule2: File + private lateinit var srcTest1: File + private lateinit var srcTest2: File // Generated source files private lateinit var genHiltApp: File @@ -56,8 +61,15 @@ class IncrementalProcessorTest { private lateinit var genActivityInjectorDeps2: File private lateinit var genModuleDeps1: File private lateinit var genModuleDeps2: File + private lateinit var genComponentTreeDeps: File private lateinit var genHiltComponents: File private lateinit var genDaggerHiltApplicationComponent: File + private lateinit var genTest1ComponentTreeDeps: File + private lateinit var genTest2ComponentTreeDeps: File + private lateinit var genTest1HiltComponents: File + private lateinit var genTest2HiltComponents: File + private lateinit var genTest1DaggerHiltApplicationComponent: File + private lateinit var genTest2DaggerHiltApplicationComponent: File // Compiled classes private lateinit var classSrcApp: File @@ -65,6 +77,8 @@ class IncrementalProcessorTest { private lateinit var classSrcActivity2: File private lateinit var classSrcModule1: File private lateinit var classSrcModule2: File + private lateinit var classSrcTest1: File + private lateinit var classSrcTest2: File private lateinit var classGenHiltApp: File private lateinit var classGenHiltActivity1: File private lateinit var classGenHiltActivity2: File @@ -76,8 +90,15 @@ class IncrementalProcessorTest { private lateinit var classGenActivityInjectorDeps2: File private lateinit var classGenModuleDeps1: File private lateinit var classGenModuleDeps2: File + private lateinit var classGenComponentTreeDeps: File private lateinit var classGenHiltComponents: File private lateinit var classGenDaggerHiltApplicationComponent: File + private lateinit var classGenTest1ComponentTreeDeps: File + private lateinit var classGenTest2ComponentTreeDeps: File + private lateinit var classGenTest1HiltComponents: File + private lateinit var classGenTest2HiltComponents: File + private lateinit var classGenTest1DaggerHiltApplicationComponent: File + private lateinit var classGenTest2DaggerHiltApplicationComponent: File // Timestamps of files private lateinit var fileToTimestampMap: Map<File, Long> @@ -87,6 +108,19 @@ class IncrementalProcessorTest { private lateinit var unchangedFiles: Set<File> private lateinit var deletedFiles: Set<File> + private val compileTaskName = if (incapMode == ISOLATING_MODE) { + ":hiltJavaCompileDebug" + } else { + ":compileDebugJavaWithJavac" + } + private val testCompileTaskName = if (incapMode == ISOLATING_MODE) { + ":hiltJavaCompileDebugUnitTest" + } else { + ":compileDebugUnitTestJavaWithJavac" + } + private val aggregatingTaskName = ":hiltAggregateDepsDebug" + private val testAggregatingTaskName = ":hiltAggregateDepsDebugUnitTest" + @Before fun setup() { val projectRoot = testProjectDir.root @@ -99,15 +133,16 @@ class IncrementalProcessorTest { buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'com.android.tools.build:gradle:4.2.0' } } plugins { id 'com.android.application' + id 'dagger.hilt.android.plugin' } android { @@ -118,6 +153,11 @@ class IncrementalProcessorTest { applicationId "hilt.simple" minSdkVersion 21 targetSdkVersion 30 + javaCompileOptions { + annotationProcessorOptions { + arguments += ["dagger.hilt.shareTestComponents" : "true"] + } + } } compileOptions { @@ -129,7 +169,7 @@ class IncrementalProcessorTest { repositories { mavenLocal() google() - jcenter() + mavenCentral() } dependencies { @@ -138,84 +178,194 @@ class IncrementalProcessorTest { annotationProcessor 'com.google.dagger:dagger-compiler:LOCAL-SNAPSHOT' implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT' annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT' + + testImplementation 'junit:junit:4.12' + testImplementation 'androidx.test.ext:junit:1.1.2' + testImplementation 'androidx.test:runner:1.3.0' + testImplementation 'org.robolectric:robolectric:4.4' + testImplementation 'com.google.dagger:hilt-android-testing:LOCAL-SNAPSHOT' + testAnnotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT' + } + + hilt { + enableAggregatingTask = ${if (incapMode == ISOLATING_MODE) "true" else "false"} } """.trimIndent() ) + // Compute directory paths + val defaultGenSrcDir = "build/generated/ap_generated_sources/debug/out/" + val testDefaultGenSrcDir = "build/generated/ap_generated_sources/debugUnitTest/out/" + fun getComponentTreeDepsGenSrcDir(variant: String) = if (incapMode == ISOLATING_MODE) { + "build/generated/hilt/component_trees/$variant/" + } else { + "build/generated/ap_generated_sources/$variant/out/" + } + val componentTreeDepsGenSrcDir = getComponentTreeDepsGenSrcDir("debug") + val testComponentTreeDepsGenSrcDir = getComponentTreeDepsGenSrcDir("debugUnitTest") + fun getRootGenSrcDir(variant: String) = if (incapMode == ISOLATING_MODE) { + "build/generated/hilt/component_sources/$variant/" + } else { + "build/generated/ap_generated_sources/$variant/out/" + } + val rootGenSrcDir = getRootGenSrcDir("debug") + val testRootGenSrcDir = getRootGenSrcDir("debugUnitTest") + val defaultClassesDir = "build/intermediates/javac/debug/classes" + val testDefaultClassesDir = "build/intermediates/javac/debugUnitTest/classes" + fun getRootClassesDir(variant: String) = if (incapMode == ISOLATING_MODE) { + "build/intermediates/hilt/component_classes/$variant/" + } else { + "build/intermediates/javac/$variant/classes" + } + val rootClassesDir = getRootClassesDir("debug") + val testRootClassesDir = getRootClassesDir("debugUnitTest") + // Compute file paths - srcApp = File(projectRoot, "$SRC_DIR/simple/SimpleApp.java") - srcActivity1 = File(projectRoot, "$SRC_DIR/simple/Activity1.java") - srcActivity2 = File(projectRoot, "$SRC_DIR/simple/Activity2.java") - srcModule1 = File(projectRoot, "$SRC_DIR/simple/Module1.java") - srcModule2 = File(projectRoot, "$SRC_DIR/simple/Module2.java") - - genHiltApp = File(projectRoot, "$GEN_SRC_DIR/simple/Hilt_SimpleApp.java") - genHiltActivity1 = File(projectRoot, "$GEN_SRC_DIR/simple/Hilt_Activity1.java") - genHiltActivity2 = File(projectRoot, "$GEN_SRC_DIR/simple/Hilt_Activity2.java") - genAppInjector = File(projectRoot, "$GEN_SRC_DIR/simple/SimpleApp_GeneratedInjector.java") - genActivityInjector1 = File(projectRoot, "$GEN_SRC_DIR/simple/Activity1_GeneratedInjector.java") - genActivityInjector2 = File(projectRoot, "$GEN_SRC_DIR/simple/Activity2_GeneratedInjector.java") + srcApp = File(projectRoot, "$MAIN_SRC_DIR/simple/SimpleApp.java") + srcActivity1 = File(projectRoot, "$MAIN_SRC_DIR/simple/Activity1.java") + srcActivity2 = File(projectRoot, "$MAIN_SRC_DIR/simple/Activity2.java") + srcModule1 = File(projectRoot, "$MAIN_SRC_DIR/simple/Module1.java") + srcModule2 = File(projectRoot, "$MAIN_SRC_DIR/simple/Module2.java") + srcTest1 = File(projectRoot, "$TEST_SRC_DIR/simple/Test1.java") + srcTest2 = File(projectRoot, "$TEST_SRC_DIR/simple/Test2.java") + + genHiltApp = File(projectRoot, "$rootGenSrcDir/simple/Hilt_SimpleApp.java") + genHiltActivity1 = File(projectRoot, "$defaultGenSrcDir/simple/Hilt_Activity1.java") + genHiltActivity2 = File(projectRoot, "$defaultGenSrcDir/simple/Hilt_Activity2.java") + genAppInjector = File(projectRoot, "$defaultGenSrcDir/simple/SimpleApp_GeneratedInjector.java") + genActivityInjector1 = + File(projectRoot, "$defaultGenSrcDir/simple/Activity1_GeneratedInjector.java") + genActivityInjector2 = + File(projectRoot, "$defaultGenSrcDir/simple/Activity2_GeneratedInjector.java") genAppInjectorDeps = File( projectRoot, - "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_SimpleApp_GeneratedInjector.java" + "$defaultGenSrcDir/hilt_aggregated_deps/_simple_SimpleApp_GeneratedInjector.java" ) genActivityInjectorDeps1 = File( projectRoot, - "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_Activity1_GeneratedInjector.java" + "$defaultGenSrcDir/hilt_aggregated_deps/_simple_Activity1_GeneratedInjector.java" ) genActivityInjectorDeps2 = File( projectRoot, - "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_Activity2_GeneratedInjector.java" + "$defaultGenSrcDir/hilt_aggregated_deps/_simple_Activity2_GeneratedInjector.java" ) genModuleDeps1 = File( projectRoot, - "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_Module1.java" + "$defaultGenSrcDir/hilt_aggregated_deps/_simple_Module1.java" ) - genModuleDeps2 = File(projectRoot, "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_Module2.java") - genHiltComponents = File(projectRoot, "$GEN_SRC_DIR/simple/SimpleApp_HiltComponents.java") + genModuleDeps2 = + File(projectRoot, "$defaultGenSrcDir/hilt_aggregated_deps/_simple_Module2.java") + genComponentTreeDeps = + File(projectRoot, "$componentTreeDepsGenSrcDir/simple/SimpleApp_ComponentTreeDeps.java") + genHiltComponents = File(projectRoot, "$rootGenSrcDir/simple/SimpleApp_HiltComponents.java") genDaggerHiltApplicationComponent = File( projectRoot, - "$GEN_SRC_DIR/simple/DaggerSimpleApp_HiltComponents_SingletonC.java" - ) - - classSrcApp = File(projectRoot, "$CLASS_DIR/simple/SimpleApp.class") - classSrcActivity1 = File(projectRoot, "$CLASS_DIR/simple/Activity1.class") - classSrcActivity2 = File(projectRoot, "$CLASS_DIR/simple/Activity2.class") - classSrcModule1 = File(projectRoot, "$CLASS_DIR/simple/Module1.class") - classSrcModule2 = File(projectRoot, "$CLASS_DIR/simple/Module2.class") - classGenHiltApp = File(projectRoot, "$CLASS_DIR/simple/Hilt_SimpleApp.class") - classGenHiltActivity1 = File(projectRoot, "$CLASS_DIR/simple/Hilt_Activity1.class") - classGenHiltActivity2 = File(projectRoot, "$CLASS_DIR/simple/Hilt_Activity2.class") - classGenAppInjector = File(projectRoot, "$CLASS_DIR/simple/SimpleApp_GeneratedInjector.class") + "$rootGenSrcDir/simple/DaggerSimpleApp_HiltComponents_SingletonC.java" + ) + genTest1ComponentTreeDeps = File( + projectRoot, + testComponentTreeDepsGenSrcDir + + "/dagger/hilt/android/internal/testing/root/Test1_ComponentTreeDeps.java" + ) + genTest2ComponentTreeDeps = File( + projectRoot, + testComponentTreeDepsGenSrcDir + + "/dagger/hilt/android/internal/testing/root/Test2_ComponentTreeDeps.java" + ) + genTest1HiltComponents = File( + projectRoot, + "$testRootGenSrcDir/dagger/hilt/android/internal/testing/root/Test1_HiltComponents.java" + ) + genTest2HiltComponents = File( + projectRoot, + "$testRootGenSrcDir/dagger/hilt/android/internal/testing/root/Test2_HiltComponents.java" + ) + genTest1DaggerHiltApplicationComponent = File( + projectRoot, + testRootGenSrcDir + + "/dagger/hilt/android/internal/testing/root/DaggerTest1_HiltComponents_SingletonC.java" + ) + genTest2DaggerHiltApplicationComponent = File( + projectRoot, + testRootGenSrcDir + + "/dagger/hilt/android/internal/testing/root/DaggerTest2_HiltComponents_SingletonC.java" + ) + + classSrcApp = File(projectRoot, "$defaultClassesDir/simple/SimpleApp.class") + classSrcActivity1 = File(projectRoot, "$defaultClassesDir/simple/Activity1.class") + classSrcActivity2 = File(projectRoot, "$defaultClassesDir/simple/Activity2.class") + classSrcModule1 = File(projectRoot, "$defaultClassesDir/simple/Module1.class") + classSrcModule2 = File(projectRoot, "$defaultClassesDir/simple/Module2.class") + classSrcTest1 = File(projectRoot, "$testDefaultClassesDir/simple/Test1.class") + classSrcTest2 = File(projectRoot, "$testDefaultClassesDir/simple/Test2.class") + classGenHiltApp = File(projectRoot, "$rootClassesDir/simple/Hilt_SimpleApp.class") + classGenHiltActivity1 = File(projectRoot, "$defaultClassesDir/simple/Hilt_Activity1.class") + classGenHiltActivity2 = File(projectRoot, "$defaultClassesDir/simple/Hilt_Activity2.class") + classGenAppInjector = + File(projectRoot, "$defaultClassesDir/simple/SimpleApp_GeneratedInjector.class") classGenActivityInjector1 = File( projectRoot, - "$CLASS_DIR/simple/Activity1_GeneratedInjector.class" + "$defaultClassesDir/simple/Activity1_GeneratedInjector.class" ) classGenActivityInjector2 = File( projectRoot, - "$CLASS_DIR/simple/Activity2_GeneratedInjector.class" + "$defaultClassesDir/simple/Activity2_GeneratedInjector.class" ) classGenAppInjectorDeps = File( projectRoot, - "$CLASS_DIR/hilt_aggregated_deps/_simple_SimpleApp_GeneratedInjector.class" + "$defaultClassesDir/hilt_aggregated_deps/_simple_SimpleApp_GeneratedInjector.class" ) classGenActivityInjectorDeps1 = File( projectRoot, - "$CLASS_DIR/hilt_aggregated_deps/_simple_Activity1_GeneratedInjector.class" + "$defaultClassesDir/hilt_aggregated_deps/_simple_Activity1_GeneratedInjector.class" ) classGenActivityInjectorDeps2 = File( projectRoot, - "$CLASS_DIR/hilt_aggregated_deps/_simple_Activity2_GeneratedInjector.class" + "$defaultClassesDir/hilt_aggregated_deps/_simple_Activity2_GeneratedInjector.class" + ) + classGenModuleDeps1 = + File(projectRoot, "$defaultClassesDir/hilt_aggregated_deps/_simple_Module1.class") + classGenModuleDeps2 = + File(projectRoot, "$defaultClassesDir/hilt_aggregated_deps/_simple_Module2.class") + classGenComponentTreeDeps = File( + projectRoot, + "$rootClassesDir/simple/SimpleApp_ComponentTreeDeps.class" ) - classGenModuleDeps1 = File(projectRoot, "$CLASS_DIR/hilt_aggregated_deps/_simple_Module1.class") - classGenModuleDeps2 = File(projectRoot, "$CLASS_DIR/hilt_aggregated_deps/_simple_Module2.class") classGenHiltComponents = File( projectRoot, - "$CLASS_DIR/simple/SimpleApp_HiltComponents.class" + "$rootClassesDir/simple/SimpleApp_HiltComponents.class" ) classGenDaggerHiltApplicationComponent = File( projectRoot, - "$CLASS_DIR/simple/DaggerSimpleApp_HiltComponents_SingletonC.class" + "$rootClassesDir/simple/DaggerSimpleApp_HiltComponents_SingletonC.class" + ) + classGenTest1ComponentTreeDeps = File( + projectRoot, + testRootClassesDir + + "/dagger/hilt/android/internal/testing/root/Test1_ComponentTreeDeps.class" + ) + classGenTest2ComponentTreeDeps = File( + projectRoot, + testRootClassesDir + + "/dagger/hilt/android/internal/testing/root/Test2_ComponentTreeDeps.class" + ) + classGenTest1HiltComponents = File( + projectRoot, + "$testRootClassesDir/dagger/hilt/android/internal/testing/root/Test1_HiltComponents.class" + ) + classGenTest2HiltComponents = File( + projectRoot, + "$testRootClassesDir/dagger/hilt/android/internal/testing/root/Test2_HiltComponents.class" + ) + classGenTest1DaggerHiltApplicationComponent = File( + projectRoot, + testRootClassesDir + + "/dagger/hilt/android/internal/testing/root/DaggerTest1_HiltComponents_SingletonC.class" + ) + classGenTest2DaggerHiltApplicationComponent = File( + projectRoot, + testRootClassesDir + + "/dagger/hilt/android/internal/testing/root/DaggerTest2_HiltComponents_SingletonC.class" ) } @@ -224,51 +374,58 @@ class IncrementalProcessorTest { // This test verifies the results of the first full (non-incremental) build. The other tests // verify the results of the second incremental build based on different change scenarios. val result = runFullBuild() - expect.that(result.task(COMPILE_TASK)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + expect.that(result.task(compileTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) // Check annotation processing outputs assertFilesExist( - genHiltApp, - genHiltActivity1, - genHiltActivity2, - genAppInjector, - genActivityInjector1, - genActivityInjector2, - genAppInjectorDeps, - genActivityInjectorDeps1, - genActivityInjectorDeps2, - genModuleDeps1, - genModuleDeps2, - genHiltComponents, - genDaggerHiltApplicationComponent + listOf( + genHiltApp, + genHiltActivity1, + genHiltActivity2, + genAppInjector, + genActivityInjector1, + genActivityInjector2, + genAppInjectorDeps, + genActivityInjectorDeps1, + genActivityInjectorDeps2, + genModuleDeps1, + genModuleDeps2, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) ) // Check compilation outputs assertFilesExist( - classSrcApp, - classSrcActivity1, - classSrcActivity2, - classSrcModule1, - classSrcModule2, - classGenHiltApp, - classGenHiltActivity1, - classGenHiltActivity2, - classGenAppInjector, - classGenActivityInjector1, - classGenActivityInjector2, - classGenAppInjectorDeps, - classGenActivityInjectorDeps1, - classGenActivityInjectorDeps2, - classGenModuleDeps1, - classGenModuleDeps2, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent + listOf( + classSrcApp, + classSrcActivity1, + classSrcActivity2, + classSrcModule1, + classSrcModule2, + classGenHiltApp, + classGenHiltActivity1, + classGenHiltActivity2, + classGenAppInjector, + classGenActivityInjector1, + classGenActivityInjector2, + classGenAppInjectorDeps, + classGenActivityInjectorDeps1, + classGenActivityInjectorDeps2, + classGenModuleDeps1, + classGenModuleDeps2, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) ) } @Test - fun changeActivitySource() { + fun changeActivitySource_addPublicMethod() { runFullBuild() + val componentTreeDepsFullBuild = genComponentTreeDeps.readText(Charsets.UTF_8) // Change Activity 1 source searchAndReplace( @@ -282,43 +439,158 @@ class IncrementalProcessorTest { ) val result = runIncrementalBuild() - expect.that(result.task(COMPILE_TASK)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + expect.that(result.task(compileTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) // Check annotation processing outputs // * Only activity 1 sources are re-generated, isolation in modules and from other activities - // * Root classes along with components are always re-generated (aggregated processor) - assertChangedFiles( - FileType.JAVA, - genHiltApp, - genHiltActivity1, - genAppInjector, - genActivityInjector1, - genAppInjectorDeps, - genActivityInjectorDeps1, - genHiltComponents, - genDaggerHiltApplicationComponent - ) + val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { + // * Aggregating task did not run, no change in deps + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) + // * Components are re-generated due to a recompilation of a dep + listOf( + genHiltApp, // Re-gen because components got re-gen + genHiltActivity1, + genActivityInjector1, + genActivityInjectorDeps1, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } else { + // * Root classes along with components are always re-generated (aggregated processor) + listOf( + genHiltApp, + genHiltActivity1, + genAppInjector, + genActivityInjector1, + genAppInjectorDeps, + genActivityInjectorDeps1, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } + assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) + + val componentTreeDepsIncrementalBuild = genComponentTreeDeps.readText(Charsets.UTF_8) + expect.withMessage("Full build") + .that(componentTreeDepsFullBuild) + .isEqualTo(componentTreeDepsIncrementalBuild) // Check compilation outputs // * Gen sources from activity 1 are re-compiled - // * All aggregating processor gen sources are re-compiled - assertChangedFiles( - FileType.CLASS, - classSrcActivity1, - classGenHiltApp, - classGenHiltActivity1, - classGenAppInjector, - classGenActivityInjector1, - classGenAppInjectorDeps, - classGenActivityInjectorDeps1, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent + val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { + listOf( + classSrcActivity1, + classGenHiltApp, + classGenHiltActivity1, + classGenActivityInjector1, + classGenActivityInjectorDeps1, + classGenComponentTreeDeps, // Re-compiled because reference to activity injector + classGenHiltComponents, + classGenDaggerHiltApplicationComponent, + ) + } else { + // * All aggregating processor gen sources are re-compiled + listOf( + classSrcActivity1, + classGenHiltApp, + classGenHiltActivity1, + classGenAppInjector, + classGenActivityInjector1, + classGenAppInjectorDeps, + classGenActivityInjectorDeps1, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } + assertChangedFiles(FileType.CLASS, recompiledClassFiles) + } + + @Test + fun changeActivitySource_addPrivateMethod() { + runFullBuild() + val componentTreeDepsFullBuild = genComponentTreeDeps.readText(Charsets.UTF_8) + + // Change Activity 1 source + searchAndReplace( + srcActivity1, "// Insert-change", + """ + private void foo() { } + """.trimIndent() ) + + val result = runIncrementalBuild() + val expectedOutcome = if (incapMode == ISOLATING_MODE) { + // In isolating mode, changes that do not affect ABI will not cause re-compilation. + TaskOutcome.UP_TO_DATE + } else { + TaskOutcome.SUCCESS + } + expect.that(result.task(compileTaskName)!!.outcome).isEqualTo(expectedOutcome) + + // Check annotation processing outputs + // * Only activity 1 sources are re-generated, isolation in modules and from other activities + val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { + // * Aggregating task did not run, no change in deps + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) + listOf( + genHiltActivity1, + genActivityInjector1, + genActivityInjectorDeps1, + ) + } else { + // * Root classes along with components are always re-generated (aggregated processor) + listOf( + genHiltApp, + genHiltActivity1, + genAppInjector, + genActivityInjector1, + genAppInjectorDeps, + genActivityInjectorDeps1, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } + assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) + + val componentTreeDepsIncrementalBuild = genComponentTreeDeps.readText(Charsets.UTF_8) + expect.withMessage("Full build") + .that(componentTreeDepsFullBuild) + .isEqualTo(componentTreeDepsIncrementalBuild) + + // Check compilation outputs + // * Gen sources from activity 1 are re-compiled + val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { + listOf( + classSrcActivity1, + classGenHiltActivity1, + classGenActivityInjector1, + classGenActivityInjectorDeps1 + ) + } else { + // * All aggregating processor gen sources are re-compiled + listOf( + classSrcActivity1, + classGenHiltApp, + classGenHiltActivity1, + classGenAppInjector, + classGenActivityInjector1, + classGenAppInjectorDeps, + classGenActivityInjectorDeps1, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } + assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @Test fun changeModuleSource() { runFullBuild() + val componentTreeDepsFullBuild = genComponentTreeDeps.readText(Charsets.UTF_8) // Change Module 1 source searchAndReplace( @@ -332,39 +604,70 @@ class IncrementalProcessorTest { ) val result = runIncrementalBuild() - expect.that(result.task(COMPILE_TASK)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + expect.that(result.task(compileTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) // Check annotation processing outputs // * Only module 1 sources are re-generated, isolation from other modules - // * Root classes along with components are always re-generated (aggregated processor) - assertChangedFiles( - FileType.JAVA, - genHiltApp, - genAppInjector, - genAppInjectorDeps, - genModuleDeps1, - genHiltComponents, - genDaggerHiltApplicationComponent - ) + val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { + // * Aggregating task did not run, no change in deps + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) + // * Components are re-generated due to a recompilation of a dep + listOf( + genHiltApp, // Re-generated because components got re-generated + genModuleDeps1, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } else { + // * Root classes along with components are always re-generated (aggregated processor) + listOf( + genHiltApp, + genAppInjector, + genAppInjectorDeps, + genModuleDeps1, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } + assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) + + val componentTreeDepsIncrementalBuild = genComponentTreeDeps.readText(Charsets.UTF_8) + expect.withMessage("Full build") + .that(componentTreeDepsFullBuild) + .isEqualTo(componentTreeDepsIncrementalBuild) // Check compilation outputs // * Gen sources from module 1 are re-compiled - // * All aggregating processor gen sources are re-compiled - assertChangedFiles( - FileType.CLASS, - classSrcModule1, - classGenHiltApp, - classGenAppInjector, - classGenAppInjectorDeps, - classGenModuleDeps1, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent - ) + val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { + listOf( + classSrcModule1, + classGenHiltApp, + classGenModuleDeps1, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } else { + // * All aggregating processor gen sources are re-compiled + listOf( + classSrcModule1, + classGenHiltApp, + classGenAppInjector, + classGenAppInjectorDeps, + classGenModuleDeps1, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } + assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @Test fun changeAppSource() { runFullBuild() + val componentTreeDepsFullBuild = genComponentTreeDeps.readText(Charsets.UTF_8) // Change Application source searchAndReplace( @@ -378,31 +681,63 @@ class IncrementalProcessorTest { ) val result = runIncrementalBuild() - expect.that(result.task(COMPILE_TASK)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + expect.that(result.task(compileTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) // Check annotation processing outputs // * No modules or activities (or any other non-root) classes should be generated - // * Root classes along with components are always re-generated (aggregated processor) - assertChangedFiles( - FileType.JAVA, - genHiltApp, - genAppInjector, - genAppInjectorDeps, - genHiltComponents, - genDaggerHiltApplicationComponent - ) + val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { + // * Aggregating task did not run, no change in deps + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) + // * Components are re-generated due to a recompilation of a dep + listOf( + genHiltApp, // Re-generated because components got re-generated + genAppInjector, + genAppInjectorDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } else { + // * Root classes along with components are always re-generated (aggregated processor) + listOf( + genHiltApp, + genAppInjector, + genAppInjectorDeps, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } + assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) + + val componentTreeDepsIncrementalBuild = genComponentTreeDeps.readText(Charsets.UTF_8) + expect.withMessage("Full build") + .that(componentTreeDepsFullBuild) + .isEqualTo(componentTreeDepsIncrementalBuild) // Check compilation outputs - // * All aggregating processor gen sources are re-compiled - assertChangedFiles( - FileType.CLASS, - classSrcApp, // re-compiles because superclass re-compiled - classGenHiltApp, - classGenAppInjector, - classGenAppInjectorDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent - ) + val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { + listOf( + classSrcApp, + classGenHiltApp, + classGenAppInjector, + classGenAppInjectorDeps, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } else { + // * All aggregating processor gen sources are re-compiled + listOf( + classSrcApp, + classGenHiltApp, + classGenAppInjector, + classGenAppInjectorDeps, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } + assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @Test @@ -412,43 +747,70 @@ class IncrementalProcessorTest { srcActivity2.delete() val result = runIncrementalBuild() - expect.that(result.task(COMPILE_TASK)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + expect.that(result.task(compileTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) // Check annotation processing outputs // * All related gen classes from activity 2 should be deleted // * Unrelated activities and modules are in isolation and should be unchanged // * Root classes along with components are always re-generated (aggregated processor) assertDeletedFiles( - genHiltActivity2, - genActivityInjector2, - genActivityInjectorDeps2 - ) - assertChangedFiles( - FileType.JAVA, - genHiltApp, - genAppInjector, - genAppInjectorDeps, - genHiltComponents, - genDaggerHiltApplicationComponent + listOf( + genHiltActivity2, + genActivityInjector2, + genActivityInjectorDeps2 + ) ) + val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { + // * Aggregating task ran due to a change in dep + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + // * Components are re-generated since there was a change in dep + listOf( + genHiltApp, // Re-generated because components got re-generated + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } else { + listOf( + genHiltApp, + genAppInjector, + genAppInjectorDeps, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } + assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) // Check compilation outputs // * All compiled classes from activity 2 should be deleted // * Unrelated activities and modules are in isolation and should be unchanged assertDeletedFiles( - classSrcActivity2, - classGenHiltActivity2, - classGenActivityInjector2, - classGenActivityInjectorDeps2 - ) - assertChangedFiles( - FileType.CLASS, - classGenHiltApp, - classGenAppInjector, - classGenAppInjectorDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent + listOf( + classSrcActivity2, + classGenHiltActivity2, + classGenActivityInjector2, + classGenActivityInjectorDeps2 + ) ) + val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { + listOf( + classGenHiltApp, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } else { + listOf( + classGenHiltApp, + classGenAppInjector, + classGenAppInjectorDeps, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } + assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @Test @@ -458,39 +820,277 @@ class IncrementalProcessorTest { srcModule2.delete() val result = runIncrementalBuild() - expect.that(result.task(COMPILE_TASK)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + expect.that(result.task(compileTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) // Check annotation processing outputs // * All related gen classes from module 2 should be deleted // * Unrelated activities and modules are in isolation and should be unchanged - // * Root classes along with components are always re-generated (aggregated processor) + assertDeletedFiles( - genModuleDeps2 - ) - assertChangedFiles( - FileType.JAVA, - genHiltApp, - genAppInjector, - genAppInjectorDeps, - genHiltComponents, - genDaggerHiltApplicationComponent + listOf(genModuleDeps2) ) + val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { + // * Aggregating task ran due to a change in dep + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + // * Components are re-generated since there was a change in dep + listOf( + genHiltApp, // Re-generated because components got re-generated + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } else { + // * Root classes along with components are always re-generated (aggregated processor) + listOf( + genHiltApp, + genAppInjector, + genAppInjectorDeps, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } + assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) // Check compilation outputs // * All compiled classes from module 2 should be deleted // * Unrelated activities and modules are in isolation and should be unchanged assertDeletedFiles( - classSrcModule2, - classGenModuleDeps2 + listOf( + classSrcModule2, + classGenModuleDeps2 + ) ) - assertChangedFiles( - FileType.CLASS, - classGenHiltApp, - classGenAppInjector, - classGenAppInjectorDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent + val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { + listOf( + classGenHiltApp, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } else { + listOf( + classGenHiltApp, + classGenAppInjector, + classGenAppInjectorDeps, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } + assertChangedFiles(FileType.CLASS, recompiledClassFiles) + } + + @Test + fun addNewSource() { + runFullBuild() + + val newSource = File(testProjectDir.root, "$MAIN_SRC_DIR/simple/Foo.java") + newSource.writeText( + """ + package simple; + + public class Foo { } + """.trimIndent() ) + + val result = runIncrementalBuild() + val expectedOutcome = if (incapMode == ISOLATING_MODE) { + // In isolating mode, component compile task does not re-compile. + TaskOutcome.UP_TO_DATE + } else { + TaskOutcome.SUCCESS + } + expect.that(result.task(compileTaskName)!!.outcome).isEqualTo(expectedOutcome) + + val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { + // * Aggregating task did not run, no change in deps + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) + // * Non-DI related source causes no files to be generated + emptyList() + } else { + // * Root classes are always re-generated (aggregated processor) + listOf( + genHiltApp, + genAppInjector, + genAppInjectorDeps, + ) + } + assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) + + val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { + emptyList() + } else { + listOf( + classGenHiltApp, + classGenAppInjector, + classGenAppInjectorDeps, + ) + } + assertChangedFiles(FileType.CLASS, recompiledClassFiles) + } + + @Test + fun firstTestFullBuild() { + val result = runFullTestBuild() + expect.that(result.task(testCompileTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + + assertFilesExist( + listOf( + genTest1ComponentTreeDeps, + genTest2ComponentTreeDeps, + genTest1HiltComponents, + genTest2HiltComponents, + genTest1DaggerHiltApplicationComponent, + genTest2DaggerHiltApplicationComponent, + ) + ) + + assertFilesExist( + listOf( + classSrcTest1, + classSrcTest2, + classGenTest1ComponentTreeDeps, + classGenTest2ComponentTreeDeps, + classGenTest1HiltComponents, + classGenTest2HiltComponents, + classGenTest1DaggerHiltApplicationComponent, + classGenTest2DaggerHiltApplicationComponent, + ) + ) + } + + @Test + fun changeTestSource_addPublicMethod() { + runFullTestBuild() + val test1ComponentTreeDepsFullBuild = genTest1ComponentTreeDeps.readText(Charsets.UTF_8) + val test2ComponentTreeDepsFullBuild = genTest2ComponentTreeDeps.readText(Charsets.UTF_8) + + // Change Test 1 source + searchAndReplace( + srcTest1, "// Insert-change", + """ + @Test + public void newTest() { } + """.trimIndent() + ) + + val result = runIncrementalTestBuild() + expect.that(result.task(testCompileTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + + // Check annotation processing outputs + // * Unrelated test components should be unchanged + + val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { + listOf( + genTest1HiltComponents, + genTest1DaggerHiltApplicationComponent, + ) + } else { + listOf( + genTest1ComponentTreeDeps, + genTest2ComponentTreeDeps, + genTest1HiltComponents, + genTest2HiltComponents, + genTest1DaggerHiltApplicationComponent, + genTest2DaggerHiltApplicationComponent, + ) + } + assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) + + val test1ComponentTreeDepsIncrementalBuild = genTest1ComponentTreeDeps.readText(Charsets.UTF_8) + val test2ComponentTreeDepsIncrementalBuild = genTest2ComponentTreeDeps.readText(Charsets.UTF_8) + expect.withMessage("Full build") + .that(test1ComponentTreeDepsFullBuild) + .isEqualTo(test1ComponentTreeDepsIncrementalBuild) + expect.withMessage("Full build") + .that(test2ComponentTreeDepsFullBuild) + .isEqualTo(test2ComponentTreeDepsIncrementalBuild) + + val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { + listOf( + classSrcTest1, + classGenTest1ComponentTreeDeps, + classGenTest1HiltComponents, + classGenTest1DaggerHiltApplicationComponent, + ) + } else { + listOf( + classSrcTest1, + classGenTest1ComponentTreeDeps, + classGenTest2ComponentTreeDeps, + classGenTest1HiltComponents, + classGenTest2HiltComponents, + classGenTest1DaggerHiltApplicationComponent, + classGenTest2DaggerHiltApplicationComponent, + ) + } + assertChangedFiles(FileType.CLASS, recompiledClassFiles) + } + + @Test + fun changeTestSource_addPrivateMethod() { + runFullTestBuild() + val test1ComponentTreeDepsFullBuild = genTest1ComponentTreeDeps.readText(Charsets.UTF_8) + val test2ComponentTreeDepsFullBuild = genTest2ComponentTreeDeps.readText(Charsets.UTF_8) + + // Change Test 1 source + searchAndReplace( + srcTest1, "// Insert-change", + """ + private void newMethod() { } + """.trimIndent() + ) + + val result = runIncrementalTestBuild() + val expectedOutcome = if (incapMode == ISOLATING_MODE) { + // In isolating mode, changes that do not affect ABI will not cause re-compilation. + TaskOutcome.UP_TO_DATE + } else { + TaskOutcome.SUCCESS + } + expect.that(result.task(testCompileTaskName)!!.outcome).isEqualTo(expectedOutcome) + + // Check annotation processing outputs + // * Unrelated test components should be unchanged + + val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { + emptyList() + } else { + listOf( + genTest1ComponentTreeDeps, + genTest2ComponentTreeDeps, + genTest1HiltComponents, + genTest2HiltComponents, + genTest1DaggerHiltApplicationComponent, + genTest2DaggerHiltApplicationComponent, + ) + } + assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) + + val test1ComponentTreeDepsIncrementalBuild = genTest1ComponentTreeDeps.readText(Charsets.UTF_8) + val test2ComponentTreeDepsIncrementalBuild = genTest2ComponentTreeDeps.readText(Charsets.UTF_8) + expect.withMessage("Full build") + .that(test1ComponentTreeDepsFullBuild) + .isEqualTo(test1ComponentTreeDepsIncrementalBuild) + expect.withMessage("Full build") + .that(test2ComponentTreeDepsFullBuild) + .isEqualTo(test2ComponentTreeDepsIncrementalBuild) + + val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { + listOf(classSrcTest1) + } else { + listOf( + classSrcTest1, + classGenTest1ComponentTreeDeps, + classGenTest2ComponentTreeDeps, + classGenTest1HiltComponents, + classGenTest2HiltComponents, + classGenTest1DaggerHiltApplicationComponent, + classGenTest2DaggerHiltApplicationComponent, + ) + } + assertChangedFiles(FileType.CLASS, recompiledClassFiles) } private fun runGradleTasks(vararg args: String): BuildResult { @@ -503,16 +1103,30 @@ class IncrementalProcessorTest { } private fun runFullBuild(): BuildResult { - val result = runGradleTasks(CLEAN_TASK, COMPILE_TASK) + val result = runGradleTasks(CLEAN_TASK, compileTaskName) + recordTimestamps() + return result + } + + private fun runFullTestBuild(): BuildResult { + runFullBuild() + val result = runIncrementalTestBuild() recordTimestamps() return result } private fun runIncrementalBuild(): BuildResult { - val result = runGradleTasks(COMPILE_TASK) + val result = runGradleTasks(compileTaskName) recordFileChanges() return result } + + private fun runIncrementalTestBuild(): BuildResult { + val result = runGradleTasks(testCompileTaskName) + recordFileChanges() + return result + } + private fun recordTimestamps() { val files = listOf( genHiltApp, @@ -526,13 +1140,22 @@ class IncrementalProcessorTest { genActivityInjectorDeps2, genModuleDeps1, genModuleDeps2, + genComponentTreeDeps, genHiltComponents, genDaggerHiltApplicationComponent, + genTest1ComponentTreeDeps, + genTest2ComponentTreeDeps, + genTest1HiltComponents, + genTest2HiltComponents, + genTest1DaggerHiltApplicationComponent, + genTest2DaggerHiltApplicationComponent, classSrcApp, classSrcActivity1, classSrcActivity2, classSrcModule1, classSrcModule2, + classSrcTest1, + classSrcTest2, classGenHiltApp, classGenHiltActivity1, classGenHiltActivity2, @@ -544,8 +1167,15 @@ class IncrementalProcessorTest { classGenActivityInjectorDeps2, classGenModuleDeps1, classGenModuleDeps2, + classGenComponentTreeDeps, classGenHiltComponents, - classGenDaggerHiltApplicationComponent + classGenDaggerHiltApplicationComponent, + classGenTest1ComponentTreeDeps, + classGenTest2ComponentTreeDeps, + classGenTest1HiltComponents, + classGenTest2HiltComponents, + classGenTest1DaggerHiltApplicationComponent, + classGenTest2DaggerHiltApplicationComponent, ) fileToTimestampMap = mutableMapOf<File, Long>().apply { @@ -567,19 +1197,19 @@ class IncrementalProcessorTest { deletedFiles = fileToTimestampMap.filter { (file, _) -> !file.exists() }.keys } - private fun assertFilesExist(vararg files: File) { + private fun assertFilesExist(files: Collection<File>) { expect.withMessage("Existing files") .that(files.filter { it.exists() }) .containsExactlyElementsIn(files) } - private fun assertChangedFiles(type: FileType, vararg files: File) { + private fun assertChangedFiles(type: FileType, files: Collection<File>) { expect.withMessage("Changed files") .that(changedFiles.filter { it.name.endsWith(type.extension) }) .containsExactlyElementsIn(files) } - private fun assertDeletedFiles(vararg files: File) { + private fun assertDeletedFiles(files: Collection<File>) { expect.withMessage("Deleted files").that(deletedFiles).containsAtLeastElementsIn(files) } @@ -593,11 +1223,19 @@ class IncrementalProcessorTest { } companion object { - private const val SRC_DIR = "src/main/java" - private const val GEN_SRC_DIR = "build/generated/ap_generated_sources/debug/out/" - private const val CLASS_DIR = "build/intermediates/javac/debug/classes" + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun parameters() = listOf( + ISOLATING_MODE, + AGGREGATING_MODE + ) + + private const val ISOLATING_MODE = "isolating" + private const val AGGREGATING_MODE = "aggregating" + + private const val MAIN_SRC_DIR = "src/main/java" + private const val TEST_SRC_DIR = "src/test/java" private const val CLEAN_TASK = ":clean" - private const val COMPILE_TASK = ":compileDebugJavaWithJavac" } } diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/NoTransformTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/NoTransformTest.kt new file mode 100644 index 000000000..cb67c2c12 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/NoTransformTest.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 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. + */ + +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class NoTransformTest { + + @get:Rule + val testProjectDir = TemporaryFolder() + + lateinit var gradleRunner: GradleTestRunner + + @Before + fun setup() { + gradleRunner = GradleTestRunner(testProjectDir) + } + + // Simple functional test to verify transformation. + @Test + fun testAssemble() { + gradleRunner.addDependencies( + "implementation 'androidx.appcompat:appcompat:1.1.0'", + "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'", + "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'" + ) + gradleRunner.addAndroidOption( + "buildFeatures.buildConfig = false" + ) + + val result = gradleRunner.build() + val assembleTask = result.getTask(":assembleDebug") + Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome) + } +} diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/TransformTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/TransformTest.kt index 0b8400214..d45dc3ec4 100644 --- a/java/dagger/hilt/android/plugin/src/test/kotlin/TransformTest.kt +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/TransformTest.kt @@ -14,11 +14,12 @@ * limitations under the License. */ +import com.google.common.truth.Truth.assertThat import java.io.DataInputStream import java.io.FileInputStream import javassist.bytecode.ByteArray import javassist.bytecode.ClassFile -import junit.framework.Assert.assertEquals +import javassist.bytecode.SignatureAttribute import org.gradle.testkit.runner.TaskOutcome import org.junit.Assert import org.junit.Before @@ -278,12 +279,12 @@ class TransformTest { gradleRunner.build().let { val assembleTask = it.getTask(TRANSFORM_TASK_NAME) - assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome) + assertThat(assembleTask.outcome).isEqualTo(TaskOutcome.SUCCESS) } gradleRunner.build().let { val assembleTask = it.getTask(TRANSFORM_TASK_NAME) - assertEquals(TaskOutcome.UP_TO_DATE, assembleTask.outcome) + assertThat(assembleTask.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) } gradleRunner.addSrc( @@ -303,7 +304,94 @@ class TransformTest { val result = gradleRunner.build() val assembleTask = result.getTask(TRANSFORM_TASK_NAME) - assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome) + assertThat(assembleTask.outcome).isEqualTo(TaskOutcome.SUCCESS) + } + + // Verifies the Signature attribute in the ClassFile is updated when the superclass uses type + // variables or parameterized types. + // See: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.9 + @Test + fun testTransform_genericSuperclass() { + gradleRunner.addDependencies( + "implementation 'androidx.appcompat:appcompat:1.1.0'", + "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'", + "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'" + ) + + gradleRunner.addSrc( + srcPath = "minimal/BaseActivity.java", + srcContent = + """ + package minimal; + + import androidx.appcompat.app.AppCompatActivity; + + public abstract class BaseActivity<T> extends AppCompatActivity { + } + """.trimIndent() + ) + + gradleRunner.addSrc( + srcPath = "minimal/SimpleActivity.java", + srcContent = + """ + package minimal; + + @dagger.hilt.android.AndroidEntryPoint + public class SimpleActivity extends BaseActivity<String> { + } + """.trimIndent() + ) + + gradleRunner.addSrc( + srcPath = "minimal/BasicActivityThing.java", + srcContent = + """ + package minimal; + + public class BasicActivityThing { + } + """.trimIndent() + ) + + gradleRunner.addSrc( + srcPath = "minimal/BasicActivity.java", + srcContent = + """ + package minimal; + + @dagger.hilt.android.AndroidEntryPoint + public class BasicActivity extends BaseActivity<BasicActivityThing> { + } + """.trimIndent() + ) + + val result = gradleRunner.build() + val assembleTask = result.getTask(":assembleDebug") + Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome) + + val transformedClass1 = result.getTransformedFile("minimal/SimpleActivity.class") + FileInputStream(transformedClass1).use { fileInput -> + ClassFile(DataInputStream(fileInput)).let { classFile -> + Assert.assertEquals("minimal.Hilt_SimpleActivity", classFile.superclass) + val signatureAttr = classFile.getAttribute(SignatureAttribute.tag) as SignatureAttribute + Assert.assertEquals( + "Lminimal/Hilt_SimpleActivity<Ljava/lang/String;>;", + signatureAttr.signature + ) + } + } + + val transformedClass2 = result.getTransformedFile("minimal/BasicActivity.class") + FileInputStream(transformedClass2).use { fileInput -> + ClassFile(DataInputStream(fileInput)).let { classFile -> + val signatureAttr = classFile.getAttribute(SignatureAttribute.tag) as SignatureAttribute + Assert.assertEquals( + "Lminimal/Hilt_BasicActivity<Lminimal/BasicActivityThing;>;", + signatureAttr.signature + ) + } + } } companion object { diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/VariantsConfigurationTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/VariantsConfigurationTest.kt new file mode 100644 index 000000000..fb4c9c3b2 --- /dev/null +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/VariantsConfigurationTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 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. + */ + +import com.google.common.truth.Expect +import java.io.File +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +// Verifies the aggregated task is configured correctly in a multi-module flavored project. +class VariantsConfigurationTest { + @get:Rule + val testProjectDir = TemporaryFolder() + + @get:Rule + val expect: Expect = Expect.create() + + @Before + fun setup() { + val projectRoot = testProjectDir.root + File("src/test/data/flavored-project").copyRecursively(projectRoot) + } + + @Test + fun verifyFlavorConfiguration_demoDebug() { + val result = runGradleTasks(":app:assembleMinApi21DemoDebug") + expect.that(result.task(":app:assembleMinApi21DemoDebug")!!.outcome) + .isEqualTo(TaskOutcome.SUCCESS) + } + + @Test + fun verifyFlavorConfiguration_fullRelease() { + val result = runGradleTasks(":app:assembleMinApi24FullRelease") + expect.that(result.task(":app:assembleMinApi24FullRelease")!!.outcome) + .isEqualTo(TaskOutcome.SUCCESS) + } + + private fun runGradleTasks(vararg args: String): BuildResult { + return GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(*args) + .withPluginClasspath() + .forwardOutput() + .build() + } +} diff --git a/java/dagger/hilt/android/processor/BUILD b/java/dagger/hilt/android/processor/BUILD index ccf21033d..869e3e23c 100644 --- a/java/dagger/hilt/android/processor/BUILD +++ b/java/dagger/hilt/android/processor/BUILD @@ -66,9 +66,11 @@ gen_maven_artifact( "//java/dagger/hilt/processor/internal/generatesrootinput:generates_root_inputs", "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib", "//java/dagger/hilt/processor/internal/originatingelement:processor_lib", - "//java/dagger/hilt/processor/internal/root:processor_lib", + "//java/dagger/hilt/processor/internal/root:component_tree_deps_processor_lib", + "//java/dagger/hilt/processor/internal/root:root_processor_lib", "//java/dagger/hilt/processor/internal/root:root_metadata", "//java/dagger/hilt/processor/internal/root:root_type", + "//java/dagger/hilt/processor/internal/root/ir:ir", "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib", "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata", ], @@ -85,6 +87,7 @@ gen_maven_artifact( "javax.inject:javax.inject", "net.ltgt.gradle.incap:incap", "org.jetbrains.kotlin:kotlin-stdlib", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", "org.jetbrains.kotlinx:kotlinx-metadata-jvm", ], javadoc_android_api_level = 30, @@ -95,8 +98,10 @@ gen_maven_artifact( javadoc_srcs = [ "//java/dagger/hilt:hilt_processing_filegroup", ], - shaded_deps = ["@maven//:com_google_auto_auto_common"], - shaded_rules = ["rule com.google.auto.common.** dagger.hilt.android.shaded.auto.common.@1"], + # The shaded deps are added using jarjar, but they won't be shaded until later + # due to: https://github.com/google/dagger/issues/2765. For the shaded rules see + # util/deploy-hilt.sh + shaded_deps = ["//third_party/java/auto:common"], ) filegroup( diff --git a/java/dagger/hilt/android/processor/internal/AndroidClassNames.java b/java/dagger/hilt/android/processor/internal/AndroidClassNames.java index d37092003..2d954481a 100644 --- a/java/dagger/hilt/android/processor/internal/AndroidClassNames.java +++ b/java/dagger/hilt/android/processor/internal/AndroidClassNames.java @@ -58,6 +58,10 @@ public final class AndroidClassNames { get("dagger.hilt.android", "WithFragmentBindings"); public static final ClassName HILT_ANDROID_APP = get("dagger.hilt.android", "HiltAndroidApp"); + public static final ClassName CUSTOM_INJECT = + get("dagger.hilt.android.migration", "CustomInject"); + public static final ClassName CUSTOM_INJECTION = + get("dagger.hilt.android.migration", "CustomInjection"); public static final ClassName OPTIONAL_INJECT = get("dagger.hilt.android.migration", "OptionalInject"); @@ -78,6 +82,9 @@ public final class AndroidClassNames { public static final ClassName VIEW_MODEL_COMPONENT = get("dagger.hilt.android.components", "ViewModelComponent"); + public static final ClassName FRAGMENT_GET_CONTEXT_FIX = + get("dagger.hilt.android.flags", "FragmentGetContextFix"); + public static final ClassName ACTIVITY_COMPONENT_MANAGER = get("dagger.hilt.android.internal.managers", "ActivityComponentManager"); public static final ClassName APPLICATION_COMPONENT_MANAGER = @@ -93,6 +100,8 @@ public final class AndroidClassNames { public static final ClassName VIEW_COMPONENT_MANAGER = get("dagger.hilt.android.internal.managers", "ViewComponentManager"); + public static final ClassName HAS_CUSTOM_INJECT = + get("dagger.hilt.android.internal.migration", "HasCustomInject"); public static final ClassName INJECTED_BY_HILT = get("dagger.hilt.android.internal.migration", "InjectedByHilt"); diff --git a/java/dagger/hilt/android/processor/internal/BUILD b/java/dagger/hilt/android/processor/internal/BUILD index aaf8b8916..dfd73337e 100644 --- a/java/dagger/hilt/android/processor/internal/BUILD +++ b/java/dagger/hilt/android/processor/internal/BUILD @@ -23,7 +23,7 @@ java_library( "AndroidClassNames.java", ], deps = [ - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/javapoet", ], ) @@ -34,8 +34,8 @@ java_library( "MoreTypes.java", ], deps = [ - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", ], ) diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java index 86fbaa712..f933a9030 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java @@ -67,6 +67,7 @@ public final class ActivityGenerator { Generators.addComponentOverride(metadata, builder); Generators.copyLintAnnotations(metadata.element(), builder); + Generators.copySuppressAnnotations(metadata.element(), builder); Generators.addInjectionMethods(metadata, builder); @@ -112,10 +113,19 @@ public final class ActivityGenerator { // this, super.getDefaultViewModelProviderFactory()); // } private MethodSpec getDefaultViewModelProviderFactory() { - return MethodSpec.methodBuilder("getDefaultViewModelProviderFactory") + MethodSpec.Builder builder = MethodSpec.methodBuilder("getDefaultViewModelProviderFactory") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) - .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY) + .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY); + + if (metadata.allowsOptionalInjection()) { + builder + .beginControlFlow("if (!optionalInjectParentUsesHilt(optionalInjectGetParent()))") + .addStatement("return super.getDefaultViewModelProviderFactory()") + .endControlFlow(); + } + + return builder .addStatement( "return $T.getActivityFactory(this, super.getDefaultViewModelProviderFactory())", AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES) diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java index c94f6a977..44c7ae1a0 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java @@ -158,8 +158,7 @@ public abstract class AndroidEntryPointMetadata { } private static ClassName generatedClassName(TypeElement element) { - String prefix = "Hilt_"; - return Processors.prepend(Processors.getEnclosedClassName(ClassName.get(element)), prefix); + return Processors.prepend(Processors.getEnclosedClassName(ClassName.get(element)), "Hilt_"); } private static final ImmutableSet<ClassName> HILT_ANNOTATION_NAMES = diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java index cd3290b48..c755bbd1a 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java @@ -16,12 +16,14 @@ package dagger.hilt.android.processor.internal.androidentrypoint; +import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableSet; import dagger.hilt.android.processor.internal.AndroidClassNames; import dagger.hilt.processor.internal.BaseProcessor; +import dagger.hilt.processor.internal.ProcessorErrors; import java.util.Set; import javax.annotation.processing.Processor; import javax.lang.model.element.Element; @@ -54,7 +56,29 @@ public final class AndroidEntryPointProcessor extends BaseProcessor { new InjectorEntryPointGenerator(getProcessingEnv(), metadata).generate(); switch (metadata.androidType()) { case APPLICATION: - new ApplicationGenerator(getProcessingEnv(), metadata).generate(); + // The generated application references the generated component so they must be generated + // in the same build unit. Thus, we only generate the application here if we're using the + // aggregating root processor. If we're using the Hilt Gradle plugin's aggregating task, we + // need to generate the application within ComponentTreeDepsProcessor instead. + if (useAggregatingRootProcessor(getProcessingEnv())) { + // While we could always generate the application in ComponentTreeDepsProcessor, even if + // we're using the aggregating root processor, it can lead to extraneous errors when + // things fail before ComponentTreeDepsProcessor runs so we generate it here to avoid that + new ApplicationGenerator(getProcessingEnv(), metadata).generate(); + } else { + // If we're not using the aggregating root processor, then make sure the root application + // does not extend the generated application directly, and instead uses bytecode injection + ProcessorErrors.checkState( + metadata.requiresBytecodeInjection(), + metadata.element(), + "'enableAggregatingTask=true' cannot be used when the application directly " + + "references the generated Hilt class, %s. Either extend %s directly (relying " + + "on the Gradle plugin described in " + + "https://dagger.dev/hilt/gradle-setup#why-use-the-plugin or set " + + "'enableAggregatingTask=false'.", + metadata.generatedClassName(), + metadata.baseClassName()); + } break; case ACTIVITY: new ActivityGenerator(getProcessingEnv(), metadata).generate(); diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java index 8d63cdbea..8347b0ea7 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java @@ -16,7 +16,10 @@ package dagger.hilt.android.processor.internal.androidentrypoint; +import static javax.lang.model.element.Modifier.ABSTRACT; +import static javax.lang.model.element.Modifier.PROTECTED; +import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; @@ -28,10 +31,16 @@ import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import dagger.hilt.android.processor.internal.AndroidClassNames; import dagger.hilt.processor.internal.ComponentNames; +import dagger.hilt.processor.internal.ProcessorErrors; import dagger.hilt.processor.internal.Processors; import java.io.IOException; +import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import javax.lang.model.util.ElementFilter; /** Generates an Hilt Application for an @AndroidEntryPoint app class. */ public final class ApplicationGenerator { @@ -68,15 +77,49 @@ public final class ApplicationGenerator { .forEachOrdered(typeSpecBuilder::addTypeVariable); Generators.copyLintAnnotations(metadata.element(), typeSpecBuilder); + Generators.copySuppressAnnotations(metadata.element(), typeSpecBuilder); Generators.addComponentOverride(metadata, typeSpecBuilder); + if (hasCustomInject()) { + typeSpecBuilder.addSuperinterface(AndroidClassNames.HAS_CUSTOM_INJECT); + typeSpecBuilder.addMethod(customInjectMethod()); + } else { typeSpecBuilder.addMethod(onCreateMethod()); + } JavaFile.builder(metadata.elementClassName().packageName(), typeSpecBuilder.build()) .build() .writeTo(env.getFiler()); } + private boolean hasCustomInject() { + boolean hasCustomInject = + Processors.hasAnnotation(metadata.element(), AndroidClassNames.CUSTOM_INJECT); + if (hasCustomInject) { + // Check that the Hilt base class does not already define a customInject implementation. + Set<ExecutableElement> customInjectMethods = + ElementFilter.methodsIn( + ImmutableSet.<Element>builder() + .addAll(metadata.element().getEnclosedElements()) + .addAll(env.getElementUtils().getAllMembers(metadata.baseElement())) + .build()) + .stream() + .filter(method -> method.getSimpleName().contentEquals("customInject")) + .filter(method -> method.getParameters().isEmpty()) + .collect(Collectors.toSet()); + + for (ExecutableElement customInjectMethod : customInjectMethods) { + ProcessorErrors.checkState( + customInjectMethod.getModifiers().containsAll(ImmutableSet.of(ABSTRACT, PROTECTED)), + customInjectMethod, + "%s#%s, must have modifiers `abstract` and `protected` when using @CustomInject.", + customInjectMethod.getEnclosingElement(), + customInjectMethod); + } + } + return hasCustomInject; + } + // private final ApplicationComponentManager<ApplicationComponent> componentManager = // new ApplicationComponentManager(/* creatorType */); private FieldSpec componentManagerField() { @@ -108,9 +151,6 @@ public final class ApplicationGenerator { // } // } private TypeSpec creatorType() { - ClassName component = - componentNames.generatedComponent( - metadata.elementClassName(), AndroidClassNames.SINGLETON_COMPONENT); return TypeSpec.anonymousClassBuilder("") .addSuperinterface(AndroidClassNames.COMPONENT_SUPPLIER) .addMethod( @@ -118,17 +158,27 @@ public final class ApplicationGenerator { .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .returns(TypeName.OBJECT) - .addStatement( - "return $T.builder()\n" - + ".applicationContextModule(new $T($T.this))\n" - + ".build()", - Processors.prepend(Processors.getEnclosedClassName(component), "Dagger"), - AndroidClassNames.APPLICATION_CONTEXT_MODULE, - wrapperClassName) + .addCode(componentBuilder()) .build()) .build(); } + // return DaggerApplicationComponent.builder() + // .applicationContextModule(new ApplicationContextModule(Hilt_$APP.this)) + // .build(); + private CodeBlock componentBuilder() { + ClassName component = + componentNames.generatedComponent( + metadata.elementClassName(), AndroidClassNames.SINGLETON_COMPONENT); + return CodeBlock.builder() + .addStatement( + "return $T.builder()$Z" + ".applicationContextModule(new $T($T.this))$Z" + ".build()", + Processors.prepend(Processors.getEnclosedClassName(component), "Dagger"), + AndroidClassNames.APPLICATION_CONTEXT_MODULE, + wrapperClassName) + .build(); + } + // @CallSuper // @Override // public void onCreate() { @@ -147,7 +197,21 @@ public final class ApplicationGenerator { .build(); } - // // This is a known unsafe cast but should be fine if the only use is + // @Override + // public final void customInject() { + // // This is a known unsafe cast but is safe in the only correct use case: + // // $APP extends Hilt_$APP + // generatedComponent().inject(($APP) this); + // } + private MethodSpec customInjectMethod() { + return MethodSpec.methodBuilder("customInject") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addCode(injectCodeBlock()) + .build(); + } + + // // This is a known unsafe cast but is safe in the only correct use case: // // $APP extends Hilt_$APP // generatedComponent().inject$APP(($APP) this); private CodeBlock injectCodeBlock() { diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD b/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD index efbf9e47a..46bcdd6d3 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD @@ -38,9 +38,12 @@ java_library( ":metadata", "//java/dagger/hilt/android/processor/internal:android_classnames", "//java/dagger/hilt/processor/internal:base_processor", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/incap", + "//java/dagger/hilt/processor/internal:compiler_options", + "//java/dagger/hilt/processor/internal:processor_errors", + "//java/dagger/hilt/processor/internal:processors", + "//third_party/java/auto:service", + "//third_party/java/guava/collect", + "//third_party/java/incap", ], ) @@ -61,15 +64,16 @@ java_library( "//java/dagger/hilt/android/processor/internal:android_classnames", "//java/dagger/hilt/android/processor/internal:utils", "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:compiler_options", "//java/dagger/hilt/processor/internal:component_names", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/langmodel", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) @@ -88,11 +92,11 @@ java_library( "//java/dagger/hilt/processor/internal:processors", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/kotlin", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java index f8e9f6078..a8eb7a6b2 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java @@ -78,6 +78,7 @@ public final class BroadcastReceiverGenerator { Generators.addInjectionMethods(metadata, builder); Generators.copyLintAnnotations(metadata.element(), builder); + Generators.copySuppressAnnotations(metadata.element(), builder); // Add an unused field used as a marker to let the bytecode injector know this receiver will // need to be injected with a super.onReceive call. This is only necessary if no concrete diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java index 81b2b6156..e7a70268c 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java @@ -16,10 +16,12 @@ package dagger.hilt.android.processor.internal.androidentrypoint; +import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import dagger.hilt.android.processor.internal.AndroidClassNames; @@ -36,6 +38,11 @@ public final class FragmentGenerator { .addModifiers(Modifier.PRIVATE) .build(); + private static final FieldSpec DISABLE_GET_CONTEXT_FIX_FIELD = + FieldSpec.builder(TypeName.BOOLEAN, "disableGetContextFix") + .addModifiers(Modifier.PRIVATE) + .build(); + private final ProcessingEnvironment env; private final AndroidEntryPointMetadata metadata; private final ClassName generatedClassName; @@ -69,12 +76,13 @@ public final class FragmentGenerator { .addMethod(onAttachActivityMethod()) .addMethod(initializeComponentContextMethod()) .addMethod(getContextMethod()) - .addMethod(inflatorMethod()); + .addMethod(inflatorMethod()) + .addField(DISABLE_GET_CONTEXT_FIX_FIELD); Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT); Processors.addGeneratedAnnotation(builder, env, getClass()); Generators.copyLintAnnotations(metadata.element(), builder); - Generators.addSuppressAnnotation(builder, "deprecation"); + Generators.copySuppressAnnotations(metadata.element(), builder); Generators.copyConstructors(metadata.baseElement(), builder); metadata.baseElement().getTypeParameters().stream() @@ -94,9 +102,10 @@ public final class FragmentGenerator { // @CallSuper // @Override - // public void onAttach(Activity activity) { - // super.onAttach(activity); + // public void onAttach(Context context) { + // super.onAttach(context); // initializeComponentContext(); + // inject(); // } private static MethodSpec onAttachContextMethod() { return MethodSpec.methodBuilder("onAttach") @@ -106,21 +115,29 @@ public final class FragmentGenerator { .addParameter(AndroidClassNames.CONTEXT, "context") .addStatement("super.onAttach(context)") .addStatement("initializeComponentContext()") + // The inject method will internally check if injected already + .addStatement("inject()") .build(); } // @CallSuper // @Override + // @SuppressWarnings("deprecation") // public void onAttach(Activity activity) { // super.onAttach(activity); // Preconditions.checkState( // componentContext == null || FragmentComponentManager.findActivity( // componentContext) == activity, "..."); // initializeComponentContext(); + // inject(); // } private static MethodSpec onAttachActivityMethod() { return MethodSpec.methodBuilder("onAttach") .addAnnotation(Override.class) + .addAnnotation( + AnnotationSpec.builder(ClassNames.SUPPRESS_WARNINGS) + .addMember("value", "\"deprecation\"") + .build()) .addAnnotation(AndroidClassNames.CALL_SUPER) .addAnnotation(AndroidClassNames.MAIN_THREAD) .addModifiers(Modifier.PUBLIC) @@ -135,23 +152,24 @@ public final class FragmentGenerator { "onAttach called multiple times with different Context! " + "Hilt Fragments should not be retained.") .addStatement("initializeComponentContext()") + // The inject method will internally check if injected already + .addStatement("inject()") .build(); } // private void initializeComponentContext() { - // // Only inject on the first call to onAttach. // if (componentContext == null) { // // Note: The LayoutInflater provided by this componentContext may be different from super // // Fragment's because we are getting it from base context instead of cloning from super // // Fragment's LayoutInflater. // componentContext = FragmentComponentManager.createContextWrapper(super.getContext(), this); - // inject(); + // disableGetContextFix = FragmentGetContextFix.isFragmentGetContextFixDisabled( + // super.getContext()); // } // } private MethodSpec initializeComponentContextMethod() { - return MethodSpec.methodBuilder("initializeComponentContext") + MethodSpec.Builder builder = MethodSpec.methodBuilder("initializeComponentContext") .addModifiers(Modifier.PRIVATE) - .addComment("Only inject on the first call to onAttach.") .beginControlFlow("if ($N == null)", COMPONENT_CONTEXT_FIELD) .addComment( "Note: The LayoutInflater provided by this componentContext may be different from" @@ -160,21 +178,54 @@ public final class FragmentGenerator { .addStatement( "$N = $T.createContextWrapper(super.getContext(), this)", COMPONENT_CONTEXT_FIELD, - metadata.componentManager()) - .addStatement("inject()") + metadata.componentManager()); + if (metadata.allowsOptionalInjection()) { + // When optionally injected, since the runtime flag is only available in Hilt, we need to + // check that the parent uses Hilt first. + builder.beginControlFlow("if (optionalInjectParentUsesHilt(optionalInjectGetParent()))"); + } + + builder + .addStatement("$N = $T.isFragmentGetContextFixDisabled(super.getContext())", + DISABLE_GET_CONTEXT_FIX_FIELD, + AndroidClassNames.FRAGMENT_GET_CONTEXT_FIX); + + if (metadata.allowsOptionalInjection()) { + // If not attached to a Hilt parent, just disable the fix for now since this is the current + // default. There's not a good way to flip this at runtime without Hilt, so after we flip + // the default we may just have to flip this and hope that the Hilt usage is already enough + // coverage as this should be a fairly rare case. + builder.nextControlFlow("else") + .addStatement("$N = true", DISABLE_GET_CONTEXT_FIX_FIELD) + .endControlFlow(); + } + + return builder .endControlFlow() .build(); } // @Override // public Context getContext() { + // if (super.getContext() == null && !disableGetContextFix) { + // return null; + // } + // initializeComponentContext(); // return componentContext; // } - private static MethodSpec getContextMethod() { + private MethodSpec getContextMethod() { return MethodSpec.methodBuilder("getContext") .returns(AndroidClassNames.CONTEXT) .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) + // Note that disableGetContext can only be true if componentContext is set, so if it is + // true we don't need to check whether componentContext is set or not. + .beginControlFlow( + "if (super.getContext() == null && !$N)", + DISABLE_GET_CONTEXT_FIX_FIELD) + .addStatement("return null") + .endControlFlow() + .addStatement("initializeComponentContext()") .addStatement("return $N", COMPONENT_CONTEXT_FIELD) .build(); } @@ -206,10 +257,19 @@ public final class FragmentGenerator { // this, super.getDefaultViewModelProviderFactory()); // } private MethodSpec getDefaultViewModelProviderFactory() { - return MethodSpec.methodBuilder("getDefaultViewModelProviderFactory") + MethodSpec.Builder builder = MethodSpec.methodBuilder("getDefaultViewModelProviderFactory") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) - .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY) + .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY); + + if (metadata.allowsOptionalInjection()) { + builder + .beginControlFlow("if (!optionalInjectParentUsesHilt(optionalInjectGetParent()))") + .addStatement("return super.getDefaultViewModelProviderFactory()") + .endControlFlow(); + } + + return builder .addStatement( "return $T.getFragmentFactory(this, super.getDefaultViewModelProviderFactory())", AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES) diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java index 91df537c9..e2892aa27 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java @@ -18,9 +18,14 @@ package dagger.hilt.android.processor.internal.androidentrypoint; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static java.util.stream.Collectors.joining; import static javax.lang.model.element.Modifier.PRIVATE; +import com.google.auto.common.AnnotationMirrors; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; @@ -30,6 +35,7 @@ import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import dagger.hilt.android.processor.internal.AndroidClassNames; +import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointMetadata.AndroidType; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; import java.util.List; @@ -45,6 +51,11 @@ import javax.lang.model.util.ElementFilter; /** Helper class for writing Hilt generators. */ final class Generators { + private static final ImmutableMap<ClassName, String> SUPPRESS_ANNOTATION_PROPERTY_NAME = + ImmutableMap.<ClassName, String>builder() + .put(ClassNames.SUPPRESS_WARNINGS, "value") + .put(ClassNames.KOTLIN_SUPPRESS, "names") + .build(); static void addGeneratedBaseClassJavadoc(TypeSpec.Builder builder, ClassName annotation) { builder.addJavadoc("A generated base class to be extended by the @$T annotated class. If using" @@ -123,15 +134,14 @@ final class Generators { private static MethodSpec copyConstructor(ExecutableElement constructor, CodeBlock body) { List<ParameterSpec> params = constructor.getParameters().stream() - .map(parameter -> getParameterSpecWithNullable(parameter)) + .map(Generators::getParameterSpecWithNullable) .collect(Collectors.toList()); final MethodSpec.Builder builder = MethodSpec.constructorBuilder() .addParameters(params) .addStatement( - "super($L)", - params.stream().map(param -> param.name).collect(Collectors.joining(", "))) + "super($L)", params.stream().map(param -> param.name).collect(joining(", "))) .addCode(body); constructor.getAnnotationMirrors().stream() @@ -143,6 +153,27 @@ final class Generators { return builder.build(); } + /** Copies SuppressWarnings annotations from the annotated element to the generated element. */ + static void copySuppressAnnotations(Element element, TypeSpec.Builder builder) { + ImmutableSet<String> suppressValues = + SUPPRESS_ANNOTATION_PROPERTY_NAME.keySet().stream() + .filter(annotation -> Processors.hasAnnotation(element, annotation)) + .map( + annotation -> + AnnotationMirrors.getAnnotationValue( + Processors.getAnnotationMirror(element, annotation), + SUPPRESS_ANNOTATION_PROPERTY_NAME.get(annotation))) + .flatMap(value -> Processors.getStringArrayAnnotationValue(value).stream()) + .collect(toImmutableSet()); + + if (!suppressValues.isEmpty()) { + // Replace kotlin Suppress with java SuppressWarnings, as the generated file is java. + AnnotationSpec.Builder annotation = AnnotationSpec.builder(ClassNames.SUPPRESS_WARNINGS); + suppressValues.forEach(value -> annotation.addMember("value", "$S", value)); + builder.addAnnotation(annotation.build()); + } + } + /** * Copies the Android lint annotations from the annotated element to the generated element. * @@ -186,7 +217,7 @@ final class Generators { addComponentManagerMethods(metadata, builder); // fall through case BROADCAST_RECEIVER: - addInjectMethod(metadata, builder); + addInjectAndMaybeOptionalInjectMethod(metadata, builder); break; default: throw new AssertionError(); @@ -293,7 +324,7 @@ final class Generators { // injected = true; // } // } - private static void addInjectMethod( + private static void addInjectAndMaybeOptionalInjectMethod( AndroidEntryPointMetadata metadata, TypeSpec.Builder typeSpecBuilder) { MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("inject") .addModifiers(Modifier.PROTECTED); @@ -301,22 +332,44 @@ final class Generators { // Check if the parent is a Hilt type. If it isn't or if it is but it // wasn't injected by hilt, then return. // Object parent = ...depends on type... - // if (!(parent instanceof GeneratedComponentManager) - // || ((parent instanceof InjectedByHilt) && - // !((InjectedByHilt) parent).wasInjectedByHilt())) { + // if (!optionalInjectParentUsesHilt()) { // return; // if (metadata.allowsOptionalInjection()) { + CodeBlock parentCodeBlock; + if (metadata.androidType() != AndroidType.BROADCAST_RECEIVER) { + parentCodeBlock = CodeBlock.of("optionalInjectGetParent()"); + + // Also, add the optionalInjectGetParent method we just used. This is a separate method so + // other parts of the code when dealing with @OptionalInject. BroadcastReceiver can't have + // this method since the context is only accessible as a parameter to receive()/inject(). + typeSpecBuilder.addMethod(MethodSpec.methodBuilder("optionalInjectGetParent") + .addModifiers(Modifier.PRIVATE) + .returns(TypeName.OBJECT) + .addStatement("return $L", getParentCodeBlock(metadata)) + .build()); + } else { + // For BroadcastReceiver, use the "context" field that is on the stack. + parentCodeBlock = CodeBlock.of( + "$T.getApplication(context.getApplicationContext())", ClassNames.CONTEXTS); + } + methodSpecBuilder - .addStatement("$T parent = $L", ClassNames.OBJECT, getParentCodeBlock(metadata)) - .beginControlFlow( - "if (!(parent instanceof $T) " - + "|| ((parent instanceof $T) && !(($T) parent).wasInjectedByHilt()))", + .beginControlFlow("if (!optionalInjectParentUsesHilt($L))", parentCodeBlock) + .addStatement("return") + .endControlFlow(); + + // Add the optionalInjectParentUsesHilt used above. + typeSpecBuilder.addMethod(MethodSpec.methodBuilder("optionalInjectParentUsesHilt") + .addModifiers(Modifier.PRIVATE) + .addParameter(TypeName.OBJECT, "parent") + .returns(TypeName.BOOLEAN) + .addStatement("return (parent instanceof $T) " + + "&& (!(parent instanceof $T) || (($T) parent).wasInjectedByHilt())", ClassNames.GENERATED_COMPONENT_MANAGER, AndroidClassNames.INJECTED_BY_HILT, AndroidClassNames.INJECTED_BY_HILT) - .addStatement("return") - .endControlFlow(); + .build()); } // Only add @Override if an ancestor extends a generated Hilt class. @@ -393,15 +446,16 @@ final class Generators { switch (metadata.androidType()) { case ACTIVITY: case SERVICE: - return CodeBlock.of("getApplicationContext()"); + return CodeBlock.of("$T.getApplication(getApplicationContext())", ClassNames.CONTEXTS); case FRAGMENT: return CodeBlock.of("getHost()"); case VIEW: return CodeBlock.of( "$L.maybeGetParentComponentManager()", componentManagerCallBlock(metadata)); case BROADCAST_RECEIVER: - // Broadcast receivers receive a "context" parameter - return CodeBlock.of("context.getApplicationContext()"); + // Broadcast receivers receive a "context" parameter that make it so this code block + // isn't really usable anywhere + throw new AssertionError("BroadcastReceiver types should not get here"); default: throw new AssertionError(); } @@ -476,18 +530,5 @@ final class Generators { .build(); } - /** - * Adds the SupressWarnings to supress a warning in the generated code. - * - * @param keys the string keys of the warnings to suppress, e.g. 'deprecation', 'unchecked', etc. - */ - public static void addSuppressAnnotation(TypeSpec.Builder builder, String... keys) { - AnnotationSpec.Builder annotationBuilder = AnnotationSpec.builder(SuppressWarnings.class); - for (String key : keys) { - annotationBuilder.addMember("value", "$S", key); - } - builder.addAnnotation(annotationBuilder.build()); - } - private Generators() {} } diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/InjectorEntryPointGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/InjectorEntryPointGenerator.java index e9e217ddc..88c40e7d4 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/InjectorEntryPointGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/InjectorEntryPointGenerator.java @@ -61,6 +61,7 @@ public final class InjectorEntryPointGenerator { Processors.addGeneratedAnnotation(builder, env, getClass()); Generators.copyLintAnnotations(metadata.element(), builder); + Generators.copySuppressAnnotations(metadata.element(), builder); JavaFile.builder(name.packageName(), builder.build()).build().writeTo(env.getFiler()); } diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ServiceGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ServiceGenerator.java index a80a21545..267336387 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ServiceGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ServiceGenerator.java @@ -61,6 +61,7 @@ public final class ServiceGenerator { Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT); Processors.addGeneratedAnnotation(builder, env, getClass()); Generators.copyLintAnnotations(metadata.element(), builder); + Generators.copySuppressAnnotations(metadata.element(), builder); metadata.baseElement().getTypeParameters().stream() .map(TypeVariableName::get) diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ViewGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ViewGenerator.java index 412b09a26..47029ca6f 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ViewGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ViewGenerator.java @@ -31,7 +31,6 @@ import java.util.List; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -70,6 +69,7 @@ public final class ViewGenerator { Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT); Processors.addGeneratedAnnotation(builder, env, getClass()); Generators.copyLintAnnotations(metadata.element(), builder); + Generators.copySuppressAnnotations(metadata.element(), builder); metadata.baseElement().getTypeParameters().stream() .map(TypeVariableName::get) @@ -177,13 +177,13 @@ public final class ViewGenerator { } private static boolean isSecondRestrictedParameter(Element element) { - return element instanceof TypeElement + return MoreElements.isType(element) && Processors.isAssignableFrom( MoreElements.asType(element), AndroidClassNames.ATTRIBUTE_SET); } private static boolean isFirstRestrictedParameter(Element element) { - return element instanceof TypeElement + return MoreElements.isType(element) && Processors.isAssignableFrom(MoreElements.asType(element), AndroidClassNames.CONTEXT); } diff --git a/java/dagger/hilt/android/processor/internal/bindvalue/BUILD b/java/dagger/hilt/android/processor/internal/bindvalue/BUILD index cbc51b3c5..5a59735a2 100644 --- a/java/dagger/hilt/android/processor/internal/bindvalue/BUILD +++ b/java/dagger/hilt/android/processor/internal/bindvalue/BUILD @@ -42,15 +42,15 @@ java_library( "//java/dagger/hilt/processor/internal:processors", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/kotlin", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/jsr250_annotations", - "@google_bazel_common//third_party/java/jsr330_inject", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/auto:value", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/incap", + "//third_party/java/javapoet", + "//third_party/java/jsr250_annotations", + "//third_party/java/jsr330_inject", ], ) diff --git a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueMetadata.java b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueMetadata.java index bf5028829..ecd05a8d7 100644 --- a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueMetadata.java +++ b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueMetadata.java @@ -30,7 +30,6 @@ import dagger.hilt.processor.internal.Processors; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import java.util.Collection; import java.util.Optional; -import javax.inject.Inject; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -125,7 +124,7 @@ abstract class BindValueMetadata { } ProcessorErrors.checkState( - !Processors.hasAnnotation(element, Inject.class), + !Processors.hasAnnotation(element, ClassNames.INJECT), element, "@%s fields cannot be used with @Inject annotation. Found %s", annotationClassName.simpleName(), diff --git a/java/dagger/hilt/android/processor/internal/customtestapplication/BUILD b/java/dagger/hilt/android/processor/internal/customtestapplication/BUILD index e9721d5d3..681ac79c8 100644 --- a/java/dagger/hilt/android/processor/internal/customtestapplication/BUILD +++ b/java/dagger/hilt/android/processor/internal/customtestapplication/BUILD @@ -37,13 +37,13 @@ java_library( "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/auto:value", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/incap", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/BUILD b/java/dagger/hilt/android/processor/internal/viewmodel/BUILD index b45925fe8..353729d13 100644 --- a/java/dagger/hilt/android/processor/internal/viewmodel/BUILD +++ b/java/dagger/hilt/android/processor/internal/viewmodel/BUILD @@ -38,11 +38,11 @@ kt_jvm_library( "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/guava/collect", + "//third_party/java/incap", + "//third_party/java/javapoet", ], ) @@ -60,10 +60,10 @@ kt_jvm_library( "//:spi", "//java/dagger/hilt/android/processor/internal:android_classnames", "//java/dagger/hilt/processor/internal:processors", - "//java/dagger/internal/guava:graph", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/guava/graph", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/android/qualifiers/BUILD b/java/dagger/hilt/android/qualifiers/BUILD index 26b45ec74..51bb8422e 100644 --- a/java/dagger/hilt/android/qualifiers/BUILD +++ b/java/dagger/hilt/android/qualifiers/BUILD @@ -25,7 +25,7 @@ android_library( ], deps = [ ":package_info", - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/jsr330_inject", ], ) diff --git a/java/dagger/hilt/android/scopes/BUILD b/java/dagger/hilt/android/scopes/BUILD index 5abc27e09..d89176f1e 100644 --- a/java/dagger/hilt/android/scopes/BUILD +++ b/java/dagger/hilt/android/scopes/BUILD @@ -29,7 +29,7 @@ android_library( ], deps = [ ":package_info", - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/jsr330_inject", ], ) diff --git a/java/dagger/hilt/android/testing/BUILD b/java/dagger/hilt/android/testing/BUILD index 93d4ceb94..85dea9df4 100644 --- a/java/dagger/hilt/android/testing/BUILD +++ b/java/dagger/hilt/android/testing/BUILD @@ -42,7 +42,8 @@ android_library( testonly = 1, srcs = ["HiltAndroidTest.java"], exported_plugins = [ - "//java/dagger/hilt/processor/internal/root:plugin", + "//java/dagger/hilt/processor/internal/root:component_tree_deps_plugin", + "//java/dagger/hilt/processor/internal/root:root_plugin", "//java/dagger/hilt/android/processor/internal/androidentrypoint:plugin", "//java/dagger/hilt/android/processor/internal/viewmodel:validation_plugin", ], @@ -54,7 +55,9 @@ android_library( "//:dagger_with_compiler", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android/components", + "//java/dagger/hilt/android/internal", "//java/dagger/hilt/android/internal/builders", + "//java/dagger/hilt/android/internal/legacy:aggregated_element_proxy", "//java/dagger/hilt/android/internal/managers", "//java/dagger/hilt/android/internal/modules", "//java/dagger/hilt/android/internal/testing:early_test_singleton_component_creator", @@ -71,6 +74,7 @@ android_library( "//java/dagger/hilt/internal:preconditions", "//java/dagger/hilt/internal:test_singleton_component", "//java/dagger/hilt/internal/aggregatedroot", + "//java/dagger/hilt/internal/componenttreedeps", "//java/dagger/hilt/internal/processedrootsentinel", "//java/dagger/hilt/migration:disable_install_in_check", "@maven//:androidx_annotation_annotation", @@ -118,10 +122,11 @@ android_library( ":package_info", "//:dagger_with_compiler", "//java/dagger/hilt:entry_point", + "//java/dagger/hilt/android/internal", "//java/dagger/hilt/android/internal/testing:test_application_component_manager_holder", "//java/dagger/hilt/internal:component_manager", "//java/dagger/hilt/internal:preconditions", - "@google_bazel_common//third_party/java/auto:value", + "//third_party/java/auto:value", ], ) @@ -141,7 +146,7 @@ android_library( ], ) -java_library( +android_library( name = "bind_value", testonly = 1, srcs = [ @@ -167,7 +172,7 @@ java_library( name = "package_info", srcs = ["package-info.java"], deps = [ - "@google_bazel_common//third_party/java/jsr305_annotations", + "//third_party/java/jsr305_annotations", ], ) @@ -226,6 +231,7 @@ gen_maven_artifact( "com.google.dagger:hilt-android", "javax.inject:javax.inject", "junit:junit", + "org.jetbrains.kotlin:kotlin-stdlib", ], artifact_target_maven_deps_banned = [ "com.google.guava:guava", diff --git a/java/dagger/hilt/android/testing/OnComponentReadyRunner.java b/java/dagger/hilt/android/testing/OnComponentReadyRunner.java index 925a19f49..dd670d576 100644 --- a/java/dagger/hilt/android/testing/OnComponentReadyRunner.java +++ b/java/dagger/hilt/android/testing/OnComponentReadyRunner.java @@ -20,6 +20,7 @@ import android.app.Application; import android.content.Context; import com.google.auto.value.AutoValue; import dagger.hilt.EntryPoints; +import dagger.hilt.android.internal.Contexts; import dagger.hilt.android.internal.testing.TestApplicationComponentManagerHolder; import dagger.hilt.internal.GeneratedComponentManager; import dagger.hilt.internal.Preconditions; @@ -48,7 +49,7 @@ public final class OnComponentReadyRunner { /** Must be called on the test thread, before the Statement is evaluated. */ public static <T> void addListener( Context context, Class<T> entryPoint, OnComponentReadyListener<T> listener) { - Application application = (Application) context.getApplicationContext(); + Application application = Contexts.getApplication(context.getApplicationContext()); if (application instanceof TestApplicationComponentManagerHolder) { TestApplicationComponentManagerHolder managerHolder = (TestApplicationComponentManagerHolder) application; diff --git a/javatests/dagger/hilt/android/processor/BUILD b/java/dagger/hilt/android/testing/compile/BUILD index 4ec4d56a6..72fb0c2dc 100644 --- a/javatests/dagger/hilt/android/processor/BUILD +++ b/java/dagger/hilt/android/testing/compile/BUILD @@ -17,22 +17,25 @@ package(default_visibility = ["//:src"]) java_library( - name = "android_compilers", - srcs = ["AndroidCompilers.java"], + name = "compile", + testonly = 1, + srcs = ["HiltCompilerTests.java"], deps = [ + "//third_party/java/guava/collect", + "//third_party/java/compile_testing", "//java/dagger/hilt/android/processor/internal/androidentrypoint:processor_lib", "//java/dagger/hilt/android/processor/internal/customtestapplication:processor_lib", "//java/dagger/hilt/processor/internal/aggregateddeps:processor_lib", + "//java/dagger/hilt/processor/internal/aliasof:processor_lib", "//java/dagger/hilt/processor/internal/definecomponent:processor_lib", "//java/dagger/hilt/processor/internal/earlyentrypoint:processor_lib", "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib", "//java/dagger/hilt/processor/internal/originatingelement:processor_lib", - "//java/dagger/hilt/processor/internal/root:processor_lib", + "//java/dagger/hilt/processor/internal/root:component_tree_deps_processor_lib", + "//java/dagger/hilt/processor/internal/root:root_processor_lib", "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib", "//java/dagger/internal/codegen:processor", - "//java/dagger/internal/guava:collect", "//java/dagger/testing/compile", - "@google_bazel_common//third_party/java/compile_testing", "@maven//:com_github_tschuchortdev_kotlin_compile_testing", ], ) diff --git a/javatests/dagger/hilt/android/processor/AndroidCompilers.java b/java/dagger/hilt/android/testing/compile/HiltCompilerTests.java index 1fb19ae3e..68a2add5f 100644 --- a/javatests/dagger/hilt/android/processor/AndroidCompilers.java +++ b/java/dagger/hilt/android/testing/compile/HiltCompilerTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package dagger.hilt.android.processor; +package dagger.hilt.android.testing.compile; import static java.util.stream.Collectors.toMap; @@ -23,30 +23,36 @@ import com.google.testing.compile.Compiler; import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointProcessor; import dagger.hilt.android.processor.internal.customtestapplication.CustomTestApplicationProcessor; import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsProcessor; +import dagger.hilt.processor.internal.aliasof.AliasOfProcessor; import dagger.hilt.processor.internal.definecomponent.DefineComponentProcessor; import dagger.hilt.processor.internal.earlyentrypoint.EarlyEntryPointProcessor; import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputProcessor; import dagger.hilt.processor.internal.originatingelement.OriginatingElementProcessor; +import dagger.hilt.processor.internal.root.ComponentTreeDepsProcessor; import dagger.hilt.processor.internal.root.RootProcessor; import dagger.hilt.processor.internal.uninstallmodules.UninstallModulesProcessor; import dagger.internal.codegen.ComponentProcessor; import dagger.testing.compile.CompilerTests; import java.util.Arrays; +import java.util.Collection; import java.util.Map; import javax.annotation.processing.Processor; import com.tschuchort.compiletesting.KotlinCompilation; /** {@link Compiler} instances for testing Android Hilt. */ -public final class AndroidCompilers { +public final class HiltCompilerTests { public static Compiler compiler(Processor... extraProcessors) { + return compiler(Arrays.asList(extraProcessors)); + } + + public static Compiler compiler(Collection<? extends Processor> extraProcessors) { Map<Class<?>, Processor> processors = defaultProcessors().stream() .collect(toMap((Processor e) -> e.getClass(), (Processor e) -> e)); // Adds extra processors, and allows overriding any processors of the same class. - Arrays.stream(extraProcessors) - .forEach(processor -> processors.put(processor.getClass(), processor)); + extraProcessors.stream().forEach(processor -> processors.put(processor.getClass(), processor)); return CompilerTests.compiler().withProcessors(processors.values()); } @@ -66,16 +72,18 @@ public final class AndroidCompilers { private static ImmutableList<Processor> defaultProcessors() { return ImmutableList.of( new AggregatedDepsProcessor(), + new AliasOfProcessor(), new AndroidEntryPointProcessor(), new ComponentProcessor(), + new ComponentTreeDepsProcessor(), + new CustomTestApplicationProcessor(), new DefineComponentProcessor(), new EarlyEntryPointProcessor(), new GeneratesRootInputProcessor(), new OriginatingElementProcessor(), - new CustomTestApplicationProcessor(), - new UninstallModulesProcessor(), - new RootProcessor()); + new RootProcessor(), + new UninstallModulesProcessor()); } - private AndroidCompilers() {} + private HiltCompilerTests() {} } diff --git a/java/dagger/hilt/components/BUILD b/java/dagger/hilt/components/BUILD index 48dc8cd26..9baf4762b 100644 --- a/java/dagger/hilt/components/BUILD +++ b/java/dagger/hilt/components/BUILD @@ -25,7 +25,7 @@ java_library( deps = [ ":package_info", "//java/dagger/hilt:define_component", - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/jsr330_inject", ], ) diff --git a/java/dagger/hilt/internal/aggregatedroot/AggregatedRoot.java b/java/dagger/hilt/internal/aggregatedroot/AggregatedRoot.java index b53ee7258..a6aa5a50c 100644 --- a/java/dagger/hilt/internal/aggregatedroot/AggregatedRoot.java +++ b/java/dagger/hilt/internal/aggregatedroot/AggregatedRoot.java @@ -30,5 +30,7 @@ import java.lang.annotation.Target; public @interface AggregatedRoot { String root(); + String originatingRoot(); + Class<?> rootAnnotation(); } diff --git a/java/dagger/hilt/internal/aliasof/AliasOfPropagatedData.java b/java/dagger/hilt/internal/aliasof/AliasOfPropagatedData.java index 25ea7043a..53c5caefc 100644 --- a/java/dagger/hilt/internal/aliasof/AliasOfPropagatedData.java +++ b/java/dagger/hilt/internal/aliasof/AliasOfPropagatedData.java @@ -26,7 +26,7 @@ import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) public @interface AliasOfPropagatedData { - Class<? extends Annotation> defineComponentScope(); + Class<? extends Annotation>[] defineComponentScopes(); Class<? extends Annotation> alias(); } diff --git a/java/dagger/hilt/android/example/BUILD b/java/dagger/hilt/internal/componenttreedeps/BUILD index 2a4c19450..1a100d569 100644 --- a/java/dagger/hilt/android/example/BUILD +++ b/java/dagger/hilt/internal/componenttreedeps/BUILD @@ -1,4 +1,4 @@ -# Copyright (C) 2017 The Dagger Authors. +# Copyright (C) 2021 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. @@ -11,17 +11,18 @@ # 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. -# + # Description: -# A skeletal application that demonstrates wiring for an injected Application and Activity. +# The annotation for aggregating information about Hilt roots. package(default_visibility = ["//:src"]) +java_library( + name = "componenttreedeps", + srcs = ["ComponentTreeDeps.java"], +) + filegroup( name = "srcs_filegroup", - srcs = glob( - ["**/*"], - # Exclude Gradle build folder to enable working along side Bazel - exclude = ["**/build/**"], - ), + srcs = glob(["*"]), ) diff --git a/java/dagger/hilt/internal/componenttreedeps/ComponentTreeDeps.java b/java/dagger/hilt/internal/componenttreedeps/ComponentTreeDeps.java new file mode 100644 index 000000000..52123e5a2 --- /dev/null +++ b/java/dagger/hilt/internal/componenttreedeps/ComponentTreeDeps.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 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.hilt.internal.componenttreedeps; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** An annotation that kicks off the generation of a component tree. */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface ComponentTreeDeps { + + /** Returns the set of {@link dagger.hilt.internal.aggregatedroot.AggregatedRoot} dependencies. */ + Class<?>[] rootDeps() default {}; + + /** + * Returns the set of {@link dagger.hilt.internal.definecomponent.DefineComponentClasses} + * dependencies. + */ + Class<?>[] defineComponentDeps() default {}; + + /** Returns the set of {@link dagger.hilt.internal.aliasof.AliasOfPropagatedData} dependencies. */ + Class<?>[] aliasOfDeps() default {}; + + /** Returns the set of {@link dagger.hilt.internal.aggregateddeps.AggregatedDeps} dependencies. */ + Class<?>[] aggregatedDeps() default {}; + + /** + * Returns the set of {@link + * dagger.hilt.internal.uninstallmodules.AggregatedUninstallModulesMetadata} dependencies. + */ + Class<?>[] uninstallModulesDeps() default {}; + + /** + * Returns the set of {@link dagger.hilt.android.earlyentrypoint.AggregatedEarlyEntryPoint} + * dependencies. + */ + Class<?>[] earlyEntryPointDeps() default {}; +} diff --git a/java/dagger/hilt/migration/AliasOf.java b/java/dagger/hilt/migration/AliasOf.java index b68d4f12d..c26197da4 100644 --- a/java/dagger/hilt/migration/AliasOf.java +++ b/java/dagger/hilt/migration/AliasOf.java @@ -41,6 +41,6 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.CLASS) @GeneratesRootInput public @interface AliasOf { - /** Returns the existing Hilt scope that the annotated scope is aliasing. */ - Class<? extends Annotation> value(); + /** Returns the existing Hilt scope(s) that the annotated scope is aliasing. */ + Class<? extends Annotation>[] value(); } diff --git a/java/dagger/hilt/migration/BUILD b/java/dagger/hilt/migration/BUILD index a14410d0b..446e7a2ea 100644 --- a/java/dagger/hilt/migration/BUILD +++ b/java/dagger/hilt/migration/BUILD @@ -16,7 +16,7 @@ package(default_visibility = ["//:src"]) -android_library( +java_library( name = "alias_of", srcs = [ "AliasOf.java", diff --git a/java/dagger/hilt/processor/BUILD b/java/dagger/hilt/processor/BUILD index 1f75d0a34..bf2166cd6 100644 --- a/java/dagger/hilt/processor/BUILD +++ b/java/dagger/hilt/processor/BUILD @@ -35,7 +35,8 @@ java_library( "//java/dagger/hilt/processor/internal/earlyentrypoint:processor_lib", "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib", "//java/dagger/hilt/processor/internal/originatingelement:processor_lib", - "//java/dagger/hilt/processor/internal/root:processor_lib", + "//java/dagger/hilt/processor/internal/root:component_tree_deps_processor_lib", + "//java/dagger/hilt/processor/internal/root:root_processor_lib", "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib", "//java/dagger/internal/codegen:processor", ], @@ -87,9 +88,11 @@ gen_maven_artifact( "//java/dagger/hilt/processor/internal/generatesrootinput:generates_root_inputs", "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib", "//java/dagger/hilt/processor/internal/originatingelement:processor_lib", - "//java/dagger/hilt/processor/internal/root:processor_lib", + "//java/dagger/hilt/processor/internal/root:component_tree_deps_processor_lib", + "//java/dagger/hilt/processor/internal/root:root_processor_lib", "//java/dagger/hilt/processor/internal/root:root_metadata", "//java/dagger/hilt/processor/internal/root:root_type", + "//java/dagger/hilt/processor/internal/root/ir:ir", "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib", "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata", ], @@ -106,6 +109,7 @@ gen_maven_artifact( "javax.inject:javax.inject", "net.ltgt.gradle.incap:incap", "org.jetbrains.kotlin:kotlin-stdlib", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", "org.jetbrains.kotlinx:kotlinx-metadata-jvm", ], javadoc_android_api_level = 30, @@ -116,8 +120,10 @@ gen_maven_artifact( javadoc_srcs = [ "//java/dagger/hilt:hilt_processing_filegroup", ], - shaded_deps = ["@maven//:com_google_auto_auto_common"], - shaded_rules = ["rule com.google.auto.common.** dagger.hilt.android.shaded.auto.common.@1"], + # The shaded deps are added using jarjar, but they won't be shaded until later + # due to: https://github.com/google/dagger/issues/2765. For the shaded rules see + # util/deploy-hilt.sh + shaded_deps = ["//third_party/java/auto:common"], ) filegroup( diff --git a/java/dagger/hilt/processor/internal/AggregatedElements.java b/java/dagger/hilt/processor/internal/AggregatedElements.java index 8ee820fbd..be593e9d2 100644 --- a/java/dagger/hilt/processor/internal/AggregatedElements.java +++ b/java/dagger/hilt/processor/internal/AggregatedElements.java @@ -17,10 +17,12 @@ package dagger.hilt.processor.internal; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static javax.lang.model.element.Modifier.PUBLIC; import com.google.auto.common.MoreElements; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; +import java.util.Optional; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; @@ -28,6 +30,34 @@ import javax.lang.model.util.Elements; /** Utility class for aggregating metadata. */ public final class AggregatedElements { + /** Returns the class name of the proxy or {@link Optional#empty()} if a proxy is not needed. */ + public static Optional<ClassName> aggregatedElementProxyName(TypeElement aggregatedElement) { + if (aggregatedElement.getModifiers().contains(PUBLIC)) { + // Public aggregated elements do not have proxies. + return Optional.empty(); + } + ClassName name = ClassName.get(aggregatedElement); + // To avoid going over the class name size limit, just prepend a single character. + return Optional.of(name.peerClass("_" + name.simpleName())); + } + + /** Returns back the set of input {@code aggregatedElements} with all proxies unwrapped. */ + public static ImmutableSet<TypeElement> unwrapProxies( + ImmutableSet<TypeElement> aggregatedElements, Elements elements) { + return aggregatedElements.stream() + .map(aggregatedElement -> unwrapProxy(aggregatedElement, elements)) + .collect(toImmutableSet()); + } + + private static TypeElement unwrapProxy(TypeElement element, Elements elements) { + return Processors.hasAnnotation(element, ClassNames.AGGREGATED_ELEMENT_PROXY) + ? Processors.getAnnotationClassValue( + elements, + Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_ELEMENT_PROXY), + "value") + : element; + } + /** Returns all aggregated elements in the aggregating package after validating them. */ public static ImmutableSet<TypeElement> from( String aggregatingPackage, ClassName aggregatingAnnotation, Elements elements) { @@ -40,6 +70,10 @@ public final class AggregatedElements { ImmutableSet<TypeElement> aggregatedElements = packageElement.getEnclosedElements().stream() .map(MoreElements::asType) + // We're only interested in returning the original deps here. Proxies will be generated + // (if needed) and swapped just before generating @ComponentTreeDeps. + .filter( + element -> !Processors.hasAnnotation(element, ClassNames.AGGREGATED_ELEMENT_PROXY)) .collect(toImmutableSet()); ProcessorErrors.checkState( diff --git a/java/dagger/hilt/processor/internal/BUILD b/java/dagger/hilt/processor/internal/BUILD index 978655dea..b63a899dc 100644 --- a/java/dagger/hilt/processor/internal/BUILD +++ b/java/dagger/hilt/processor/internal/BUILD @@ -27,11 +27,11 @@ java_library( ":compiler_options", ":processor_errors", ":processors", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) @@ -43,11 +43,11 @@ java_library( "ProcessorErrors.java", ], deps = [ - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/error_prone:annotations", - "@google_bazel_common//third_party/java/jsr305_annotations", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/error_prone:annotations", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/jsr305_annotations", ], ) @@ -63,11 +63,11 @@ java_library( ":processor_errors", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/kotlin", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/jsr330_inject", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", + "//third_party/java/jsr330_inject", "@maven//:org_jetbrains_kotlin_kotlin_stdlib", "@maven//:org_jetbrains_kotlinx_kotlinx_metadata_jvm", ], @@ -79,7 +79,7 @@ java_library( "ClassNames.java", ], deps = [ - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/javapoet", ], ) @@ -89,11 +89,10 @@ java_library( "ComponentNames.java", ], deps = [ - ":classnames", ":processors", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) @@ -103,12 +102,13 @@ java_library( "AggregatedElements.java", ], deps = [ + ":classnames", ":processor_errors", ":processors", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) @@ -116,9 +116,9 @@ java_library( name = "component_descriptor", srcs = ["ComponentDescriptor.java"], deps = [ - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/auto:value", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) @@ -135,10 +135,10 @@ java_library( ":processors", "//java/dagger/hilt/processor/internal/definecomponent:define_components", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) @@ -156,8 +156,9 @@ java_library( srcs = ["HiltCompilerOptions.java"], deps = [ ":processor_errors", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/processor/internal/BaseProcessor.java b/java/dagger/hilt/processor/internal/BaseProcessor.java index 1a63f8b47..a92107529 100644 --- a/java/dagger/hilt/processor/internal/BaseProcessor.java +++ b/java/dagger/hilt/processor/internal/BaseProcessor.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkState; import com.google.auto.common.MoreElements; import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.SetMultimap; import com.squareup.javapoet.ClassName; @@ -102,7 +103,15 @@ public abstract class BaseProcessor extends AbstractProcessor { // warning if any used option is not unsupported. This can happen when there is a module // which uses Hilt but lacks any @AndroidEntryPoint annotations. // See: https://github.com/google/dagger/issues/2040 - return HiltCompilerOptions.getProcessorOptions(); + return ImmutableSet.<String>builder() + .addAll(HiltCompilerOptions.getProcessorOptions()) + .addAll(additionalProcessingOptions()) + .build(); + } + + /** Returns additional processing options that should only be applied for a single processor. */ + protected Set<String> additionalProcessingOptions() { + return ImmutableSet.of(); } /** Used to perform initialization before each round of processing. */ @@ -150,6 +159,7 @@ public abstract class BaseProcessor extends AbstractProcessor { this.elements = processingEnv.getElementUtils(); this.types = processingEnv.getTypeUtils(); this.errorHandler = new ProcessorErrorHandler(processingEnvironment); + HiltCompilerOptions.checkWrongAndDeprecatedOptions(processingEnvironment); } @Override diff --git a/java/dagger/hilt/processor/internal/ClassNames.java b/java/dagger/hilt/processor/internal/ClassNames.java index 093e1b3d5..dd65202e5 100644 --- a/java/dagger/hilt/processor/internal/ClassNames.java +++ b/java/dagger/hilt/processor/internal/ClassNames.java @@ -22,6 +22,11 @@ import com.squareup.javapoet.ClassName; /** Holder for commonly used class names. */ public final class ClassNames { + public static final ClassName AGGREGATED_ELEMENT_PROXY = + get("dagger.hilt.android.internal.legacy", "AggregatedElementProxy"); + public static final ClassName COMPONENT_TREE_DEPS = + get("dagger.hilt.internal.componenttreedeps", "ComponentTreeDeps"); + public static final String AGGREGATED_ROOT_PACKAGE = "dagger.hilt.internal.aggregatedroot.codegen"; public static final ClassName AGGREGATED_ROOT = @@ -31,6 +36,8 @@ public final class ClassNames { public static final ClassName PROCESSED_ROOT_SENTINEL = get("dagger.hilt.internal.processedrootsentinel", "ProcessedRootSentinel"); + public static final ClassName CONTEXTS = get("dagger.hilt.android.internal", "Contexts"); + public static final String AGGREGATED_EARLY_ENTRY_POINT_PACKAGE = "dagger.hilt.android.internal.earlyentrypoint.codegen"; public static final ClassName AGGREGATED_EARLY_ENTRY_POINT = @@ -202,6 +209,9 @@ public final class ClassNames { public static final ClassName OBJECT = get("java.lang", "Object"); + public static final ClassName SUPPRESS_WARNINGS = get("java.lang", "SuppressWarnings"); + public static final ClassName KOTLIN_SUPPRESS = get("kotlin", "Suppress"); + // Kotlin-specific class names public static final ClassName KOTLIN_METADATA = get("kotlin", "Metadata"); diff --git a/java/dagger/hilt/processor/internal/ComponentNames.java b/java/dagger/hilt/processor/internal/ComponentNames.java index eeaa5f44b..f3f0444e7 100644 --- a/java/dagger/hilt/processor/internal/ComponentNames.java +++ b/java/dagger/hilt/processor/internal/ComponentNames.java @@ -16,25 +16,8 @@ package dagger.hilt.processor.internal; -import static java.lang.Character.isUpperCase; -import static java.lang.String.format; -import static java.util.Comparator.comparing; - -import com.google.common.base.CharMatcher; -import com.google.common.base.Preconditions; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Multimaps; import com.squareup.javapoet.ClassName; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; +import java.util.function.Function; /** * Utility class for getting the generated component name. @@ -42,41 +25,38 @@ import javax.lang.model.element.TypeElement; * <p>This should not be used externally. */ public final class ComponentNames { - private static final Splitter QUALIFIED_NAME_SPLITTER = Splitter.on('.'); - - private final boolean renameTestComponents; - private final String destinationPackage; - private final ImmutableMap<ClassName, String> simpleNameByClassName; + /** + * Returns an instance of {@link ComponentNames} that will base all component names off of the + * given root. + */ public static ComponentNames withoutRenaming() { - return new ComponentNames( - /*renameTestComponents=*/ false, /*destinationPackage=*/ null, ImmutableMap.of()); + return new ComponentNames(Function.identity()); + } + + /** + * Returns an instance of {@link ComponentNames} that will base all component names off of the + * given root after mapping it with {@code rootRenamer}. + */ + public static ComponentNames withRenaming(Function<ClassName, ClassName> rootRenamer) { + return new ComponentNames(rootRenamer); } - public static ComponentNames withRenamingIntoPackage( - String destinationPackage, ImmutableList<TypeElement> roots) { - ImmutableMap.Builder<ClassName, String> builder = ImmutableMap.builder(); - ImmutableListMultimap<String, TypeElement> rootsBySimpleName = - Multimaps.index(roots, typeElement -> typeElement.getSimpleName().toString()); - rootsBySimpleName.asMap().values().stream() - .map(ComponentNames::disambiguateConflictingSimpleNames) - .forEach(builder::putAll); - return new ComponentNames(/*renameTestComponents=*/ true, destinationPackage, builder.build()); + private final Function<ClassName, ClassName> rootRenamer; + + private ComponentNames(Function<ClassName, ClassName> rootRenamer) { + this.rootRenamer = rootRenamer; } - private ComponentNames( - boolean renameTestComponents, - String destinationPackage, - ImmutableMap<ClassName, String> simpleNameByClassName) { - this.renameTestComponents = renameTestComponents; - this.destinationPackage = destinationPackage; - this.simpleNameByClassName = simpleNameByClassName; + public ClassName generatedComponentTreeDeps(ClassName root) { + return Processors.append( + Processors.getEnclosedClassName(rootRenamer.apply(root)), "_ComponentTreeDeps"); } /** Returns the name of the generated component wrapper. */ public ClassName generatedComponentsWrapper(ClassName root) { return Processors.append( - Processors.getEnclosedClassName(maybeRenameComponent(root)), "_HiltComponents"); + Processors.getEnclosedClassName(rootRenamer.apply(root)), "_HiltComponents"); } /** Returns the name of the generated component. */ @@ -99,81 +79,4 @@ public final class ComponentNames { return Processors.getEnclosedName(component).replaceAll("Component$", "C"); } - /** - * Rewrites the provided HiltAndroidTest-annotated class name using the shared component - * directory. - */ - private ClassName maybeRenameComponent(ClassName className) { - return (renameTestComponents && !className.equals(ClassNames.DEFAULT_ROOT)) - ? ClassName.get(destinationPackage, dedupeSimpleName(className)) - : className; - } - - /** - * Derives a new generated component base name, should the simple names of two roots have - * conflicting simple names. - * - * <p>This is lifted nearly verbatim (albeit with new different struct types) from {@link - * dagger.internal.codegen.writing.SubcomponentNames}. - */ - private String dedupeSimpleName(ClassName className) { - Preconditions.checkState( - simpleNameByClassName.containsKey(className), - "Class name %s not found in simple name map", - className.canonicalName()); - return simpleNameByClassName.get(className); - } - - private static ImmutableMap<ClassName, String> disambiguateConflictingSimpleNames( - Collection<TypeElement> rootsWithConflictingNames) { - // If there's only 1 root there's nothing to disambiguate so return the simple name. - if (rootsWithConflictingNames.size() == 1) { - TypeElement root = Iterables.getOnlyElement(rootsWithConflictingNames); - return ImmutableMap.of(ClassName.get(root), root.getSimpleName().toString()); - } - - // There are conflicting simple names, so disambiguate them with a unique prefix. - // We keep them small to fix https://github.com/google/dagger/issues/421. - // Sorted in order to guarantee determinism if this is invoked by different processors. - ImmutableList<TypeElement> sortedRootsWithConflictingNames = - ImmutableList.sortedCopyOf( - comparing(typeElement -> typeElement.getQualifiedName().toString()), - rootsWithConflictingNames); - Set<String> usedNames = new HashSet<>(); - ImmutableMap.Builder<ClassName, String> uniqueNames = ImmutableMap.builder(); - for (TypeElement root : sortedRootsWithConflictingNames) { - String basePrefix = uniquingPrefix(root); - String uniqueName = basePrefix; - for (int differentiator = 2; !usedNames.add(uniqueName); differentiator++) { - uniqueName = basePrefix + differentiator; - } - uniqueNames.put(ClassName.get(root), format("%s_%s", uniqueName, root.getSimpleName())); - } - return uniqueNames.build(); - } - - /** Returns a prefix that could make the component's simple name more unique. */ - private static String uniquingPrefix(TypeElement typeElement) { - String containerName = typeElement.getEnclosingElement().getSimpleName().toString(); - - // If parent element looks like a class, use its initials as a prefix. - if (!containerName.isEmpty() && isUpperCase(containerName.charAt(0))) { - return CharMatcher.javaLowerCase().removeFrom(containerName); - } - - // Not in a normally named class. Prefix with the initials of the elements leading here. - Name qualifiedName = typeElement.getQualifiedName(); - Iterator<String> pieces = QUALIFIED_NAME_SPLITTER.split(qualifiedName).iterator(); - StringBuilder b = new StringBuilder(); - - while (pieces.hasNext()) { - String next = pieces.next(); - if (pieces.hasNext()) { - b.append(next.charAt(0)); - } - } - - // Note that a top level class in the root package will be prefixed "$_". - return b.length() > 0 ? b.toString() : "$"; - } } diff --git a/java/dagger/hilt/processor/internal/HiltCompilerOptions.java b/java/dagger/hilt/processor/internal/HiltCompilerOptions.java index 0d248239b..b06faedb6 100644 --- a/java/dagger/hilt/processor/internal/HiltCompilerOptions.java +++ b/java/dagger/hilt/processor/internal/HiltCompilerOptions.java @@ -16,12 +16,14 @@ package dagger.hilt.processor.internal; +import com.google.common.base.Ascii; import com.google.common.collect.ImmutableSet; import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic.Kind; /** Hilt annotation processor options. */ // TODO(danysantiago): Consider consolidating with Dagger compiler options logic. @@ -75,16 +77,30 @@ public final class HiltCompilerOptions { return BooleanOption.SHARE_TEST_COMPONENTS.get(env); } + /** + * Returns {@code true} if the aggregating processor is enabled (default is {@code true}). + * + * <p>Note:This is for internal use only! + */ + public static boolean useAggregatingRootProcessor(ProcessingEnvironment env) { + return BooleanOption.USE_AGGREGATING_ROOT_PROCESSOR.get(env); + } + /** Processor options which can have true or false values. */ private enum BooleanOption { + /** Do not use! This is for internal use only. */ DISABLE_ANDROID_SUPERCLASS_VALIDATION( "android.internal.disableAndroidSuperclassValidation", false), + /** Do not use! This is for internal use only. */ + USE_AGGREGATING_ROOT_PROCESSOR("internal.useAggregatingRootProcessor", true), + DISABLE_CROSS_COMPILATION_ROOT_VALIDATION("disableCrossCompilationRootValidation", false), DISABLE_MODULES_HAVE_INSTALL_IN_CHECK("disableModulesHaveInstallInCheck", false), - SHARE_TEST_COMPONENTS("shareTestComponents", false); + SHARE_TEST_COMPONENTS( + "shareTestComponents", true); private final String name; private final boolean defaultValue; @@ -99,8 +115,22 @@ public final class HiltCompilerOptions { if (value == null) { return defaultValue; } - // TODO(danysantiago): Strictly verify input, either 'true' or 'false' and nothing else. - return Boolean.parseBoolean(value); + + // Using Boolean.parseBoolean will turn any non-"true" value into false. Strictly verify the + // inputs to reduce user errors. + String lowercaseValue = Ascii.toLowerCase(value); + switch (lowercaseValue) { + case "true": + return true; + case "false": + return false; + default: + throw new IllegalStateException( + "Expected a value of true/false for the flag \"" + + name + + "\". Got instead: " + + value); + } } String getQualifiedName() { @@ -108,6 +138,32 @@ public final class HiltCompilerOptions { } } + private static final ImmutableSet<String> DEPRECATED_OPTIONS = ImmutableSet.of( + "dagger.hilt.android.useFragmentGetContextFix"); + + public static void checkWrongAndDeprecatedOptions(ProcessingEnvironment env) { + Set<String> knownOptions = getProcessorOptions(); + for (String option : env.getOptions().keySet()) { + if (knownOptions.contains(option)) { + continue; + } + + if (DEPRECATED_OPTIONS.contains(option)) { + env.getMessager().printMessage( + Kind.ERROR, + "The compiler option " + option + " is deprecated and no longer does anything. " + + "Please do not set this option."); + continue; + } + + if (option.startsWith("dagger.hilt.")) { + env.getMessager().printMessage( + Kind.ERROR, + "The compiler option " + option + " is not a recognized Hilt option. Is there a typo?"); + } + } + } + public static Set<String> getProcessorOptions() { return Arrays.stream(BooleanOption.values()) .map(BooleanOption::getQualifiedName) diff --git a/java/dagger/hilt/processor/internal/Processors.java b/java/dagger/hilt/processor/internal/Processors.java index 8740bd6a0..4e2e256f9 100644 --- a/java/dagger/hilt/processor/internal/Processors.java +++ b/java/dagger/hilt/processor/internal/Processors.java @@ -62,7 +62,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; -import javax.inject.Qualifier; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; @@ -104,7 +103,7 @@ public final class Processors { .addModifiers(PUBLIC) .addOriginatingElement(element) .addAnnotation(aggregatingAnnotation) - .addJavadoc("This class should only be referenced by generated code!") + .addJavadoc("This class should only be referenced by generated code! ") .addJavadoc("This class aggregates information across multiple compilations.\n");; addGeneratedAnnotation(builder, env, generatedAnnotationClass); @@ -767,7 +766,7 @@ public final class Processors { public static ImmutableList<AnnotationMirror> getQualifierAnnotations(Element element) { // TODO(bcorso): Consolidate this logic with InjectionAnnotations in Dagger ImmutableSet<? extends AnnotationMirror> qualifiers = - AnnotationMirrors.getAnnotatedAnnotations(element, Qualifier.class); + AnnotationMirrors.getAnnotatedAnnotations(element, ClassNames.QUALIFIER.canonicalName()); KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil(); if (element.getKind() == ElementKind.FIELD // static fields are generally not supported, no need to get qualifier from kotlin metadata @@ -779,7 +778,7 @@ public final class Processors { metadataUtil.isMissingSyntheticPropertyForAnnotations(fieldElement) ? Stream.empty() : metadataUtil - .getSyntheticPropertyAnnotations(fieldElement, Qualifier.class) + .getSyntheticPropertyAnnotations(fieldElement, ClassNames.QUALIFIER) .stream()) .map(AnnotationMirrors.equivalence()::wrap) .distinct() @@ -931,6 +930,17 @@ public final class Processors { && !KotlinMetadataUtils.getMetadataUtil().isObjectOrCompanionObjectClass(module); } + public static boolean hasVisibleEmptyConstructor(TypeElement type) { + List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements()); + return constructors.isEmpty() + || constructors.stream() + .filter(constructor -> constructor.getParameters().isEmpty()) + .anyMatch( + constructor -> + !constructor.getModifiers().contains(Modifier.PRIVATE) + ); + } + private static boolean isBindingMethod(ExecutableElement method) { return hasAnnotation(method, ClassNames.PROVIDES) || hasAnnotation(method, ClassNames.BINDS) diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java index 09e1651fe..9217a574d 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java @@ -24,11 +24,14 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.AggregatedElements; import dagger.hilt.processor.internal.AnnotationValues; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.root.ir.AggregatedDepsIr; import java.util.Optional; +import java.util.stream.Collectors; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.TypeElement; @@ -39,7 +42,7 @@ import javax.lang.model.util.Elements; * dagger.hilt.processor.internal.aggregateddeps.AggregatedDeps} annotation. */ @AutoValue -abstract class AggregatedDepsMetadata { +public abstract class AggregatedDepsMetadata { private static final String AGGREGATED_DEPS_PACKAGE = "hilt_aggregated_deps"; enum DependencyType { @@ -48,24 +51,61 @@ abstract class AggregatedDepsMetadata { COMPONENT_ENTRY_POINT } - abstract Optional<TypeElement> testElement(); + /** Returns the aggregating element */ + public abstract TypeElement aggregatingElement(); - abstract ImmutableSet<TypeElement> componentElements(); + public abstract Optional<TypeElement> testElement(); + + public abstract ImmutableSet<TypeElement> componentElements(); abstract DependencyType dependencyType(); abstract TypeElement dependency(); - abstract ImmutableSet<TypeElement> replacedDependencies(); + public abstract ImmutableSet<TypeElement> replacedDependencies(); + + public boolean isModule() { + return dependencyType() == DependencyType.MODULE; + } - /** Returns all aggregated deps in the aggregating package. */ + /** Returns metadata for all aggregated elements in the aggregating package. */ public static ImmutableSet<AggregatedDepsMetadata> from(Elements elements) { - return AggregatedElements.from(AGGREGATED_DEPS_PACKAGE, ClassNames.AGGREGATED_DEPS, elements) - .stream() + return from( + AggregatedElements.from(AGGREGATED_DEPS_PACKAGE, ClassNames.AGGREGATED_DEPS, elements), + elements); + } + + /** Returns metadata for each aggregated element. */ + public static ImmutableSet<AggregatedDepsMetadata> from( + ImmutableSet<TypeElement> aggregatedElements, Elements elements) { + return aggregatedElements.stream() .map(aggregatedElement -> create(aggregatedElement, elements)) .collect(toImmutableSet()); } + public static AggregatedDepsIr toIr(AggregatedDepsMetadata metadata) { + return new AggregatedDepsIr( + ClassName.get(metadata.aggregatingElement()), + metadata.componentElements().stream() + .map(ClassName::get) + .collect(Collectors.toList()), + metadata.testElement() + .map(ClassName::get) + .orElse(null), + metadata.replacedDependencies().stream() + .map(ClassName::get) + .collect(Collectors.toList()), + metadata.dependencyType() == DependencyType.MODULE + ? ClassName.get(metadata.dependency()) + : null, + metadata.dependencyType() == DependencyType.ENTRY_POINT + ? ClassName.get(metadata.dependency()) + : null, + metadata.dependencyType() == DependencyType.COMPONENT_ENTRY_POINT + ? ClassName.get(metadata.dependency()) + : null); + } + private static AggregatedDepsMetadata create(TypeElement element, Elements elements) { AnnotationMirror annotationMirror = Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_DEPS); @@ -74,12 +114,11 @@ abstract class AggregatedDepsMetadata { Processors.getAnnotationValues(elements, annotationMirror); return new AutoValue_AggregatedDepsMetadata( + element, getTestElement(values.get("test"), elements), getComponents(values.get("components"), elements), getDependencyType( - values.get("modules"), - values.get("entryPoints"), - values.get("componentEntryPoints")), + values.get("modules"), values.get("entryPoints"), values.get("componentEntryPoints")), getDependency( values.get("modules"), values.get("entryPoints"), diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java index 151401e60..184894af8 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java @@ -26,7 +26,6 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static javax.lang.model.element.ElementKind.CLASS; import static javax.lang.model.element.ElementKind.INTERFACE; import static javax.lang.model.element.Modifier.ABSTRACT; -import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.STATIC; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; @@ -135,7 +134,7 @@ public final class AggregatedDepsProcessor extends BaseProcessor { || installInCheckDisabled(element), element, "%s is missing an @InstallIn annotation. If this was intentional, see" - + " https://dagger.dev/hilt/compiler-options#disable-install-in-check for how to disable this" + + " https://dagger.dev/hilt/flags#disable-install-in-check for how to disable this" + " check.", element); @@ -167,8 +166,8 @@ public final class AggregatedDepsProcessor extends BaseProcessor { ProcessorErrors.checkState( // Skip ApplicationContextModule, since Hilt manages this module internally. ClassNames.APPLICATION_CONTEXT_MODULE.equals(ClassName.get(module)) - || !Processors.requiresModuleInstance(getElementUtils(), module) - || hasVisibleEmptyConstructor(module), + || !Processors.requiresModuleInstance(getElementUtils(), module) + || Processors.hasVisibleEmptyConstructor(module), module, "Modules that need to be instantiated by Hilt must have a visible, empty constructor."); @@ -444,15 +443,4 @@ public final class AggregatedDepsProcessor extends BaseProcessor { return name.contentEquals("javax.annotation.Generated") || name.contentEquals("javax.annotation.processing.Generated"); } - - private static boolean hasVisibleEmptyConstructor(TypeElement type) { - List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements()); - return constructors.isEmpty() - || constructors.stream() - .filter(constructor -> constructor.getParameters().isEmpty()) - .anyMatch( - constructor -> - !constructor.getModifiers().contains(PRIVATE) - ); - } } diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD index 5da2241f5..6af6692de 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD +++ b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD @@ -52,11 +52,11 @@ java_library( "//java/dagger/hilt/processor/internal:processors", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/kotlin", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/guava/collect", + "//third_party/java/incap", + "//third_party/java/javapoet", ], ) @@ -67,9 +67,9 @@ java_library( "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:kotlin", "//java/dagger/hilt/processor/internal:processors", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/javapoet", ], ) @@ -86,12 +86,13 @@ java_library( "//java/dagger/hilt/processor/internal:component_descriptor", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata", + "//java/dagger/hilt/processor/internal/root/ir", "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/auto:value", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java b/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java index 897ffd144..ed71c98db 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java @@ -38,204 +38,85 @@ public abstract class ComponentDependencies { } /** Returns the modules for a component, without any filtering. */ - public abstract Dependencies modules(); + public abstract ImmutableSetMultimap<ClassName, TypeElement> modules(); /** Returns the entry points associated with the given a component. */ - public abstract Dependencies entryPoints(); + public abstract ImmutableSetMultimap<ClassName, TypeElement> entryPoints(); /** Returns the component entry point associated with the given a component. */ - public abstract Dependencies componentEntryPoints(); - - /** Returns the set of early entry points */ - public abstract ImmutableSet<ClassName> earlyEntryPoints(); - - /** Returns {@code true} if any entry points are annotated with {@code EarlyEntryPoints}. */ - public boolean hasEarlyEntryPoints() { - return !earlyEntryPoints().isEmpty(); - } - - /** - * Returns {@code true} if the test binds or uninstalls test-specific bindings that would prevent - * it from sharing components with other test roots. - */ - public final boolean includesTestDeps(ClassName root) { - return modules().testDeps().keySet().stream().anyMatch((key) -> key.test().equals(root)) - || modules().uninstalledTestDeps().containsKey(root); - } + public abstract ImmutableSetMultimap<ClassName, TypeElement> componentEntryPoints(); @AutoValue.Builder abstract static class Builder { - abstract Dependencies.Builder modulesBuilder(); + abstract ImmutableSetMultimap.Builder<ClassName, TypeElement> modulesBuilder(); - abstract Dependencies.Builder entryPointsBuilder(); + abstract ImmutableSetMultimap.Builder<ClassName, TypeElement> entryPointsBuilder(); - abstract Dependencies.Builder componentEntryPointsBuilder(); - - abstract ImmutableSet.Builder<ClassName> earlyEntryPointsBuilder(); + abstract ImmutableSetMultimap.Builder<ClassName, TypeElement> componentEntryPointsBuilder(); abstract ComponentDependencies build(); } - /** A key used for grouping a test dependency by both its component and test name. */ - @AutoValue - abstract static class TestDepKey { - static TestDepKey of(ClassName component, ClassName test) { - return new AutoValue_ComponentDependencies_TestDepKey(component, test); - } - - /** Returns the name of the component this dependency should be installed in. */ - abstract ClassName component(); - - /** Returns the name of the test that this dependency should be installed in. */ - abstract ClassName test(); - } - - /** - * Holds a set of component dependencies, e.g. modules or entry points. - * - * <p>This class handles separating dependencies into global and test dependencies. Global - * dependencies are installed with every test, where test dependencies are only installed with the - * specified test. The total set of dependencies includes all global + test dependencies. - */ - @AutoValue - public abstract static class Dependencies { - static Builder builder() { - return new AutoValue_ComponentDependencies_Dependencies.Builder(); - } - - /** Returns the global deps keyed by component. */ - abstract ImmutableSetMultimap<ClassName, TypeElement> globalDeps(); - - /** Returns the global test deps keyed by component. */ - abstract ImmutableSetMultimap<ClassName, TypeElement> globalTestDeps(); - - /** Returns the test deps keyed by component and test. */ - abstract ImmutableSetMultimap<TestDepKey, TypeElement> testDeps(); - - /** Returns the uninstalled test deps keyed by test. */ - abstract ImmutableSetMultimap<ClassName, TypeElement> uninstalledTestDeps(); - - /** Returns the global uninstalled test deps. */ - abstract ImmutableSet<TypeElement> globalUninstalledTestDeps(); - - /** Returns the dependencies to be installed in the global singleton component. */ - ImmutableSet<TypeElement> getGlobalSingletonDeps() { - return ImmutableSet.<TypeElement>builder() - .addAll( - globalDeps().get(ClassNames.SINGLETON_COMPONENT).stream() - .filter(dep -> !globalUninstalledTestDeps().contains(dep)) - .collect(toImmutableSet())) - .addAll(globalTestDeps().get(ClassNames.SINGLETON_COMPONENT)) - .build(); - } - - /** Returns the dependencies to be installed in the given component for the given root. */ - public ImmutableSet<TypeElement> get(ClassName component, ClassName root, boolean isTestRoot) { - if (!isTestRoot) { - return globalDeps().get(component); - } - - ImmutableSet<TypeElement> uninstalledTestDepsForRoot = uninstalledTestDeps().get(root); - return ImmutableSet.<TypeElement>builder() - .addAll( - globalDeps().get(component).stream() - .filter(dep -> !uninstalledTestDepsForRoot.contains(dep)) - .filter(dep -> !globalUninstalledTestDeps().contains(dep)) - .collect(toImmutableSet())) - .addAll(globalTestDeps().get(component)) - .addAll(testDeps().get(TestDepKey.of(component, root))) - .build(); - } - - @AutoValue.Builder - abstract static class Builder { - abstract ImmutableSetMultimap.Builder<ClassName, TypeElement> globalDepsBuilder(); - - abstract ImmutableSetMultimap.Builder<ClassName, TypeElement> globalTestDepsBuilder(); - - abstract ImmutableSetMultimap.Builder<TestDepKey, TypeElement> testDepsBuilder(); - - abstract ImmutableSetMultimap.Builder<ClassName, TypeElement> uninstalledTestDepsBuilder(); - - abstract ImmutableSet.Builder<TypeElement> globalUninstalledTestDepsBuilder(); - - abstract Dependencies build(); - } - } - - /** - * Pulls the component dependencies from the {@code packageName}. - * - * <p>Dependency files are generated by the {@link AggregatedDepsProcessor}, and have the form: - * - * <pre>{@code - * {@literal @}AggregatedDeps( - * components = { - * "foo.FooComponent", - * "bar.BarComponent" - * }, - * modules = "baz.BazModule" - * ) - * - * }</pre> - */ + /** Returns the component dependencies for the given metadata. */ public static ComponentDependencies from( - ImmutableSet<ComponentDescriptor> descriptors, Elements elements) { + ImmutableSet<ComponentDescriptor> descriptors, + ImmutableSet<AggregatedDepsMetadata> aggregatedDepsMetadata, + ImmutableSet<AggregatedUninstallModulesMetadata> aggregatedUninstallModulesMetadata, + ImmutableSet<AggregatedEarlyEntryPointMetadata> aggregatedEarlyEntryPointMetadata, + Elements elements) { + ImmutableSet<TypeElement> uninstalledModules = + ImmutableSet.<TypeElement>builder() + .addAll( + aggregatedUninstallModulesMetadata.stream() + .flatMap(metadata -> metadata.uninstallModuleElements().stream()) + // @AggregatedUninstallModules always references the user module, so convert to + // the generated public wrapper if needed. + // TODO(bcorso): Consider converting this to the public module in the processor. + .map(module -> PkgPrivateMetadata.publicModule(module, elements)) + .collect(toImmutableSet())) + .addAll( + aggregatedDepsMetadata.stream() + .flatMap(metadata -> metadata.replacedDependencies().stream()) + .collect(toImmutableSet())) + .build(); + + ComponentDependencies.Builder componentDependencies = ComponentDependencies.builder(); ImmutableSet<ClassName> componentNames = descriptors.stream().map(ComponentDescriptor::component).collect(toImmutableSet()); - ComponentDependencies.Builder componentDependencies = ComponentDependencies.builder(); - for (AggregatedDepsMetadata metadata : AggregatedDepsMetadata.from(elements)) { - Dependencies.Builder builder = null; - switch (metadata.dependencyType()) { - case MODULE: - builder = componentDependencies.modulesBuilder(); - break; - case ENTRY_POINT: - builder = componentDependencies.entryPointsBuilder(); - break; - case COMPONENT_ENTRY_POINT: - builder = componentDependencies.componentEntryPointsBuilder(); - break; - } + for (AggregatedDepsMetadata metadata : aggregatedDepsMetadata) { for (TypeElement componentElement : metadata.componentElements()) { ClassName componentName = ClassName.get(componentElement); checkState( componentNames.contains(componentName), "%s is not a valid Component.", componentName); - if (metadata.testElement().isPresent()) { - // In this case the @InstallIn or @TestInstallIn applies to only the given test root. - ClassName test = ClassName.get(metadata.testElement().get()); - builder.testDepsBuilder().put(TestDepKey.of(componentName, test), metadata.dependency()); - builder.uninstalledTestDepsBuilder().putAll(test, metadata.replacedDependencies()); - } else { - // In this case the @InstallIn or @TestInstallIn applies to all roots - if (!metadata.replacedDependencies().isEmpty()) { - // If there are replacedDependencies() it means this is a @TestInstallIn - builder.globalTestDepsBuilder().put(componentName, metadata.dependency()); - builder.globalUninstalledTestDepsBuilder().addAll(metadata.replacedDependencies()); - } else { - builder.globalDepsBuilder().put(componentName, metadata.dependency()); - } + switch (metadata.dependencyType()) { + case MODULE: + if (!uninstalledModules.contains(metadata.dependency())) { + componentDependencies.modulesBuilder().put(componentName, metadata.dependency()); + } + break; + case ENTRY_POINT: + componentDependencies.entryPointsBuilder().put(componentName, metadata.dependency()); + break; + case COMPONENT_ENTRY_POINT: + componentDependencies + .componentEntryPointsBuilder() + .put(componentName, metadata.dependency()); + break; } } } - AggregatedUninstallModulesMetadata.from(elements) - .forEach( - metadata -> - componentDependencies - .modulesBuilder() - .uninstalledTestDepsBuilder() - .putAll( - ClassName.get(metadata.testElement()), - metadata.uninstallModuleElements().stream() - .map(module -> PkgPrivateMetadata.publicModule(module, elements)) - .collect(toImmutableSet()))); - - AggregatedEarlyEntryPointMetadata.from(elements).stream() - .map(AggregatedEarlyEntryPointMetadata::earlyEntryPoint) - .map(entryPoint -> PkgPrivateMetadata.publicEarlyEntryPoint(entryPoint, elements)) - .map(ClassName::get) - .forEach(componentDependencies.earlyEntryPointsBuilder()::add); + componentDependencies + .entryPointsBuilder() + .putAll( + ClassNames.SINGLETON_COMPONENT, + aggregatedEarlyEntryPointMetadata.stream() + .map(AggregatedEarlyEntryPointMetadata::earlyEntryPoint) + // @AggregatedEarlyEntryPointMetadata always references the user module, so convert + // to the generated public wrapper if needed. + // TODO(bcorso): Consider converting this to the public module in the processor. + .map(entryPoint -> PkgPrivateMetadata.publicEarlyEntryPoint(entryPoint, elements)) + .collect(toImmutableSet())); return componentDependencies.build(); } diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java index ff344a651..41c6b7c75 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java @@ -44,6 +44,11 @@ public abstract class PkgPrivateMetadata { return publicDep(element, elements, ClassNames.EARLY_ENTRY_POINT); } + /** Returns the public Hilt wrapped type or the type itself if it is already public. */ + public static TypeElement publicEntryPoint(TypeElement element, Elements elements) { + return publicDep(element, elements, ClassNames.ENTRY_POINT); + } + private static TypeElement publicDep( TypeElement element, Elements elements, ClassName annotation) { return of(elements, element, annotation) @@ -102,8 +107,11 @@ public abstract class PkgPrivateMetadata { if (annotation.equals(ClassNames.MODULE) ) { - // Skip modules that require a module instance. Required by - // dagger (b/31489617) + // Skip modules that require a module instance. Otherwise Dagger validation will (correctly) + // fail on the wrapper saying a public module can't include a private one, which makes the + // error more confusing for users since they probably aren't aware of the wrapper. When + // skipped, if the root is in a different package, the error will instead just be on the + // generated Hilt component. if (Processors.requiresModuleInstance(elements, MoreElements.asType(element))) { return Optional.empty(); } diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java index 02e7f5d56..6c6a73481 100644 --- a/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java +++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java @@ -20,6 +20,7 @@ import static com.google.auto.common.MoreElements.asType; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import dagger.hilt.processor.internal.BaseProcessor; import dagger.hilt.processor.internal.ClassNames; @@ -53,10 +54,16 @@ public final class AliasOfProcessor extends BaseProcessor { AnnotationMirror annotationMirror = Processors.getAnnotationMirror(element, ClassNames.ALIAS_OF); - TypeElement defineComponentScope = - Processors.getAnnotationClassValue(getElementUtils(), annotationMirror, "value"); + ImmutableList<TypeElement> defineComponentScopes = + Processors.getAnnotationClassValues(getElementUtils(), annotationMirror, "value"); - new AliasOfPropagatedDataGenerator(getProcessingEnv(), asType(element), defineComponentScope) + ProcessorErrors.checkState( + defineComponentScopes.size() >= 1, + element, + "@AliasOf annotation %s must declare at least one scope to alias.", + annotationMirror); + + new AliasOfPropagatedDataGenerator(getProcessingEnv(), asType(element), defineComponentScopes) .generate(); } } diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java index 1d7edf285..a2441f916 100644 --- a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java +++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java @@ -16,6 +16,7 @@ package dagger.hilt.processor.internal.aliasof; +import com.google.common.collect.ImmutableList; import com.squareup.javapoet.AnnotationSpec; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; @@ -28,26 +29,32 @@ final class AliasOfPropagatedDataGenerator { private final ProcessingEnvironment processingEnv; private final TypeElement aliasScope; - private final TypeElement defineComponentScope; + private final ImmutableList<TypeElement> defineComponentScopes; AliasOfPropagatedDataGenerator( ProcessingEnvironment processingEnv, TypeElement aliasScope, - TypeElement defineComponentScope) { + ImmutableList<TypeElement> defineComponentScopes) { this.processingEnv = processingEnv; this.aliasScope = aliasScope; - this.defineComponentScope = defineComponentScope; + this.defineComponentScopes = defineComponentScopes; } void generate() throws IOException { Processors.generateAggregatingClass( ClassNames.ALIAS_OF_PROPAGATED_DATA_PACKAGE, - AnnotationSpec.builder(ClassNames.ALIAS_OF_PROPAGATED_DATA) - .addMember("defineComponentScope", "$T.class", defineComponentScope) - .addMember("alias", "$T.class", aliasScope) - .build(), + propagatedDataAnnotation(), aliasScope, getClass(), processingEnv); } + + private AnnotationSpec propagatedDataAnnotation() { + AnnotationSpec.Builder builder = AnnotationSpec.builder(ClassNames.ALIAS_OF_PROPAGATED_DATA); + for (TypeElement defineComponentScope : defineComponentScopes) { + builder.addMember("defineComponentScopes", "$T.class", defineComponentScope); + } + builder.addMember("alias", "$T.class", aliasScope); + return builder.build(); + } } diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java index 30a2c70c6..d0c89dbd2 100644 --- a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java +++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java @@ -16,15 +16,20 @@ package dagger.hilt.processor.internal.aliasof; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.AggregatedElements; import dagger.hilt.processor.internal.AnnotationValues; +import dagger.hilt.processor.internal.BadInputException; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.root.ir.AliasOfPropagatedDataIr; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.TypeElement; @@ -35,22 +40,42 @@ import javax.lang.model.util.Elements; * dagger.hilt.internal.aliasof.AliasOfPropagatedData} annotation. */ @AutoValue -abstract class AliasOfPropagatedDataMetadata { +public abstract class AliasOfPropagatedDataMetadata { - abstract TypeElement defineComponentScopeElement(); + /** Returns the aggregating element */ + public abstract TypeElement aggregatingElement(); + + abstract ImmutableList<TypeElement> defineComponentScopeElements(); abstract TypeElement aliasElement(); - static ImmutableSet<AliasOfPropagatedDataMetadata> from(Elements elements) { - return AggregatedElements.from( + /** Returns metadata for all aggregated elements in the aggregating package. */ + public static ImmutableSet<AliasOfPropagatedDataMetadata> from(Elements elements) { + return from( + AggregatedElements.from( ClassNames.ALIAS_OF_PROPAGATED_DATA_PACKAGE, ClassNames.ALIAS_OF_PROPAGATED_DATA, - elements) - .stream() + elements), + elements); + } + + /** Returns metadata for each aggregated element. */ + public static ImmutableSet<AliasOfPropagatedDataMetadata> from( + ImmutableSet<TypeElement> aggregatedElements, Elements elements) { + return aggregatedElements.stream() .map(aggregatedElement -> create(aggregatedElement, elements)) .collect(toImmutableSet()); } + public static AliasOfPropagatedDataIr toIr(AliasOfPropagatedDataMetadata metadata) { + return new AliasOfPropagatedDataIr( + ClassName.get(metadata.aggregatingElement()), + metadata.defineComponentScopeElements().stream() + .map(ClassName::get) + .collect(toImmutableList()), + ClassName.get(metadata.aliasElement())); + } + private static AliasOfPropagatedDataMetadata create(TypeElement element, Elements elements) { AnnotationMirror annotationMirror = Processors.getAnnotationMirror(element, ClassNames.ALIAS_OF_PROPAGATED_DATA); @@ -58,8 +83,22 @@ abstract class AliasOfPropagatedDataMetadata { ImmutableMap<String, AnnotationValue> values = Processors.getAnnotationValues(elements, annotationMirror); + ImmutableList<TypeElement> defineComponentScopes; + if (values.containsKey("defineComponentScopes")) { + defineComponentScopes = + ImmutableList.copyOf( + AnnotationValues.getTypeElements(values.get("defineComponentScopes"))); + } else if (values.containsKey("defineComponentScope")) { + // Older version of AliasOfPropagatedData only passed a single defineComponentScope class + // value. Fall back on reading the single value if we get old propagated data. + defineComponentScopes = + ImmutableList.of(AnnotationValues.getTypeElement(values.get("defineComponentScope"))); + } else { + throw new BadInputException( + "AliasOfPropagatedData is missing defineComponentScopes", element); + } + return new AutoValue_AliasOfPropagatedDataMetadata( - AnnotationValues.getTypeElement(values.get("defineComponentScope")), - AnnotationValues.getTypeElement(values.get("alias"))); + element, defineComponentScopes, AnnotationValues.getTypeElement(values.get("alias"))); } } diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java index 18951bdf9..1fee2a31a 100644 --- a/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java +++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java @@ -16,35 +16,46 @@ package dagger.hilt.processor.internal.aliasof; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.squareup.javapoet.ClassName; +import dagger.hilt.processor.internal.ComponentDescriptor; import dagger.hilt.processor.internal.ProcessorErrors; -import javax.lang.model.util.Elements; /** * Extracts a multimap of aliases annotated with {@link dagger.hilt.migration.AliasOf} mapping them * to scopes they are alias of. */ public final class AliasOfs { - public static AliasOfs create(Elements elements, ImmutableSet<ClassName> defineComponentScopes) { + public static AliasOfs create( + ImmutableSet<AliasOfPropagatedDataMetadata> metadatas, + ImmutableSet<ComponentDescriptor> componentDescriptors) { + ImmutableSet<ClassName> defineComponentScopes = + componentDescriptors.stream() + .flatMap(descriptor -> descriptor.scopes().stream()) + .collect(toImmutableSet()); + ImmutableSetMultimap.Builder<ClassName, ClassName> builder = ImmutableSetMultimap.builder(); - AliasOfPropagatedDataMetadata.from(elements) - .forEach( - metadata -> { - ClassName defineComponentScopeName = - ClassName.get(metadata.defineComponentScopeElement()); - ClassName aliasScopeName = ClassName.get(metadata.aliasElement()); - ProcessorErrors.checkState( - defineComponentScopes.contains(defineComponentScopeName), - metadata.aliasElement(), - "The scope %s cannot be an alias for %s. You can only have aliases of a scope" - + " defined directly on a @DefineComponent type.", - aliasScopeName, - defineComponentScopeName); - builder.put(defineComponentScopeName, aliasScopeName); - }); + metadatas.forEach( + metadata -> { + ClassName aliasScopeName = ClassName.get(metadata.aliasElement()); + metadata + .defineComponentScopeElements() + .forEach( + defineComponentScope -> { + ClassName defineComponentScopeName = ClassName.get(defineComponentScope); + ProcessorErrors.checkState( + defineComponentScopes.contains(defineComponentScopeName), + metadata.aliasElement(), + "The scope %s cannot be an alias for %s. You can only have aliases of a" + + " scope defined directly on a @DefineComponent type.", + aliasScopeName, + defineComponentScopeName); + builder.put(defineComponentScopeName, aliasScopeName); + }); + }); return new AliasOfs(builder.build()); } diff --git a/java/dagger/hilt/processor/internal/aliasof/BUILD b/java/dagger/hilt/processor/internal/aliasof/BUILD index ffd0c9ae8..d589cb2c5 100644 --- a/java/dagger/hilt/processor/internal/aliasof/BUILD +++ b/java/dagger/hilt/processor/internal/aliasof/BUILD @@ -35,11 +35,11 @@ java_library( "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/guava/collect", + "//third_party/java/incap", + "//third_party/java/javapoet", ], ) @@ -52,12 +52,14 @@ java_library( deps = [ "//java/dagger/hilt/processor/internal:aggregated_elements", "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:component_descriptor", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", + "//java/dagger/hilt/processor/internal/root/ir", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/auto:value", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/processor/internal/definecomponent/BUILD b/java/dagger/hilt/processor/internal/definecomponent/BUILD index 43f4dfda0..9701a06dd 100644 --- a/java/dagger/hilt/processor/internal/definecomponent/BUILD +++ b/java/dagger/hilt/processor/internal/definecomponent/BUILD @@ -21,6 +21,9 @@ java_plugin( name = "processor", generates_api = 1, processor_class = "dagger.hilt.processor.internal.definecomponent.DefineComponentProcessor", + visibility = [ + "//java/dagger/hilt:__pkg__", + ], deps = [":processor_lib"], ) @@ -34,10 +37,10 @@ java_library( "//java/dagger/hilt/processor/internal:base_processor", "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:processors", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/auto:service", + "//third_party/java/guava/collect", + "//third_party/java/incap", + "//third_party/java/javapoet", ], ) @@ -55,11 +58,12 @@ java_library( "//java/dagger/hilt/processor/internal:component_descriptor", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", + "//java/dagger/hilt/processor/internal/root/ir", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java index 36ac28fb3..a9da094d7 100644 --- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java +++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java @@ -21,11 +21,13 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.AggregatedElements; import dagger.hilt.processor.internal.AnnotationValues; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.ProcessorErrors; import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.root.ir.DefineComponentClassesIr; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.TypeElement; @@ -36,7 +38,10 @@ import javax.lang.model.util.Elements; * dagger.hilt.internal.definecomponent.DefineComponentClasses} annotation. */ @AutoValue -abstract class DefineComponentClassesMetadata { +public abstract class DefineComponentClassesMetadata { + + /** Returns the aggregating element */ + public abstract TypeElement aggregatingElement(); /** * Returns the element annotated with {@code dagger.hilt.internal.definecomponent.DefineComponent} @@ -52,12 +57,20 @@ abstract class DefineComponentClassesMetadata { return !isComponent(); } - static ImmutableSet<DefineComponentClassesMetadata> from(Elements elements) { - return AggregatedElements.from( + /** Returns metadata for all aggregated elements in the aggregating package. */ + public static ImmutableSet<DefineComponentClassesMetadata> from(Elements elements) { + return from( + AggregatedElements.from( ClassNames.DEFINE_COMPONENT_CLASSES_PACKAGE, ClassNames.DEFINE_COMPONENT_CLASSES, - elements) - .stream() + elements), + elements); + } + + /** Returns metadata for each aggregated element. */ + public static ImmutableSet<DefineComponentClassesMetadata> from( + ImmutableSet<TypeElement> aggregatedElements, Elements elements) { + return aggregatedElements.stream() .map(aggregatedElement -> create(aggregatedElement, elements)) .collect(toImmutableSet()); } @@ -92,6 +105,13 @@ abstract class DefineComponentClassesMetadata { ClassNames.DEFINE_COMPONENT_CLASSES.simpleName(), isComponent ? "component" : "builder", componentOrBuilderName); - return new AutoValue_DefineComponentClassesMetadata(componentOrBuilderElement, isComponent); + return new AutoValue_DefineComponentClassesMetadata( + element, componentOrBuilderElement, isComponent); + } + + public static DefineComponentClassesIr toIr(DefineComponentClassesMetadata metadata) { + return new DefineComponentClassesIr( + ClassName.get(metadata.aggregatingElement()), + ClassName.get(metadata.element())); } } diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java index efa501191..e9200d001 100644 --- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java +++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java @@ -33,7 +33,6 @@ import java.util.LinkedHashMap; import java.util.Map; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; /** * A utility class for getting {@link DefineComponentMetadata} and {@link @@ -78,10 +77,8 @@ public final class DefineComponents { } /** Returns the set of aggregated {@link ComponentDescriptor}s. */ - public ImmutableSet<ComponentDescriptor> getComponentDescriptors(Elements elements) { - ImmutableSet<DefineComponentClassesMetadata> aggregatedMetadatas = - DefineComponentClassesMetadata.from(elements); - + public ImmutableSet<ComponentDescriptor> getComponentDescriptors( + ImmutableSet<DefineComponentClassesMetadata> aggregatedMetadatas) { ImmutableSet<DefineComponentMetadata> components = aggregatedMetadatas.stream() .filter(DefineComponentClassesMetadata::isComponent) diff --git a/java/dagger/hilt/processor/internal/disableinstallincheck/BUILD b/java/dagger/hilt/processor/internal/disableinstallincheck/BUILD index e3865a608..c06f9fee9 100644 --- a/java/dagger/hilt/processor/internal/disableinstallincheck/BUILD +++ b/java/dagger/hilt/processor/internal/disableinstallincheck/BUILD @@ -37,9 +37,9 @@ java_library( "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/incap", + "//third_party/java/auto:service", + "//third_party/java/guava/collect", + "//third_party/java/incap", ], ) diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java index ed347bd17..1a93cf2d0 100644 --- a/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java +++ b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java @@ -21,10 +21,12 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.AggregatedElements; import dagger.hilt.processor.internal.AnnotationValues; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.root.ir.AggregatedEarlyEntryPointIr; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.TypeElement; @@ -32,25 +34,41 @@ import javax.lang.model.util.Elements; /** * A class that represents the values stored in an {@link - * dagger.hilt.android.internal.uninstallmodules.AggregatedUninstallModules} annotation. + * dagger.hilt.android.internal.earlyentrypoint.AggregatedEarlyEntryPoint} annotation. */ @AutoValue public abstract class AggregatedEarlyEntryPointMetadata { + /** Returns the aggregating element */ + public abstract TypeElement aggregatingElement(); + /** Returns the element annotated with {@link dagger.hilt.android.EarlyEntryPoint}. */ public abstract TypeElement earlyEntryPoint(); - /** Returns all aggregated deps in the aggregating package. */ + /** Returns metadata for all aggregated elements in the aggregating package. */ public static ImmutableSet<AggregatedEarlyEntryPointMetadata> from(Elements elements) { - return AggregatedElements.from( + return from( + AggregatedElements.from( ClassNames.AGGREGATED_EARLY_ENTRY_POINT_PACKAGE, ClassNames.AGGREGATED_EARLY_ENTRY_POINT, - elements) - .stream() + elements), + elements); + } + + /** Returns metadata for each aggregated element. */ + public static ImmutableSet<AggregatedEarlyEntryPointMetadata> from( + ImmutableSet<TypeElement> aggregatedElements, Elements elements) { + return aggregatedElements.stream() .map(aggregatedElement -> create(aggregatedElement, elements)) .collect(toImmutableSet()); } + public static AggregatedEarlyEntryPointIr toIr(AggregatedEarlyEntryPointMetadata metadata) { + return new AggregatedEarlyEntryPointIr( + ClassName.get(metadata.aggregatingElement()), + ClassName.get(metadata.earlyEntryPoint())); + } + private static AggregatedEarlyEntryPointMetadata create(TypeElement element, Elements elements) { AnnotationMirror annotationMirror = Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_EARLY_ENTRY_POINT); @@ -59,6 +77,7 @@ public abstract class AggregatedEarlyEntryPointMetadata { Processors.getAnnotationValues(elements, annotationMirror); return new AutoValue_AggregatedEarlyEntryPointMetadata( + element, elements.getTypeElement(AnnotationValues.getString(values.get("earlyEntryPoint")))); } } diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD b/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD index 3573e8603..7bbc90ca2 100644 --- a/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD +++ b/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD @@ -34,11 +34,11 @@ java_library( "//java/dagger/hilt/processor/internal:base_processor", "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:processors", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/guava/collect", + "//third_party/java/incap", + "//third_party/java/javapoet", ], ) @@ -51,9 +51,11 @@ java_library( "//java/dagger/hilt/processor/internal:aggregated_elements", "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:processors", + "//java/dagger/hilt/processor/internal/root/ir", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:value", + "//third_party/java/auto:value", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/processor/internal/generatesrootinput/BUILD b/java/dagger/hilt/processor/internal/generatesrootinput/BUILD index f24ffb716..2b56408e9 100644 --- a/java/dagger/hilt/processor/internal/generatesrootinput/BUILD +++ b/java/dagger/hilt/processor/internal/generatesrootinput/BUILD @@ -36,10 +36,10 @@ java_library( "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/auto:service", + "//third_party/java/guava/collect", + "//third_party/java/incap", + "//third_party/java/javapoet", ], ) @@ -53,9 +53,9 @@ java_library( "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/processor/internal/originatingelement/BUILD b/java/dagger/hilt/processor/internal/originatingelement/BUILD index 50dbcf6b1..89425357e 100644 --- a/java/dagger/hilt/processor/internal/originatingelement/BUILD +++ b/java/dagger/hilt/processor/internal/originatingelement/BUILD @@ -34,10 +34,10 @@ java_library( "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/incap", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/guava/collect", + "//third_party/java/incap", ], ) diff --git a/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java b/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java index 722b99b72..d79c0a710 100644 --- a/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java +++ b/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java @@ -26,12 +26,17 @@ import javax.lang.model.element.TypeElement; /** Generates an {@link dagger.hilt.internal.aggregatedroot.AggregatedRoot}. */ final class AggregatedRootGenerator { private final TypeElement rootElement; + private final TypeElement originatingRootElement; private final TypeElement rootAnnotation; private final ProcessingEnvironment processingEnv; AggregatedRootGenerator( - TypeElement rootElement, TypeElement rootAnnotation, ProcessingEnvironment processingEnv) { + TypeElement rootElement, + TypeElement originatingRootElement, + TypeElement rootAnnotation, + ProcessingEnvironment processingEnv) { this.rootElement = rootElement; + this.originatingRootElement = originatingRootElement; this.rootAnnotation = rootAnnotation; this.processingEnv = processingEnv; } @@ -41,6 +46,7 @@ final class AggregatedRootGenerator { ClassNames.AGGREGATED_ROOT_PACKAGE, AnnotationSpec.builder(ClassNames.AGGREGATED_ROOT) .addMember("root", "$S", rootElement.getQualifiedName()) + .addMember("originatingRoot", "$S", originatingRootElement.getQualifiedName()) .addMember("rootAnnotation", "$T.class", rootAnnotation) .build(), rootElement, diff --git a/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java b/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java index 961689b7c..70ee63f96 100644 --- a/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java +++ b/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java @@ -19,16 +19,19 @@ package dagger.hilt.processor.internal.root; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.AggregatedElements; import dagger.hilt.processor.internal.AnnotationValues; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.root.ir.AggregatedRootIr; +import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; /** * Represents the values stored in an {@link dagger.hilt.internal.aggregatedroot.AggregatedRoot}. @@ -36,29 +39,69 @@ import javax.lang.model.util.Elements; @AutoValue abstract class AggregatedRootMetadata { + /** Returns the aggregating element */ + public abstract TypeElement aggregatingElement(); + /** Returns the element that was annotated with the root annotation. */ abstract TypeElement rootElement(); + /** + * Returns the originating root element. In most cases this will be the same as + * {@link #rootElement()}. + */ + abstract TypeElement originatingRootElement(); + /** Returns the root annotation as an element. */ abstract TypeElement rootAnnotation(); - static ImmutableSet<AggregatedRootMetadata> from(Elements elements) { - return AggregatedElements.from( - ClassNames.AGGREGATED_ROOT_PACKAGE, ClassNames.AGGREGATED_ROOT, elements) - .stream() - .map(aggregatedElement -> create(aggregatedElement, elements)) + /** Returns whether this root can use a shared component. */ + abstract boolean allowsSharingComponent(); + + @Memoized + RootType rootType() { + return RootType.of(rootElement()); + } + + static ImmutableSet<AggregatedRootMetadata> from(ProcessingEnvironment env) { + return from( + AggregatedElements.from( + ClassNames.AGGREGATED_ROOT_PACKAGE, ClassNames.AGGREGATED_ROOT, env.getElementUtils()), + env); + } + + /** Returns metadata for each aggregated element. */ + public static ImmutableSet<AggregatedRootMetadata> from( + ImmutableSet<TypeElement> aggregatedElements, ProcessingEnvironment env) { + return aggregatedElements.stream() + .map(aggregatedElement -> create(aggregatedElement, env)) .collect(toImmutableSet()); } - private static AggregatedRootMetadata create(TypeElement element, Elements elements) { + public static AggregatedRootIr toIr(AggregatedRootMetadata metadata) { + return new AggregatedRootIr( + ClassName.get(metadata.aggregatingElement()), + ClassName.get(metadata.rootElement()), + ClassName.get(metadata.originatingRootElement()), + ClassName.get(metadata.rootAnnotation()), + metadata.allowsSharingComponent()); + } + + private static AggregatedRootMetadata create(TypeElement element, ProcessingEnvironment env) { AnnotationMirror annotationMirror = Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_ROOT); ImmutableMap<String, AnnotationValue> values = - Processors.getAnnotationValues(elements, annotationMirror); + Processors.getAnnotationValues(env.getElementUtils(), annotationMirror); + TypeElement rootElement = + env.getElementUtils().getTypeElement(AnnotationValues.getString(values.get("root"))); + boolean allowSharingComponent = true; return new AutoValue_AggregatedRootMetadata( - elements.getTypeElement(AnnotationValues.getString(values.get("root"))), - AnnotationValues.getTypeElement(values.get("rootAnnotation"))); + element, + rootElement, + env.getElementUtils() + .getTypeElement(AnnotationValues.getString(values.get("originatingRoot"))), + AnnotationValues.getTypeElement(values.get("rootAnnotation")), + allowSharingComponent); } } diff --git a/java/dagger/hilt/processor/internal/root/BUILD b/java/dagger/hilt/processor/internal/root/BUILD index 5ce762fa6..126662c02 100644 --- a/java/dagger/hilt/processor/internal/root/BUILD +++ b/java/dagger/hilt/processor/internal/root/BUILD @@ -18,48 +18,95 @@ package(default_visibility = ["//:src"]) java_plugin( - name = "plugin", + name = "component_tree_deps_plugin", generates_api = 1, - processor_class = "dagger.hilt.processor.internal.root.RootProcessor", + processor_class = "dagger.hilt.processor.internal.root.ComponentTreeDepsProcessor", deps = [ - ":processor_lib", + ":component_tree_deps_processor_lib", ], ) java_library( - name = "processor_lib", + name = "component_tree_deps_processor_lib", srcs = [ - "AggregatedRootGenerator.java", "ComponentGenerator.java", + "ComponentTreeDepsProcessor.java", "EarlySingletonComponentCreatorGenerator.java", - "ProcessedRootSentinelGenerator.java", "RootFileFormatter.java", "RootGenerator.java", - "RootProcessor.java", "TestComponentDataGenerator.java", + ], + deps = [ + ":root_metadata", + "//java/dagger/hilt/android/processor/internal/androidentrypoint:android_generators", + "//java/dagger/hilt/android/processor/internal/androidentrypoint:metadata", + "//java/dagger/hilt/processor/internal:base_processor", + "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:compiler_options", + "//java/dagger/hilt/processor/internal:component_descriptor", + "//java/dagger/hilt/processor/internal:component_names", + "//java/dagger/hilt/processor/internal:processor_errors", + "//java/dagger/hilt/processor/internal:processors", + "//java/dagger/hilt/processor/internal/aggregateddeps:component_dependencies", + "//java/dagger/hilt/processor/internal/aliasof:alias_ofs", + "//java/dagger/hilt/processor/internal/definecomponent:define_components", + "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata", + "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata", + "//java/dagger/internal/codegen/extension", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/graph", + "//third_party/java/incap", + "//third_party/java/javapoet", + ], +) + +java_plugin( + name = "root_plugin", + generates_api = 1, + processor_class = "dagger.hilt.processor.internal.root.RootProcessor", + deps = [ + ":root_processor_lib", + ], +) + +java_library( + name = "root_processor_lib", + srcs = [ + "AggregatedRootGenerator.java", + "ComponentTreeDepsGenerator.java", + "ProcessedRootSentinelGenerator.java", + "RootProcessor.java", "TestInjectorGenerator.java", ], deps = [ ":root_metadata", ":root_type", + "//java/dagger/hilt/android/processor/internal/androidentrypoint:android_generators", + "//java/dagger/hilt/android/processor/internal/androidentrypoint:metadata", + "//java/dagger/hilt/processor/internal:aggregated_elements", "//java/dagger/hilt/processor/internal:base_processor", "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:compiler_options", - "//java/dagger/hilt/processor/internal:component_descriptor", "//java/dagger/hilt/processor/internal:component_names", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/hilt/processor/internal/aggregateddeps:component_dependencies", + "//java/dagger/hilt/processor/internal/aliasof:alias_ofs", "//java/dagger/hilt/processor/internal/definecomponent:define_components", + "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata", "//java/dagger/hilt/processor/internal/generatesrootinput:generates_root_inputs", + "//java/dagger/hilt/processor/internal/root/ir", + "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:graph", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/incap", + "//third_party/java/javapoet", ], ) @@ -68,6 +115,7 @@ java_library( srcs = [ "AggregatedRootMetadata.java", "ComponentTree.java", + "ComponentTreeDepsMetadata.java", "ProcessedRootSentinelMetadata.java", "Root.java", "RootMetadata.java", @@ -77,21 +125,21 @@ java_library( ":root_type", "//java/dagger/hilt/processor/internal:aggregated_elements", "//java/dagger/hilt/processor/internal:classnames", - "//java/dagger/hilt/processor/internal:compiler_options", "//java/dagger/hilt/processor/internal:component_descriptor", "//java/dagger/hilt/processor/internal:kotlin", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/hilt/processor/internal/aggregateddeps:component_dependencies", "//java/dagger/hilt/processor/internal/aliasof:alias_ofs", + "//java/dagger/hilt/processor/internal/root/ir", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/kotlin", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:graph", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/graph", + "//third_party/java/javapoet", ], ) @@ -101,7 +149,7 @@ java_library( deps = [ "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:processors", - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsGenerator.java b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsGenerator.java new file mode 100644 index 000000000..34c586aba --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsGenerator.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import static javax.lang.model.element.Modifier.PUBLIC; + +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.TypeSpec; +import dagger.hilt.processor.internal.AggregatedElements; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import java.io.IOException; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; + +/** Generates an {@link dagger.hilt.internal.componenttreedeps.ComponentTreeDeps}. */ +final class ComponentTreeDepsGenerator { + // Keeps track of already generated proxies. For correctness, this same instance of + // ComponentTreeDepsGenerator must be used for a given round. + private final Set<ClassName> generatedProxies = new HashSet<>(); + private final ProcessingEnvironment env; + + ComponentTreeDepsGenerator(ProcessingEnvironment env) { + this.env = env; + } + + void generate(ComponentTreeDepsMetadata metadata) throws IOException { + ClassName name = metadata.name(); + TypeSpec.Builder builder = + TypeSpec.classBuilder(name) + // No originating element since this is generated by the aggregating processor. + .addAnnotation(componentTreeDepsAnnotation(metadata)); + + Processors.addGeneratedAnnotation(builder, env, ClassNames.ROOT_PROCESSOR.toString()); + + JavaFile.builder(name.packageName(), builder.build()).build().writeTo(env.getFiler()); + } + + AnnotationSpec componentTreeDepsAnnotation(ComponentTreeDepsMetadata metadata) + throws IOException { + AnnotationSpec.Builder builder = AnnotationSpec.builder(ClassNames.COMPONENT_TREE_DEPS); + addDeps(builder, metadata.aggregatedRootDeps(), "rootDeps"); + addDeps(builder, metadata.defineComponentDeps(), "defineComponentDeps"); + addDeps(builder, metadata.aliasOfDeps(), "aliasOfDeps"); + addDeps(builder, metadata.aggregatedDeps(), "aggregatedDeps"); + addDeps(builder, metadata.aggregatedUninstallModulesDeps(), "uninstallModulesDeps"); + addDeps(builder, metadata.aggregatedEarlyEntryPointDeps(), "earlyEntryPointDeps"); + return builder.build(); + } + + private void addDeps(AnnotationSpec.Builder builder, ImmutableSet<TypeElement> deps, String name) + throws IOException { + for (TypeElement dep : deps) { + builder.addMember(name, "$T.class", maybeWrapInPublicProxy(dep)); + } + } + + /** + * This method will return the public proxy for {@code dep} if it is not public, otherwise it will + * return {@code dep} itself. It will also generate the proxy if it doesn't already exist. + * + * <p>Note: These proxies are only used for serialization. The proxy will be unwrapped when + * converting to {@link ComponentTreeDepsMetadata}. + * + * <p>Note: The public proxy is needed because Hilt versions < 2.35 generated package-private + * aggregating elements, which can't be referenced directly in the {@code @ComponentTreeDeps}. + */ + private ClassName maybeWrapInPublicProxy(TypeElement dep) throws IOException { + Optional<ClassName> proxyName = AggregatedElements.aggregatedElementProxyName(dep); + if (proxyName.isPresent()) { + // Check the set of already generated proxies to ensure we don't regenerate the proxy in + // this round. Also check that the element doesn't already exist to ensure we don't regenerate + // a proxy generated in a previous round. + if (generatedProxies.add(proxyName.get()) + && env.getElementUtils().getTypeElement(proxyName.get().canonicalName()) == null) { + generateProxy(dep, proxyName.get()); + } + return proxyName.get(); + } + return ClassName.get(dep); + } + + private void generateProxy(TypeElement dep, ClassName proxyName) throws IOException { + TypeSpec.Builder builder = + TypeSpec.classBuilder(proxyName) + .addModifiers(PUBLIC) + // No originating element since this is generated by the aggregating processor. + .addAnnotation( + AnnotationSpec.builder(ClassNames.AGGREGATED_ELEMENT_PROXY) + .addMember("value", "$T.class", dep) + .build()); + + Processors.addGeneratedAnnotation(builder, env, ClassNames.ROOT_PROCESSOR.toString()); + + JavaFile.builder(proxyName.packageName(), builder.build()).build().writeTo(env.getFiler()); + } +} diff --git a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsMetadata.java b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsMetadata.java new file mode 100644 index 000000000..7d1f3670d --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsMetadata.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import static com.google.common.base.Preconditions.checkArgument; +import static dagger.hilt.processor.internal.AggregatedElements.unwrapProxies; +import static dagger.hilt.processor.internal.AnnotationValues.getTypeElements; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.root.ir.ComponentTreeDepsIr; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; + +/** + * Represents the values stored in an {@link + * dagger.hilt.internal.componenttreedeps.ComponentTreeDeps}. + * + * <p>This class is used in both writing ({@link ComponentTreeDepsGenerator}) and reading ({@link + * ComponentTreeDepsProcessor}) of the {@code @ComponentTreeDeps} annotation. + */ +@AutoValue +abstract class ComponentTreeDepsMetadata { + /** + * Returns the name of the element annotated with {@link + * dagger.hilt.internal.componenttreedeps.ComponentTreeDeps}. + */ + abstract ClassName name(); + + /** Returns the {@link dagger.hilt.internal.aggregatedroot.AggregatedRoot} deps. */ + abstract ImmutableSet<TypeElement> aggregatedRootDeps(); + + /** Returns the {@link dagger.hilt.internal.definecomponent.DefineComponentClasses} deps. */ + abstract ImmutableSet<TypeElement> defineComponentDeps(); + + /** Returns the {@link dagger.hilt.internal.aliasof.AliasOfPropagatedData} deps. */ + abstract ImmutableSet<TypeElement> aliasOfDeps(); + + /** Returns the {@link dagger.hilt.internal.aggregateddeps.AggregatedDeps} deps. */ + abstract ImmutableSet<TypeElement> aggregatedDeps(); + + /** Returns the {@link dagger.hilt.android.uninstallmodules.AggregatedUninstallModules} deps. */ + abstract ImmutableSet<TypeElement> aggregatedUninstallModulesDeps(); + + /** Returns the {@link dagger.hilt.android.earlyentrypoint.AggregatedEarlyEntryPoint} deps. */ + abstract ImmutableSet<TypeElement> aggregatedEarlyEntryPointDeps(); + + static ComponentTreeDepsMetadata from(TypeElement element, Elements elements) { + checkArgument(Processors.hasAnnotation(element, ClassNames.COMPONENT_TREE_DEPS)); + AnnotationMirror annotationMirror = + Processors.getAnnotationMirror(element, ClassNames.COMPONENT_TREE_DEPS); + + ImmutableMap<String, AnnotationValue> values = + Processors.getAnnotationValues(elements, annotationMirror); + + return create( + ClassName.get(element), + unwrapProxies(getTypeElements(values.get("rootDeps")), elements), + unwrapProxies(getTypeElements(values.get("defineComponentDeps")), elements), + unwrapProxies(getTypeElements(values.get("aliasOfDeps")), elements), + unwrapProxies(getTypeElements(values.get("aggregatedDeps")), elements), + unwrapProxies(getTypeElements(values.get("uninstallModulesDeps")), elements), + unwrapProxies(getTypeElements(values.get("earlyEntryPointDeps")), elements)); + } + + static ComponentTreeDepsMetadata from(ComponentTreeDepsIr ir, Elements elements) { + return create( + ir.getName(), + ir.getRootDeps().stream() + .map(it -> elements.getTypeElement(it.canonicalName())) + .collect(toImmutableSet()), + ir.getDefineComponentDeps().stream() + .map(it -> elements.getTypeElement(it.canonicalName())) + .collect(toImmutableSet()), + ir.getAliasOfDeps().stream() + .map(it -> elements.getTypeElement(it.canonicalName())) + .collect(toImmutableSet()), + ir.getAggregatedDeps().stream() + .map(it -> elements.getTypeElement(it.canonicalName())) + .collect(toImmutableSet()), + ir.getUninstallModulesDeps().stream() + .map(it -> elements.getTypeElement(it.canonicalName())) + .collect(toImmutableSet()), + ir.getEarlyEntryPointDeps().stream() + .map(it -> elements.getTypeElement(it.canonicalName())) + .collect(toImmutableSet())); + } + + static ComponentTreeDepsMetadata create( + ClassName name, + ImmutableSet<TypeElement> aggregatedRootDeps, + ImmutableSet<TypeElement> defineComponentDeps, + ImmutableSet<TypeElement> aliasOfDeps, + ImmutableSet<TypeElement> aggregatedDeps, + ImmutableSet<TypeElement> aggregatedUninstallModulesDeps, + ImmutableSet<TypeElement> aggregatedEarlyEntryPointDeps) { + return new AutoValue_ComponentTreeDepsMetadata( + name, + aggregatedRootDeps, + defineComponentDeps, + aliasOfDeps, + aggregatedDeps, + aggregatedUninstallModulesDeps, + aggregatedEarlyEntryPointDeps); + } +} diff --git a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessor.java b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessor.java new file mode 100644 index 000000000..85eb59a47 --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessor.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import static com.google.auto.common.MoreElements.asType; +import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static javax.lang.model.element.Modifier.PUBLIC; +import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointMetadata; +import dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator; +import dagger.hilt.processor.internal.BaseProcessor; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.ComponentDescriptor; +import dagger.hilt.processor.internal.ComponentNames; +import dagger.hilt.processor.internal.ProcessorErrors; +import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsMetadata; +import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies; +import dagger.hilt.processor.internal.aliasof.AliasOfPropagatedDataMetadata; +import dagger.hilt.processor.internal.aliasof.AliasOfs; +import dagger.hilt.processor.internal.definecomponent.DefineComponentClassesMetadata; +import dagger.hilt.processor.internal.definecomponent.DefineComponents; +import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata; +import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; + +/** Processor that outputs dagger components based on transitive build deps. */ +@IncrementalAnnotationProcessor(ISOLATING) +@AutoService(Processor.class) +public final class ComponentTreeDepsProcessor extends BaseProcessor { + private final Set<ClassName> componentTreeDepNames = new HashSet<>(); + private final Set<ClassName> processed = new HashSet<>(); + private final DefineComponents defineComponents = DefineComponents.create(); + + @Override + public ImmutableSet<String> getSupportedAnnotationTypes() { + return ImmutableSet.of(ClassNames.COMPONENT_TREE_DEPS.toString()); + } + + @Override + public void processEach(TypeElement annotation, Element element) { + componentTreeDepNames.add(ClassName.get(asType(element))); + } + + @Override + public void postRoundProcess(RoundEnvironment roundEnv) throws Exception { + ImmutableSet<ComponentTreeDepsMetadata> componentTreeDepsToProcess = + componentTreeDepNames.stream() + .filter(className -> !processed.contains(className)) + .map(className -> getElementUtils().getTypeElement(className.canonicalName())) + .map(element -> ComponentTreeDepsMetadata.from(element, getElementUtils())) + .collect(toImmutableSet()); + + for (ComponentTreeDepsMetadata metadata : componentTreeDepsToProcess) { + processComponentTreeDeps(metadata); + } + } + + private void processComponentTreeDeps(ComponentTreeDepsMetadata metadata) throws IOException { + TypeElement metadataElement = getElementUtils().getTypeElement(metadata.name().canonicalName()); + try { + // We choose a name for the generated components/wrapper based off of the originating element + // annotated with @ComponentTreeDeps. This is close to but isn't necessarily a "real" name of + // a root, since with shared test components, even for single roots, the component tree deps + // will be moved to a shared package with a deduped name. + ClassName renamedRoot = Processors.removeNameSuffix(metadataElement, "_ComponentTreeDeps"); + ComponentNames componentNames = ComponentNames.withRenaming(rootName -> renamedRoot); + + boolean isDefaultRoot = ClassNames.DEFAULT_ROOT.equals(renamedRoot); + ImmutableSet<Root> roots = + AggregatedRootMetadata.from(metadata.aggregatedRootDeps(), processingEnv).stream() + .map(AggregatedRootMetadata::rootElement) + .map(rootElement -> Root.create(rootElement, getProcessingEnv())) + .collect(toImmutableSet()); + + // TODO(bcorso): For legacy reasons, a lot of the generating code requires a "root" as input + // since we used to assume 1 root per component tree. Now that each ComponentTreeDeps may + // represent multiple roots, we should refactor this logic. + Root root = + isDefaultRoot + ? Root.createDefaultRoot(getProcessingEnv()) + // Non-default roots should only ever be associated with one root element + : getOnlyElement(roots); + + ImmutableSet<ComponentDescriptor> componentDescriptors = + defineComponents.getComponentDescriptors( + DefineComponentClassesMetadata.from( + metadata.defineComponentDeps(), getElementUtils())); + ComponentTree tree = ComponentTree.from(componentDescriptors); + ComponentDependencies deps = + ComponentDependencies.from( + componentDescriptors, + AggregatedDepsMetadata.from(metadata.aggregatedDeps(), getElementUtils()), + AggregatedUninstallModulesMetadata.from( + metadata.aggregatedUninstallModulesDeps(), getElementUtils()), + AggregatedEarlyEntryPointMetadata.from( + metadata.aggregatedEarlyEntryPointDeps(), getElementUtils()), + getElementUtils()); + AliasOfs aliasOfs = + AliasOfs.create( + AliasOfPropagatedDataMetadata.from(metadata.aliasOfDeps(), getElementUtils()), + componentDescriptors); + RootMetadata rootMetadata = + RootMetadata.create(root, tree, deps, aliasOfs, getProcessingEnv()); + + generateComponents(metadata, rootMetadata, componentNames); + + // Generate a creator for the early entry point if there is a default component available + // and there are early entry points. + if (isDefaultRoot && !metadata.aggregatedEarlyEntryPointDeps().isEmpty()) { + EarlySingletonComponentCreatorGenerator.generate(getProcessingEnv()); + } + + if (root.isTestRoot()) { + // Generate test related classes for each test root that uses this component. + ImmutableList<RootMetadata> rootMetadatas = + roots.stream() + .map(test -> RootMetadata.create(test, tree, deps, aliasOfs, getProcessingEnv())) + .collect(toImmutableList()); + generateTestComponentData(metadataElement, rootMetadatas, componentNames); + } else { + generateApplication(root.element()); + } + + setProcessingState(metadata, root); + } catch (Exception e) { + processed.add(metadata.name()); + throw e; + } + } + + private void setProcessingState(ComponentTreeDepsMetadata metadata, Root root) { + processed.add(metadata.name()); + } + + private void generateComponents( + ComponentTreeDepsMetadata metadata, RootMetadata rootMetadata, ComponentNames componentNames) + throws IOException { + RootGenerator.generate(metadata, rootMetadata, componentNames, getProcessingEnv()); + } + + private void generateTestComponentData( + TypeElement metadataElement, + ImmutableList<RootMetadata> rootMetadatas, + ComponentNames componentNames) + throws IOException { + for (RootMetadata rootMetadata : rootMetadatas) { + // TODO(bcorso): Consider moving this check earlier into processEach. + TypeElement testElement = rootMetadata.testRootMetadata().testElement(); + ProcessorErrors.checkState( + testElement.getModifiers().contains(PUBLIC), + testElement, + "Hilt tests must be public, but found: %s", + testElement); + new TestComponentDataGenerator( + getProcessingEnv(), metadataElement, rootMetadata, componentNames) + .generate(); + } + } + + private void generateApplication(TypeElement rootElement) throws IOException { + // The generated application references the generated component so they must be generated + // in the same build unit. Thus, we only generate the application here if we're using the + // Hilt Gradle plugin's aggregating task. If we're using the aggregating processor, we need + // to generate the application within AndroidEntryPointProcessor instead. + if (!useAggregatingRootProcessor(getProcessingEnv())) { + AndroidEntryPointMetadata metadata = + AndroidEntryPointMetadata.of(getProcessingEnv(), rootElement); + new ApplicationGenerator( + getProcessingEnv(), + metadata) + .generate(); + } + } +} diff --git a/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java b/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java index 1dbda3982..81a904a24 100644 --- a/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java +++ b/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java @@ -45,10 +45,12 @@ final class EarlySingletonComponentCreatorGenerator { .returns(ClassName.OBJECT) .addStatement( "return $T.builder()\n" - + ".applicationContextModule(new $T($T.getApplicationContext()))\n" + + ".applicationContextModule(\n" + + " new $T($T.getApplication($T.getApplicationContext())))\n" + ".build()", DEFAULT_COMPONENT_IMPL, ClassNames.APPLICATION_CONTEXT_MODULE, + ClassNames.CONTEXTS, ClassNames.APPLICATION_PROVIDER) .build()); diff --git a/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java index d60015495..a7070b848 100644 --- a/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java +++ b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java @@ -21,10 +21,13 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.AggregatedElements; import dagger.hilt.processor.internal.AnnotationValues; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.root.ir.ProcessedRootSentinelIr; +import java.util.stream.Collectors; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.TypeElement; @@ -37,6 +40,9 @@ import javax.lang.model.util.Elements; @AutoValue abstract class ProcessedRootSentinelMetadata { + /** Returns the aggregating element */ + public abstract TypeElement aggregatingElement(); + /** Returns the processed root elements. */ abstract ImmutableSet<TypeElement> rootElements(); @@ -50,6 +56,13 @@ abstract class ProcessedRootSentinelMetadata { .collect(toImmutableSet()); } + static ProcessedRootSentinelIr toIr(ProcessedRootSentinelMetadata metadata) { + return new ProcessedRootSentinelIr( + ClassName.get(metadata.aggregatingElement()), + metadata.rootElements().stream().map(ClassName::get).collect(Collectors.toList()) + ); + } + private static ProcessedRootSentinelMetadata create(TypeElement element, Elements elements) { AnnotationMirror annotationMirror = Processors.getAnnotationMirror(element, ClassNames.PROCESSED_ROOT_SENTINEL); @@ -58,6 +71,7 @@ abstract class ProcessedRootSentinelMetadata { Processors.getAnnotationValues(elements, annotationMirror); return new AutoValue_ProcessedRootSentinelMetadata( + element, AnnotationValues.getStrings(values.get("roots")).stream() .map(elements::getTypeElement) .collect(toImmutableSet())); diff --git a/java/dagger/hilt/processor/internal/root/Root.java b/java/dagger/hilt/processor/internal/root/Root.java index 24440b0c9..00e17ca4f 100644 --- a/java/dagger/hilt/processor/internal/root/Root.java +++ b/java/dagger/hilt/processor/internal/root/Root.java @@ -43,18 +43,27 @@ abstract class Root { static Root createDefaultRoot(ProcessingEnvironment env) { TypeElement rootElement = env.getElementUtils().getTypeElement(ClassNames.DEFAULT_ROOT.canonicalName()); - return new AutoValue_Root(rootElement, /*isTestRoot=*/ true); + return new AutoValue_Root(rootElement, rootElement, /*isTestRoot=*/ true); } /** Creates a {@plainlink Root root} for the given {@plainlink Element element}. */ static Root create(Element element, ProcessingEnvironment env) { TypeElement rootElement = MoreElements.asType(element); - return new AutoValue_Root(rootElement, RootType.of(rootElement).isTestRoot()); + if (ClassNames.DEFAULT_ROOT.equals(ClassName.get(rootElement))) { + return createDefaultRoot(env); + } + return new AutoValue_Root(rootElement, rootElement, RootType.of(rootElement).isTestRoot()); } /** Returns the root element that should be used with processing. */ abstract TypeElement element(); + /** + * Returns the originating root element. In most cases this will be the same as {@link + * #element()}. + */ + abstract TypeElement originatingRootElement(); + /** Returns {@code true} if this is a test root. */ abstract boolean isTestRoot(); @@ -63,9 +72,14 @@ abstract class Root { return ClassName.get(element()); } + /** Returns the class name of the originating root element. */ + ClassName originatingRootClassname() { + return ClassName.get(originatingRootElement()); + } + @Override public final String toString() { - return element().toString(); + return originatingRootElement().toString(); } /** Returns {@code true} if this uses the default root. */ diff --git a/java/dagger/hilt/processor/internal/root/RootGenerator.java b/java/dagger/hilt/processor/internal/root/RootGenerator.java index f87fbd90b..732c6ea5f 100644 --- a/java/dagger/hilt/processor/internal/root/RootGenerator.java +++ b/java/dagger/hilt/processor/internal/root/RootGenerator.java @@ -16,6 +16,7 @@ package dagger.hilt.processor.internal.root; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static dagger.hilt.processor.internal.Processors.toClassNames; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; @@ -44,20 +45,26 @@ import java.util.Map; import java.util.Optional; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; /** Generates components and any other classes needed for a root. */ final class RootGenerator { static void generate( - RootMetadata metadata, ComponentNames componentNames, ProcessingEnvironment env) + ComponentTreeDepsMetadata componentTreeDepsMetadata, + RootMetadata metadata, + ComponentNames componentNames, + ProcessingEnvironment env) throws IOException { new RootGenerator( + componentTreeDepsMetadata, RootMetadata.copyWithNewTree(metadata, filterDescriptors(metadata.componentTree())), componentNames, env) .generateComponents(); } + private final TypeElement originatingElement; private final RootMetadata metadata; private final ProcessingEnvironment env; private final Root root; @@ -66,7 +73,13 @@ final class RootGenerator { private final ComponentNames componentNames; private RootGenerator( - RootMetadata metadata, ComponentNames componentNames, ProcessingEnvironment env) { + ComponentTreeDepsMetadata componentTreeDepsMetadata, + RootMetadata metadata, + ComponentNames componentNames, + ProcessingEnvironment env) { + this.originatingElement = + checkNotNull( + env.getElementUtils().getTypeElement(componentTreeDepsMetadata.name().toString())); this.metadata = metadata; this.componentNames = componentNames; this.env = env; @@ -165,7 +178,7 @@ final class RootGenerator { ClassName componentName, ClassName builderName, ClassName moduleName) { TypeSpec.Builder subcomponentBuilderModule = TypeSpec.interfaceBuilder(moduleName) - .addOriginatingElement(root.element()) + .addOriginatingElement(originatingElement) .addModifiers(ABSTRACT) .addAnnotation( AnnotationSpec.builder(ClassNames.MODULE) @@ -192,7 +205,7 @@ final class RootGenerator { .map( creator -> TypeSpec.interfaceBuilder("Builder") - .addOriginatingElement(root.element()) + .addOriginatingElement(originatingElement) .addModifiers(STATIC, ABSTRACT) .addSuperinterface(creator) .addAnnotation(componentBuilderAnnotation(descriptor)) @@ -221,7 +234,7 @@ final class RootGenerator { } private ClassName getComponentsWrapperClassName() { - return componentNames.generatedComponentsWrapper(root.classname()); + return componentNames.generatedComponentsWrapper(root.originatingRootClassname()); } private ClassName getComponentClassName(ComponentDescriptor componentDescriptor) { @@ -240,7 +253,8 @@ final class RootGenerator { componentDescriptor.component()); ClassName generatedComponent = - componentNames.generatedComponent(root.classname(), componentDescriptor.component()); + componentNames.generatedComponent( + root.originatingRootClassname(), componentDescriptor.component()); Integer suffix = simpleComponentNamesToDedupeSuffix.get(generatedComponent.simpleName()); if (suffix != null) { diff --git a/java/dagger/hilt/processor/internal/root/RootMetadata.java b/java/dagger/hilt/processor/internal/root/RootMetadata.java index b39b590ef..5b390031a 100644 --- a/java/dagger/hilt/processor/internal/root/RootMetadata.java +++ b/java/dagger/hilt/processor/internal/root/RootMetadata.java @@ -18,14 +18,12 @@ package dagger.hilt.processor.internal.root; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Suppliers.memoize; -import static dagger.hilt.processor.internal.HiltCompilerOptions.isSharedTestComponentsEnabled; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.STATIC; import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.squareup.javapoet.ClassName; @@ -54,33 +52,22 @@ public final class RootMetadata { Root root, ComponentTree componentTree, ComponentDependencies deps, + AliasOfs aliasOfs, ProcessingEnvironment env) { - return createInternal(root, ImmutableList.of(), componentTree, deps, env); - } - - static RootMetadata createForDefaultRoot( - Root root, - ImmutableList<RootMetadata> rootsUsingDefaultComponents, - ComponentTree componentTree, - ComponentDependencies deps, - ProcessingEnvironment env) { - checkState(root.isDefaultRoot()); - return createInternal(root, rootsUsingDefaultComponents, componentTree, deps, env); + return createInternal(root, componentTree, deps, aliasOfs, env); } static RootMetadata copyWithNewTree(RootMetadata other, ComponentTree componentTree) { - return createInternal( - other.root, other.rootsUsingDefaultComponents, componentTree, other.deps, other.env); + return createInternal(other.root, componentTree, other.deps, other.aliasOfs, other.env); } private static RootMetadata createInternal( Root root, - ImmutableList<RootMetadata> rootsUsingDefaultComponents, ComponentTree componentTree, ComponentDependencies deps, + AliasOfs aliasOfs, ProcessingEnvironment env) { - RootMetadata metadata = - new RootMetadata(root, componentTree, deps, rootsUsingDefaultComponents, env); + RootMetadata metadata = new RootMetadata(root, componentTree, deps, aliasOfs, env); metadata.validate(); return metadata; } @@ -90,7 +77,7 @@ public final class RootMetadata { private final Elements elements; private final ComponentTree componentTree; private final ComponentDependencies deps; - private final ImmutableList<RootMetadata> rootsUsingDefaultComponents; + private final AliasOfs aliasOfs; private final Supplier<ImmutableSetMultimap<ClassName, ClassName>> scopesByComponent = memoize(this::getScopesByComponentUncached); private final Supplier<TestRootMetadata> testRootMetadata = @@ -100,14 +87,14 @@ public final class RootMetadata { Root root, ComponentTree componentTree, ComponentDependencies deps, - ImmutableList<RootMetadata> rootsUsingDefaultComponents, + AliasOfs aliasOfs, ProcessingEnvironment env) { this.root = root; this.env = env; this.elements = env.getElementUtils(); this.componentTree = componentTree; this.deps = deps; - this.rootsUsingDefaultComponents = rootsUsingDefaultComponents; + this.aliasOfs = aliasOfs; } public Root root() { @@ -123,24 +110,15 @@ public final class RootMetadata { } public ImmutableSet<TypeElement> modules(ClassName componentName) { - return deps.modules().get(componentName, root.classname(), root.isTestRoot()); - } - - /** - * Returns {@code true} if this is a test root that provides no test-specific dependencies or sets - * other options that would prevent it from sharing components with other test roots. - */ - // TODO(groakley): Allow more tests to share modules, e.g. tests that uninstall the same module. - // In that case, this might instead return which shared dep grouping should be used. - public boolean canShareTestComponents() { - return isSharedTestComponentsEnabled(env) - && root.isTestRoot() - && !deps.includesTestDeps(root.classname()); + return deps.modules().get(componentName); } public ImmutableSet<TypeName> entryPoints(ClassName componentName) { return ImmutableSet.<TypeName>builder() - .addAll(getUserDefinedEntryPoints(componentName)) + .addAll( + deps.entryPoints().get(componentName).stream() + .map(ClassName::get) + .collect(toImmutableSet())) .add( root.isTestRoot() && componentName.equals(ClassNames.SINGLETON_COMPONENT) ? ClassNames.TEST_SINGLETON_COMPONENT @@ -197,7 +175,7 @@ public final class RootMetadata { "[Hilt] All test modules (unless installed in ApplicationComponent) must use " + "static provision methods or have a visible, no-arg constructor. Found: " + extraModule.getQualifiedName(), - root.element()); + root.originatingRootElement()); } else if (!root.isTestRoot()) { env.getMessager() .printMessage( @@ -205,41 +183,14 @@ public final class RootMetadata { "[Hilt] All modules must be static and use static provision methods or have a " + "visible, no-arg constructor. Found: " + extraModule.getQualifiedName(), - root.element()); + root.originatingRootElement()); } } } } - private ImmutableSet<TypeName> getUserDefinedEntryPoints(ClassName componentName) { - ImmutableSet.Builder<TypeName> entryPointSet = ImmutableSet.builder(); - if (root.isDefaultRoot() && !rootsUsingDefaultComponents.isEmpty()) { - // Add entry points for shared component - rootsUsingDefaultComponents.stream() - .flatMap(metadata -> metadata.entryPoints(componentName).stream()) - .forEach(entryPointSet::add); - } else if (root.isDefaultRoot() && componentName.equals(ClassNames.SINGLETON_COMPONENT)) { - // We only do this for SingletonComponent because EarlyEntryPoints can only be installed - // in the SingletonComponent. - deps.earlyEntryPoints().forEach(entryPointSet::add); - } else { - deps.entryPoints().get(componentName, root.classname(), root.isTestRoot()).stream() - .map(ClassName::get) - .forEach(entryPointSet::add); - } - return entryPointSet.build(); - } - private ImmutableSetMultimap<ClassName, ClassName> getScopesByComponentUncached() { ImmutableSetMultimap.Builder<ClassName, ClassName> builder = ImmutableSetMultimap.builder(); - - ImmutableSet<ClassName> defineComponentScopes = - componentTree.getComponentDescriptors().stream() - .flatMap(descriptor -> descriptor.scopes().stream()) - .collect(toImmutableSet()); - - AliasOfs aliasOfs = AliasOfs.create(env.getElementUtils(), defineComponentScopes); - for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) { for (ClassName scope : componentDescriptor.scopes()) { builder.put(componentDescriptor.component(), scope); diff --git a/java/dagger/hilt/processor/internal/root/RootProcessor.java b/java/dagger/hilt/processor/internal/root/RootProcessor.java index 1ee4446d4..3dd35f581 100644 --- a/java/dagger/hilt/processor/internal/root/RootProcessor.java +++ b/java/dagger/hilt/processor/internal/root/RootProcessor.java @@ -19,29 +19,35 @@ package dagger.hilt.processor.internal.root; import static com.google.common.base.Preconditions.checkState; import static dagger.hilt.processor.internal.HiltCompilerOptions.isCrossCompilationRootValidationDisabled; import static dagger.hilt.processor.internal.HiltCompilerOptions.isSharedTestComponentsEnabled; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import static java.util.Comparator.comparing; -import static javax.lang.model.element.Modifier.PUBLIC; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.AGGREGATING; +import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.DYNAMIC; +import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; import com.google.auto.common.MoreElements; import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.squareup.javapoet.ClassName; +import dagger.hilt.processor.internal.BadInputException; import dagger.hilt.processor.internal.BaseProcessor; -import dagger.hilt.processor.internal.ClassNames; -import dagger.hilt.processor.internal.ComponentDescriptor; -import dagger.hilt.processor.internal.ComponentNames; -import dagger.hilt.processor.internal.ProcessorErrors; -import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies; -import dagger.hilt.processor.internal.definecomponent.DefineComponents; +import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsMetadata; +import dagger.hilt.processor.internal.aliasof.AliasOfPropagatedDataMetadata; +import dagger.hilt.processor.internal.definecomponent.DefineComponentClassesMetadata; +import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata; import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputs; -import java.io.IOException; +import dagger.hilt.processor.internal.root.ir.AggregatedDepsIr; +import dagger.hilt.processor.internal.root.ir.AggregatedEarlyEntryPointIr; +import dagger.hilt.processor.internal.root.ir.AggregatedRootIr; +import dagger.hilt.processor.internal.root.ir.AggregatedRootIrValidator; +import dagger.hilt.processor.internal.root.ir.AggregatedUninstallModulesIr; +import dagger.hilt.processor.internal.root.ir.AliasOfPropagatedDataIr; +import dagger.hilt.processor.internal.root.ir.ComponentTreeDepsIr; +import dagger.hilt.processor.internal.root.ir.ComponentTreeDepsIrCreator; +import dagger.hilt.processor.internal.root.ir.DefineComponentClassesIr; +import dagger.hilt.processor.internal.root.ir.InvalidRootsException; +import dagger.hilt.processor.internal.root.ir.ProcessedRootSentinelIr; +import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata; import java.util.Arrays; -import java.util.Comparator; -import java.util.HashSet; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; @@ -51,15 +57,11 @@ import javax.lang.model.element.TypeElement; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; /** Processor that outputs dagger components based on transitive build deps. */ -@IncrementalAnnotationProcessor(AGGREGATING) +@IncrementalAnnotationProcessor(DYNAMIC) @AutoService(Processor.class) public final class RootProcessor extends BaseProcessor { - private static final Comparator<TypeElement> QUALIFIED_NAME_COMPARATOR = - comparing(TypeElement::getQualifiedName, (n1, n2) -> n1.toString().compareTo(n2.toString())); - private final Set<ClassName> processed = new HashSet<>(); - // TODO(bcorso): Consider using a Dagger component to create/scope these objects - private final DefineComponents defineComponents = DefineComponents.create(); + private boolean processed; private GeneratesRootInputs generatesRootInputs; @Override @@ -69,6 +71,13 @@ public final class RootProcessor extends BaseProcessor { } @Override + public ImmutableSet<String> additionalProcessingOptions() { + return useAggregatingRootProcessor(getProcessingEnv()) + ? ImmutableSet.of(AGGREGATING.getProcessorOption()) + : ImmutableSet.of(ISOLATING.getProcessorOption()); + } + + @Override public ImmutableSet<String> getSupportedAnnotationTypes() { return ImmutableSet.<String>builder() .addAll( @@ -81,224 +90,118 @@ public final class RootProcessor extends BaseProcessor { @Override public void processEach(TypeElement annotation, Element element) throws Exception { TypeElement rootElement = MoreElements.asType(element); + // TODO(bcorso): Move this logic into a separate isolating processor to avoid regenerating it + // for unrelated changes in Gradle. RootType rootType = RootType.of(rootElement); if (rootType.isTestRoot()) { new TestInjectorGenerator( getProcessingEnv(), TestRootMetadata.of(getProcessingEnv(), rootElement)) .generate(); } - new AggregatedRootGenerator(rootElement, annotation, getProcessingEnv()).generate(); + TypeElement originatingRootElement = + Root.create(rootElement, getProcessingEnv()).originatingRootElement(); + new AggregatedRootGenerator(rootElement, originatingRootElement, annotation, getProcessingEnv()) + .generate(); } @Override public void postRoundProcess(RoundEnvironment roundEnv) throws Exception { + if (!useAggregatingRootProcessor(getProcessingEnv())) { + return; + } Set<Element> newElements = generatesRootInputs.getElementsToWaitFor(roundEnv); - if (!processed.isEmpty() ) { + if (processed) { checkState( newElements.isEmpty(), "Found extra modules after compilation: %s\n" + "(If you are adding an annotation processor that generates root input for hilt, " + "the annotation must be annotated with @dagger.hilt.GeneratesRootInput.\n)", newElements); - } + } else if (newElements.isEmpty()) { + processed = true; - if (!newElements.isEmpty()) { - // Skip further processing since there's new elements that generate root inputs in this round. - return; - } - - ImmutableSet<Root> allRoots = - AggregatedRootMetadata.from(getElementUtils()).stream() - .map(metadata -> Root.create(metadata.rootElement(), getProcessingEnv())) - .collect(toImmutableSet()); - - ImmutableSet<Root> processedRoots = - ProcessedRootSentinelMetadata.from(getElementUtils()).stream() - .flatMap(metadata -> metadata.rootElements().stream()) - .map(rootElement -> Root.create(rootElement, getProcessingEnv())) - .collect(toImmutableSet()); - - ImmutableSet<Root> rootsToProcess = - allRoots.stream() - .filter(root -> !processedRoots.contains(root)) - .filter(root -> !processed.contains(rootName(root))) - .collect(toImmutableSet()); - - if (rootsToProcess.isEmpty()) { - // Skip further processing since there's no roots that need processing. - return; - } - - // TODO(bcorso): Currently, if there's an exception in any of the roots we stop processing - // all roots. We should consider if it's worth trying to continue processing for other - // roots. At the moment, I think it's rare that if one root failed the others would not. - try { - validateRoots(allRoots, rootsToProcess); - - boolean isTestEnv = rootsToProcess.stream().anyMatch(Root::isTestRoot); - ComponentNames componentNames = - isTestEnv && isSharedTestComponentsEnabled(getProcessingEnv()) - ? ComponentNames.withRenamingIntoPackage( - ClassNames.DEFAULT_ROOT.packageName(), - rootsToProcess.stream().map(Root::element).collect(toImmutableList())) - : ComponentNames.withoutRenaming(); - - ImmutableSet<ComponentDescriptor> componentDescriptors = - defineComponents.getComponentDescriptors(getElementUtils()); - ComponentTree tree = ComponentTree.from(componentDescriptors); - ComponentDependencies deps = - ComponentDependencies.from(componentDescriptors, getElementUtils()); - ImmutableList<RootMetadata> rootMetadatas = - rootsToProcess.stream() - .map(root -> RootMetadata.create(root, tree, deps, getProcessingEnv())) - .collect(toImmutableList()); - - for (RootMetadata rootMetadata : rootMetadatas) { - if (!rootMetadata.canShareTestComponents()) { - generateComponents(rootMetadata, componentNames); - } + ImmutableSet<AggregatedRootIr> rootsToProcess = rootsToProcess(); + if (rootsToProcess.isEmpty()) { + return; } - if (isTestEnv) { - ImmutableList<RootMetadata> rootsThatCanShareComponents = - rootMetadatas.stream() - .filter(RootMetadata::canShareTestComponents) - .collect(toImmutableList()); - generateTestComponentData(rootMetadatas, componentNames); - if (deps.hasEarlyEntryPoints() || !rootsThatCanShareComponents.isEmpty()) { - Root defaultRoot = Root.createDefaultRoot(getProcessingEnv()); - generateComponents( - RootMetadata.createForDefaultRoot( - defaultRoot, rootsThatCanShareComponents, tree, deps, getProcessingEnv()), - componentNames); - EarlySingletonComponentCreatorGenerator.generate(getProcessingEnv()); - } - } - } catch (Exception e) { - for (Root root : rootsToProcess) { - processed.add(rootName(root)); + // Generate an @ComponentTreeDeps for each unique component tree. + ComponentTreeDepsGenerator componentTreeDepsGenerator = + new ComponentTreeDepsGenerator(getProcessingEnv()); + for (ComponentTreeDepsMetadata metadata : componentTreeDepsMetadatas(rootsToProcess)) { + componentTreeDepsGenerator.generate(metadata); } - throw e; - } finally { - rootsToProcess.forEach(this::setProcessingState); - // Calculate the roots processed in this round. We do this in the finally-block rather than in - // the try-block because the catch-block can change the processing state. - ImmutableSet<Root> rootsProcessedInRound = - rootsToProcess.stream() - // Only add a sentinel for processed roots. Skip preprocessed roots since those will - // will be processed in the next round. - .filter(root -> processed.contains(rootName(root))) - .collect(toImmutableSet()); - for (Root root : rootsProcessedInRound) { - new ProcessedRootSentinelGenerator(rootElement(root), getProcessingEnv()).generate(); + + // Generate a sentinel for all processed roots. + for (AggregatedRootIr ir : rootsToProcess) { + TypeElement rootElement = getElementUtils().getTypeElement(ir.getRoot().canonicalName()); + new ProcessedRootSentinelGenerator(rootElement, getProcessingEnv()).generate(); } } } - private void validateRoots(ImmutableSet<Root> allRoots, ImmutableSet<Root> rootsToProcess) { - - ImmutableSet<TypeElement> rootElementsToProcess = - rootsToProcess.stream() - .map(Root::element) - .sorted(QUALIFIED_NAME_COMPARATOR) + private ImmutableSet<AggregatedRootIr> rootsToProcess() { + ImmutableSet<ProcessedRootSentinelIr> processedRoots = + ProcessedRootSentinelMetadata.from(getElementUtils()).stream() + .map(ProcessedRootSentinelMetadata::toIr) .collect(toImmutableSet()); - - ImmutableSet<TypeElement> appRootElementsToProcess = - rootsToProcess.stream() - .filter(root -> !root.isTestRoot()) - .map(Root::element) - .sorted(QUALIFIED_NAME_COMPARATOR) + ImmutableSet<AggregatedRootIr> aggregatedRoots = + AggregatedRootMetadata.from(processingEnv).stream() + .map(AggregatedRootMetadata::toIr) .collect(toImmutableSet()); - // Perform validation between roots in this compilation unit. - if (!appRootElementsToProcess.isEmpty()) { - ImmutableSet<TypeElement> testRootElementsToProcess = - rootsToProcess.stream() - .filter(Root::isTestRoot) - .map(Root::element) - .sorted(QUALIFIED_NAME_COMPARATOR) - .collect(toImmutableSet()); - - ProcessorErrors.checkState( - testRootElementsToProcess.isEmpty(), - "Cannot process test roots and app roots in the same compilation unit:" - + "\n\tApp root in this compilation unit: %s" - + "\n\tTest roots in this compilation unit: %s", - appRootElementsToProcess, - testRootElementsToProcess); - - ProcessorErrors.checkState( - appRootElementsToProcess.size() == 1, - "Cannot process multiple app roots in the same compilation unit: %s", - appRootElementsToProcess); - } - - // Perform validation across roots previous compilation units. - if (!isCrossCompilationRootValidationDisabled(rootElementsToProcess, getProcessingEnv())) { - ImmutableSet<TypeElement> processedTestRootElements = - allRoots.stream() - .filter(Root::isTestRoot) - .filter(root -> !rootsToProcess.contains(root)) - .map(Root::element) - .sorted(QUALIFIED_NAME_COMPARATOR) - .collect(toImmutableSet()); - - // TODO(b/185742783): Add an explanation or link to docs to explain why we're forbidding this. - ProcessorErrors.checkState( - processedTestRootElements.isEmpty(), - "Cannot process new roots when there are test roots from a previous compilation unit:" - + "\n\tTest roots from previous compilation unit: %s" - + "\n\tAll roots from this compilation unit: %s", - processedTestRootElements, - rootElementsToProcess); - - ImmutableSet<TypeElement> processedAppRootElements = - allRoots.stream() - .filter(root -> !root.isTestRoot()) - .filter(root -> !rootsToProcess.contains(root)) - .map(Root::element) - .sorted(QUALIFIED_NAME_COMPARATOR) - .collect(toImmutableSet()); - - ProcessorErrors.checkState( - processedAppRootElements.isEmpty() || appRootElementsToProcess.isEmpty(), - "Cannot process app roots in this compilation unit since there are app roots in a " - + "previous compilation unit:" - + "\n\tApp roots in previous compilation unit: %s" - + "\n\tApp roots in this compilation unit: %s", - processedAppRootElements, - appRootElementsToProcess); + boolean isCrossCompilationRootValidationDisabled = + isCrossCompilationRootValidationDisabled( + aggregatedRoots.stream() + .map(ir -> getElementUtils().getTypeElement(ir.getRoot().canonicalName())) + .collect(toImmutableSet()), + processingEnv); + try { + return ImmutableSet.copyOf( + AggregatedRootIrValidator.rootsToProcess( + isCrossCompilationRootValidationDisabled, processedRoots, aggregatedRoots)); + } catch (InvalidRootsException ex) { + throw new BadInputException(ex.getMessage()); } } - private void setProcessingState(Root root) { - processed.add(rootName(root)); - } - - private ClassName rootName(Root root) { - return ClassName.get(rootElement(root)); - } - - private TypeElement rootElement(Root root) { - return root.element(); - } - - private void generateComponents(RootMetadata rootMetadata, ComponentNames componentNames) - throws IOException { - RootGenerator.generate(rootMetadata, componentNames, getProcessingEnv()); - } + private ImmutableSet<ComponentTreeDepsMetadata> componentTreeDepsMetadatas( + ImmutableSet<AggregatedRootIr> aggregatedRoots) { + ImmutableSet<DefineComponentClassesIr> defineComponentDeps = + DefineComponentClassesMetadata.from(getElementUtils()).stream() + .map(DefineComponentClassesMetadata::toIr) + .collect(toImmutableSet()); + ImmutableSet<AliasOfPropagatedDataIr> aliasOfDeps = + AliasOfPropagatedDataMetadata.from(getElementUtils()).stream() + .map(AliasOfPropagatedDataMetadata::toIr) + .collect(toImmutableSet()); + ImmutableSet<AggregatedDepsIr> aggregatedDeps = + AggregatedDepsMetadata.from(getElementUtils()).stream() + .map(AggregatedDepsMetadata::toIr) + .collect(toImmutableSet()); + ImmutableSet<AggregatedUninstallModulesIr> aggregatedUninstallModulesDeps = + AggregatedUninstallModulesMetadata.from(getElementUtils()).stream() + .map(AggregatedUninstallModulesMetadata::toIr) + .collect(toImmutableSet()); + ImmutableSet<AggregatedEarlyEntryPointIr> aggregatedEarlyEntryPointDeps = + AggregatedEarlyEntryPointMetadata.from(getElementUtils()).stream() + .map(AggregatedEarlyEntryPointMetadata::toIr) + .collect(toImmutableSet()); - private void generateTestComponentData( - ImmutableList<RootMetadata> rootMetadatas, ComponentNames componentNames) throws IOException { - for (RootMetadata rootMetadata : rootMetadatas) { - // TODO(bcorso): Consider moving this check earlier into processEach. - TypeElement testElement = rootMetadata.testRootMetadata().testElement(); - ProcessorErrors.checkState( - testElement.getModifiers().contains(PUBLIC), - testElement, - "Hilt tests must be public, but found: %s", - testElement); - new TestComponentDataGenerator(getProcessingEnv(), rootMetadata, componentNames).generate(); - } + // We should be guaranteed that there are no mixed roots, so check if this is prod or test. + boolean isTest = aggregatedRoots.stream().anyMatch(AggregatedRootIr::isTestRoot); + Set<ComponentTreeDepsIr> componentTreeDeps = + ComponentTreeDepsIrCreator.components( + isTest, + isSharedTestComponentsEnabled(processingEnv), + aggregatedRoots, + defineComponentDeps, + aliasOfDeps, + aggregatedDeps, + aggregatedUninstallModulesDeps, + aggregatedEarlyEntryPointDeps); + return componentTreeDeps.stream() + .map(it -> ComponentTreeDepsMetadata.from(it, getElementUtils())) + .collect(toImmutableSet()); } } diff --git a/java/dagger/hilt/processor/internal/root/RootType.java b/java/dagger/hilt/processor/internal/root/RootType.java index 807f71d73..a6efc84fd 100644 --- a/java/dagger/hilt/processor/internal/root/RootType.java +++ b/java/dagger/hilt/processor/internal/root/RootType.java @@ -23,13 +23,13 @@ import javax.lang.model.element.TypeElement; /** The valid root types for Hilt applications. */ // TODO(erichang): Fix this class so we don't have to have placeholders - enum RootType { - ROOT(ClassNames.HILT_ANDROID_APP), +enum RootType { + ROOT(ClassNames.HILT_ANDROID_APP), - // Placeholder to make sure @HiltAndroidTest usages get processed - HILT_ANDROID_TEST_ROOT(ClassNames.HILT_ANDROID_TEST), + // Placeholder to make sure @HiltAndroidTest usages get processed + HILT_ANDROID_TEST_ROOT(ClassNames.HILT_ANDROID_TEST), - TEST_ROOT(ClassNames.INTERNAL_TEST_ROOT); + TEST_ROOT(ClassNames.INTERNAL_TEST_ROOT); @SuppressWarnings("ImmutableEnumChecker") private final ClassName annotation; diff --git a/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java b/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java index 101c124d9..19145f45d 100644 --- a/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java +++ b/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java @@ -44,15 +44,18 @@ import javax.lang.model.element.TypeElement; /** Generates an implementation of {@link dagger.hilt.android.internal.TestComponentData}. */ public final class TestComponentDataGenerator { private final ProcessingEnvironment processingEnv; + private final TypeElement originatingElement; private final RootMetadata rootMetadata; private final ClassName name; private final ComponentNames componentNames; public TestComponentDataGenerator( ProcessingEnvironment processingEnv, + TypeElement originatingElement, RootMetadata rootMetadata, ComponentNames componentNames) { this.processingEnv = processingEnv; + this.originatingElement = originatingElement; this.rootMetadata = rootMetadata; this.componentNames = componentNames; this.name = @@ -75,7 +78,8 @@ public final class TestComponentDataGenerator { * modules -> * DaggerFooTest_ApplicationComponent.builder() * .applicationContextModule( - * new ApplicationContextModule(ApplicationProvider.getApplicationContext())) + * new ApplicationContextModule( + * Contexts.getApplication(ApplicationProvider.getApplicationContext()))) * .testModule((FooTest.TestModule) modules.get(FooTest.TestModule.class)) * .testModule(modules.containsKey(FooTest.TestModule.class) * ? (FooTest.TestModule) modules.get(FooTest.TestModule.class) @@ -88,6 +92,7 @@ public final class TestComponentDataGenerator { public void generate() throws IOException { TypeSpec.Builder generator = TypeSpec.classBuilder(name) + .addOriginatingElement(originatingElement) .superclass(ClassNames.TEST_COMPONENT_DATA_SUPPLIER) .addModifiers(PUBLIC, FINAL) .addMethod(getMethod()) @@ -105,10 +110,7 @@ public final class TestComponentDataGenerator { TypeElement testElement = rootMetadata.testRootMetadata().testElement(); ClassName component = componentNames.generatedComponent( - rootMetadata.canShareTestComponents() - ? ClassNames.DEFAULT_ROOT - : ClassName.get(testElement), - ClassNames.SINGLETON_COMPONENT); + ClassName.get(testElement), ClassNames.SINGLETON_COMPONENT); ImmutableSet<TypeElement> daggerRequiredModules = rootMetadata.modulesThatDaggerCannotConstruct(ClassNames.SINGLETON_COMPONENT); ImmutableSet<TypeElement> hiltRequiredModules = @@ -128,11 +130,13 @@ public final class TestComponentDataGenerator { getElementsListed(hiltRequiredModules), CodeBlock.of( "(modules, testInstance, autoAddModuleEnabled) -> $T.builder()\n" - + ".applicationContextModule(new $T($T.getApplicationContext()))\n" + + ".applicationContextModule(\n" + + " new $T($T.getApplication($T.getApplicationContext())))\n" + "$L" + ".build()", Processors.prepend(Processors.getEnclosedClassName(component), "Dagger"), ClassNames.APPLICATION_CONTEXT_MODULE, + ClassNames.CONTEXTS, ClassNames.APPLICATION_PROVIDER, daggerRequiredModules.stream() .map(module -> getAddModuleStatement(module, testElement)) @@ -216,9 +220,10 @@ public final class TestComponentDataGenerator { private CodeBlock callInjectTest(TypeElement testElement) { return CodeBlock.of( - "(($T) (($T) $T.getApplicationContext()).generatedComponent()).injectTest(testInstance)", + "(($T) (($T) $T.getApplication($T.getApplicationContext())).generatedComponent()).injectTest(testInstance)", rootMetadata.testRootMetadata().testInjectorName(), ClassNames.GENERATED_COMPONENT_MANAGER, + ClassNames.CONTEXTS, ClassNames.APPLICATION_PROVIDER); } } diff --git a/java/dagger/hilt/processor/internal/root/ir/AggregatedRootIrValidator.kt b/java/dagger/hilt/processor/internal/root/ir/AggregatedRootIrValidator.kt new file mode 100644 index 000000000..b24cb14ee --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/ir/AggregatedRootIrValidator.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root.ir + +import kotlin.jvm.Throws + +// Validates roots being processed. +object AggregatedRootIrValidator { + @JvmStatic + @Throws(InvalidRootsException::class) + fun rootsToProcess( + isCrossCompilationRootValidationDisabled: Boolean, + processedRoots: Set<ProcessedRootSentinelIr>, + aggregatedRoots: Set<AggregatedRootIr> + ): Set<AggregatedRootIr> { + val processedRootNames = processedRoots.flatMap { it.roots }.toSet() + val rootsToProcess = aggregatedRoots + .filterNot { processedRootNames.contains(it.root) } + .sortedBy { it.root.toString() } + val testRootsToProcess = rootsToProcess.filter { it.isTestRoot } + val appRootsToProcess = rootsToProcess - testRootsToProcess + fun Collection<AggregatedRootIr>.rootsToString() = map { it.root }.joinToString() + if (appRootsToProcess.size > 1) { + throw InvalidRootsException( + "Cannot process multiple app roots in the same compilation unit: " + + appRootsToProcess.rootsToString() + ) + } + if (testRootsToProcess.isNotEmpty() && appRootsToProcess.isNotEmpty()) { + throw InvalidRootsException(""" + Cannot process test roots and app roots in the same compilation unit: + App root in this compilation unit: ${appRootsToProcess.rootsToString()} + Test roots in this compilation unit: ${testRootsToProcess.rootsToString()} + """.trimIndent() + ) + } + // Perform validation across roots previous compilation units. + if (!isCrossCompilationRootValidationDisabled) { + val alreadyProcessedTestRoots = aggregatedRoots.filter { + it.isTestRoot && processedRootNames.contains(it.root) + } + // TODO(b/185742783): Add an explanation or link to docs to explain why we're forbidding this. + if (alreadyProcessedTestRoots.isNotEmpty() && rootsToProcess.isNotEmpty()) { + throw InvalidRootsException(""" + Cannot process new roots when there are test roots from a previous compilation unit: + Test roots from previous compilation unit: ${alreadyProcessedTestRoots.rootsToString()} + All roots from this compilation unit: ${rootsToProcess.rootsToString()} + """.trimIndent() + ) + } + val alreadyProcessedAppRoots = aggregatedRoots.filter { + !it.isTestRoot && processedRootNames.contains(it.root) + } + if (alreadyProcessedAppRoots.isNotEmpty() && appRootsToProcess.isNotEmpty()) { + throw InvalidRootsException(""" + Cannot process new app roots when there are app roots from a previous compilation unit: + App roots in previous compilation unit: ${alreadyProcessedAppRoots.rootsToString()} + App roots in this compilation unit: ${appRootsToProcess.rootsToString()} + """.trimIndent() + ) + } + } + return rootsToProcess.toSet() + } +} + +// An exception thrown when root validation fails. +class InvalidRootsException(msg: String) : Exception(msg) diff --git a/java/dagger/hilt/processor/internal/root/ir/BUILD b/java/dagger/hilt/processor/internal/root/ir/BUILD new file mode 100644 index 000000000..4bc1a29a2 --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/ir/BUILD @@ -0,0 +1,52 @@ +# Copyright (C) 2021 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. + +# Description: +# A library containing intermediate representations of the various Hilt +# aggregating annotations along with logic to process them. + +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") + +package(default_visibility = ["//:src"]) + +kt_jvm_library( + name = "ir", + srcs = glob(["*.kt"]), + # Dependencies here should be kept to a minimum since this library is + # shadowed into the Hilt Gradle Plugin artifact. + deps = [ + "//third_party/java/javapoet", + ], +) + +# Current `kt_jvm_library` does not output source jars and gen_maven_artifact expects one. +# See: https://github.com/bazelbuild/rules_kotlin/issues/324 +genrule( + name = "ir-sources", + srcs = glob(["*.kt"]), + outs = ["libir-src.jar"], + cmd = """ + TEMP="$$(mktemp -d)" + for file in $(SRCS); do + filename="$$TEMP/$${file#java/}" + mkdir -p `dirname $$filename` && cp $$file $$filename + done + jar cf $@ -C $$TEMP . + """, +) + +filegroup( + name = "srcs_filegroup", + srcs = glob(["*"]), +) diff --git a/java/dagger/hilt/processor/internal/root/ir/ComponentTreeDepsIrCreator.kt b/java/dagger/hilt/processor/internal/root/ir/ComponentTreeDepsIrCreator.kt new file mode 100644 index 000000000..4c7b94df2 --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/ir/ComponentTreeDepsIrCreator.kt @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root.ir + +import com.squareup.javapoet.ClassName + +// Produces ComponentTreeDepsIr for a set of aggregated deps and roots to process. +class ComponentTreeDepsIrCreator private constructor( + private val isSharedTestComponentsEnabled: Boolean, + private val aggregatedRoots: Set<AggregatedRootIr>, + private val defineComponentDeps: Set<DefineComponentClassesIr>, + private val aliasOfDeps: Set<AliasOfPropagatedDataIr>, + private val aggregatedDeps: Set<AggregatedDepsIr>, + private val aggregatedUninstallModulesDeps: Set<AggregatedUninstallModulesIr>, + private val aggregatedEarlyEntryPointDeps: Set<AggregatedEarlyEntryPointIr>, +) { + private fun prodComponents(): Set<ComponentTreeDepsIr> { + // There should only be one prod root in a given build. + val aggregatedRoot = aggregatedRoots.single() + return setOf( + ComponentTreeDepsIr( + name = ComponentTreeDepsNameGenerator().generate(aggregatedRoot.root), + rootDeps = setOf(aggregatedRoot.fqName), + defineComponentDeps = defineComponentDeps.map { it.fqName }.toSet(), + aliasOfDeps = aliasOfDeps.map { it.fqName }.toSet(), + aggregatedDeps = + // @AggregatedDeps with non-empty replaces are from @TestInstallIn and should not be + // installed in production components + aggregatedDeps.filter { it.replaces.isEmpty() }.map { it.fqName }.toSet(), + uninstallModulesDeps = emptySet(), + earlyEntryPointDeps = emptySet(), + ) + ) + } + + private fun testComponents(): Set<ComponentTreeDepsIr> { + val rootsUsingSharedComponent = rootsUsingSharedComponent(aggregatedRoots) + val aggregatedRootsByRoot = aggregatedRoots.associateBy { it.root } + val aggregatedDepsByRoot = aggregatedDepsByRoot( + aggregatedRoots = aggregatedRoots, + rootsUsingSharedComponent = rootsUsingSharedComponent, + hasEarlyEntryPoints = aggregatedEarlyEntryPointDeps.isNotEmpty() + ) + val uninstallModuleDepsByRoot = + aggregatedUninstallModulesDeps.associate { it.test to it.fqName } + return mutableSetOf<ComponentTreeDepsIr>().apply { + aggregatedDepsByRoot.keys.forEach { root -> + val isDefaultRoot = root == DEFAULT_ROOT_CLASS_NAME + val isEarlyEntryPointRoot = isDefaultRoot && aggregatedEarlyEntryPointDeps.isNotEmpty() + // We want to base the generated name on the user written root rather than a generated root. + val rootName = if (isDefaultRoot) { + DEFAULT_ROOT_CLASS_NAME + } else { + aggregatedRootsByRoot.getValue(root).originatingRoot + } + val componentNameGenerator = + if (isSharedTestComponentsEnabled) { + ComponentTreeDepsNameGenerator( + destinationPackage = "dagger.hilt.android.internal.testing.root", + otherRootNames = aggregatedDepsByRoot.keys + ) + } else { + ComponentTreeDepsNameGenerator() + } + add( + ComponentTreeDepsIr( + name = componentNameGenerator.generate(rootName), + rootDeps = + // Non-default component: the root + // Shared component: all roots sharing the component + // EarlyEntryPoint component: empty + if (isDefaultRoot) { + rootsUsingSharedComponent.map { aggregatedRootsByRoot.getValue(it).fqName }.toSet() + } else { + setOf(aggregatedRootsByRoot.getValue(root).fqName) + }, + defineComponentDeps = defineComponentDeps.map { it.fqName }.toSet(), + aliasOfDeps = aliasOfDeps.map { it.fqName }.toSet(), + aggregatedDeps = aggregatedDepsByRoot.getOrElse(root) { emptySet() }, + uninstallModulesDeps = uninstallModuleDepsByRoot[root]?.let { setOf(it) } ?: emptySet(), + earlyEntryPointDeps = + if (isEarlyEntryPointRoot) { + aggregatedEarlyEntryPointDeps.map { it.fqName }.toSet() + } else { + emptySet() + } + ) + ) + } + } + } + + private fun rootsUsingSharedComponent(roots: Set<AggregatedRootIr>): Set<ClassName> { + if (!isSharedTestComponentsEnabled) { + return emptySet() + } + val hasLocalModuleDependencies: Set<ClassName> = mutableSetOf<ClassName>().apply { + addAll(aggregatedDeps.filter { it.module != null }.mapNotNull { it.test }) + addAll(aggregatedUninstallModulesDeps.map { it.test }) + } + return roots + .filter { it.isTestRoot && it.allowsSharingComponent } + .map { it.root } + .filter { !hasLocalModuleDependencies.contains(it) } + .toSet() + } + + private fun aggregatedDepsByRoot( + aggregatedRoots: Set<AggregatedRootIr>, + rootsUsingSharedComponent: Set<ClassName>, + hasEarlyEntryPoints: Boolean + ): Map<ClassName, Set<ClassName>> { + val testDepsByRoot = aggregatedDeps + .filter { it.test != null } + .groupBy(keySelector = { it.test }, valueTransform = { it.fqName }) + val globalModules = aggregatedDeps + .filter { it.test == null && it.module != null } + .map { it.fqName } + val globalEntryPointsByComponent = aggregatedDeps + .filter { it.test == null && it.module == null } + .groupBy(keySelector = { it.test }, valueTransform = { it.fqName }) + val result = mutableMapOf<ClassName, LinkedHashSet<ClassName>>() + aggregatedRoots.forEach { aggregatedRoot -> + if (!rootsUsingSharedComponent.contains(aggregatedRoot.root)) { + result.getOrPut(aggregatedRoot.root) { linkedSetOf() }.apply { + addAll(globalModules) + addAll(globalEntryPointsByComponent.values.flatten()) + addAll(testDepsByRoot.getOrElse(aggregatedRoot.root) { emptyList() }) + } + } + } + // Add the Default/EarlyEntryPoint root if necessary. + if (rootsUsingSharedComponent.isNotEmpty()) { + result.getOrPut(DEFAULT_ROOT_CLASS_NAME) { linkedSetOf() }.apply { + addAll(globalModules) + addAll(globalEntryPointsByComponent.values.flatten()) + addAll(rootsUsingSharedComponent.flatMap { testDepsByRoot.getOrElse(it) { emptyList() } }) + } + } else if (hasEarlyEntryPoints) { + result.getOrPut(DEFAULT_ROOT_CLASS_NAME) { linkedSetOf() }.apply { + addAll(globalModules) + addAll( + globalEntryPointsByComponent.entries + .filterNot { (component, _) -> component == SINGLETON_COMPONENT_CLASS_NAME } + .flatMap { (_, entryPoints) -> entryPoints } + ) + } + } + return result + } + + /** + * Generates a component name for a tree that will be based off the given root after mapping it to + * the [destinationPackage] and disambiguating from [otherRootNames]. + */ + private class ComponentTreeDepsNameGenerator( + private val destinationPackage: String? = null, + private val otherRootNames: Collection<ClassName> = emptySet() + ) { + private val simpleNameMap: Map<ClassName, String> by lazy { + mutableMapOf<ClassName, String>().apply { + otherRootNames.groupBy { it.enclosedName() }.values.forEach { conflictingRootNames -> + if (conflictingRootNames.size == 1) { + // If there's only 1 root there's nothing to disambiguate so return the simple name. + put(conflictingRootNames.first(), conflictingRootNames.first().enclosedName()) + } else { + // There are conflicting simple names, so disambiguate them with a unique prefix. + // We keep them small to fix https://github.com/google/dagger/issues/421. + // Sorted in order to guarantee determinism if this is invoked by different processors. + val usedNames = mutableSetOf<String>() + conflictingRootNames.sorted().forEach { rootClassName -> + val basePrefix = rootClassName.let { className -> + val containerName = className.enclosingClassName()?.enclosedName() ?: "" + if (containerName.isNotEmpty() && containerName[0].isUpperCase()) { + // If parent element looks like a class, use its initials as a prefix. + containerName.filterNot { it.isLowerCase() } + } else { + // Not in a normally named class. Prefix with the initials of the elements + // leading here. + className.toString().split('.').dropLast(1) + .joinToString(separator = "") { "${it.first()}" } + } + } + var uniqueName = basePrefix + var differentiator = 2 + while (!usedNames.add(uniqueName)) { + uniqueName = basePrefix + differentiator++ + } + put(rootClassName, "${uniqueName}_${rootClassName.enclosedName()}") + } + } + } + } + } + + fun generate(rootName: ClassName): ClassName = + ClassName.get( + destinationPackage ?: rootName.packageName(), + if (otherRootNames.isEmpty()) { + rootName.enclosedName() + } else { + simpleNameMap.getValue(rootName) + } + ).append("_ComponentTreeDeps") + + private fun ClassName.enclosedName() = simpleNames().joinToString(separator = "_") + + private fun ClassName.append(suffix: String) = peerClass(simpleName() + suffix) + } + + companion object { + + @JvmStatic + fun components( + isTest: Boolean, + isSharedTestComponentsEnabled: Boolean, + aggregatedRoots: Set<AggregatedRootIr>, + defineComponentDeps: Set<DefineComponentClassesIr>, + aliasOfDeps: Set<AliasOfPropagatedDataIr>, + aggregatedDeps: Set<AggregatedDepsIr>, + aggregatedUninstallModulesDeps: Set<AggregatedUninstallModulesIr>, + aggregatedEarlyEntryPointDeps: Set<AggregatedEarlyEntryPointIr>, + ) = ComponentTreeDepsIrCreator( + isSharedTestComponentsEnabled, + // TODO(bcorso): Consider creating a common interface for fqName so that we can sort these + // using a shared method rather than repeating the sorting logic. + aggregatedRoots.toList().sortedBy { it.fqName.canonicalName() }.toSet(), + defineComponentDeps.toList().sortedBy { it.fqName.canonicalName() }.toSet(), + aliasOfDeps.toList().sortedBy { it.fqName.canonicalName() }.toSet(), + aggregatedDeps.toList().sortedBy { it.fqName.canonicalName() }.toSet(), + aggregatedUninstallModulesDeps.toList().sortedBy { it.fqName.canonicalName() }.toSet(), + aggregatedEarlyEntryPointDeps.toList().sortedBy { it.fqName.canonicalName() }.toSet() + ).let { producer -> + if (isTest) { + producer.testComponents() + } else { + producer.prodComponents() + } + } + + val DEFAULT_ROOT_CLASS_NAME: ClassName = + ClassName.get("dagger.hilt.android.internal.testing.root", "Default") + val SINGLETON_COMPONENT_CLASS_NAME: ClassName = + ClassName.get("dagger.hilt.components", "SingletonComponent") + } +} diff --git a/java/dagger/hilt/processor/internal/root/ir/MetadataIr.kt b/java/dagger/hilt/processor/internal/root/ir/MetadataIr.kt new file mode 100644 index 000000000..200c0d45a --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/ir/MetadataIr.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root.ir + +import com.squareup.javapoet.ClassName + +/** + * Represents [dagger.hilt.processor.internal.aggregateddeps.AggregatedDeps] + * + * Even though the annotation uses arrays for modules, entryPoints and componentEntryPoints the + * reality is that exactly only one value will be present in one of those arrays. + */ +data class AggregatedDepsIr( + val fqName: ClassName, + val components: List<ClassName>, + val test: ClassName?, + val replaces: List<ClassName>, + val module: ClassName?, + val entryPoint: ClassName?, + val componentEntryPoint: ClassName?, +) + +/** Represents [dagger.hilt.android.internal.earlyentrypoint.AggregatedEarlyEntryPoint] */ +data class AggregatedEarlyEntryPointIr( + val fqName: ClassName, + val earlyEntryPoint: ClassName, +) + +/** Represents [dagger.hilt.android.internal.legacy.AggregatedElementProxy] */ +data class AggregatedElementProxyIr( + val fqName: ClassName, + val value: ClassName, +) + +/** Represents [dagger.hilt.internal.aggregatedroot.AggregatedRoot] */ +data class AggregatedRootIr( + val fqName: ClassName, + val root: ClassName, + val originatingRoot: ClassName, + val rootAnnotation: ClassName, + // External property from the annotation that indicates if root can use a shared component. + val allowsSharingComponent: Boolean = true +) { + // Equivalent to RootType.isTestRoot() + val isTestRoot = TEST_ROOT_ANNOTATIONS.contains(rootAnnotation.toString()) + + companion object { + private val TEST_ROOT_ANNOTATIONS = + listOf( + "dagger.hilt.android.testing.HiltAndroidTest", + "dagger.hilt.android.internal.testing.InternalTestRoot", + ) + } +} + +/** Represents [dagger.hilt.android.internal.uninstallmodules.AggregatedUninstallModules] */ +data class AggregatedUninstallModulesIr( + val fqName: ClassName, + val test: ClassName, + val uninstallModules: List<ClassName> +) + +/** Represents [dagger.hilt.internal.aliasof.AliasOfPropagatedData] */ +data class AliasOfPropagatedDataIr( + val fqName: ClassName, + val defineComponentScopes: List<ClassName>, + val alias: ClassName, +) + +/** Represents [dagger.hilt.internal.componenttreedeps.ComponentTreeDeps] */ +data class ComponentTreeDepsIr( + val name: ClassName, + val rootDeps: Set<ClassName>, + val defineComponentDeps: Set<ClassName>, + val aliasOfDeps: Set<ClassName>, + val aggregatedDeps: Set<ClassName>, + val uninstallModulesDeps: Set<ClassName>, + val earlyEntryPointDeps: Set<ClassName>, +) + +/** Represents [dagger.hilt.internal.definecomponent.DefineComponentClasses] */ +data class DefineComponentClassesIr( + val fqName: ClassName, + val component: ClassName, +) + +/** Represents [dagger.hilt.internal.processedrootsentinel.ProcessedRootSentinel] */ +data class ProcessedRootSentinelIr(val fqName: ClassName, val roots: List<ClassName>) diff --git a/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java index eee78490d..5cad3c9df 100644 --- a/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java +++ b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java @@ -23,10 +23,13 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.AggregatedElements; import dagger.hilt.processor.internal.AnnotationValues; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.root.ir.AggregatedUninstallModulesIr; +import java.util.stream.Collectors; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.TypeElement; @@ -39,6 +42,9 @@ import javax.lang.model.util.Elements; @AutoValue public abstract class AggregatedUninstallModulesMetadata { + /** Returns the aggregating element */ + public abstract TypeElement aggregatingElement(); + /** Returns the test annotated with {@link dagger.hilt.android.testing.UninstallModules}. */ public abstract TypeElement testElement(); @@ -47,17 +53,33 @@ public abstract class AggregatedUninstallModulesMetadata { */ public abstract ImmutableList<TypeElement> uninstallModuleElements(); - /** Returns all aggregated deps in the aggregating package mapped by the top-level element. */ + /** Returns metadata for all aggregated elements in the aggregating package. */ public static ImmutableSet<AggregatedUninstallModulesMetadata> from(Elements elements) { - return AggregatedElements.from( + return from( + AggregatedElements.from( ClassNames.AGGREGATED_UNINSTALL_MODULES_PACKAGE, ClassNames.AGGREGATED_UNINSTALL_MODULES, - elements) - .stream() + elements), + elements); + } + + /** Returns metadata for each aggregated element. */ + public static ImmutableSet<AggregatedUninstallModulesMetadata> from( + ImmutableSet<TypeElement> aggregatedElements, Elements elements) { + return aggregatedElements.stream() .map(aggregatedElement -> create(aggregatedElement, elements)) .collect(toImmutableSet()); } + public static AggregatedUninstallModulesIr toIr(AggregatedUninstallModulesMetadata metadata) { + return new AggregatedUninstallModulesIr( + ClassName.get(metadata.aggregatingElement()), + ClassName.get(metadata.testElement()), + metadata.uninstallModuleElements().stream() + .map(ClassName::get) + .collect(Collectors.toList())); + } + private static AggregatedUninstallModulesMetadata create(TypeElement element, Elements elements) { AnnotationMirror annotationMirror = Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_UNINSTALL_MODULES); @@ -66,6 +88,7 @@ public abstract class AggregatedUninstallModulesMetadata { Processors.getAnnotationValues(elements, annotationMirror); return new AutoValue_AggregatedUninstallModulesMetadata( + element, elements.getTypeElement(AnnotationValues.getString(values.get("test"))), AnnotationValues.getAnnotationValues(values.get("uninstallModules")).stream() .map(AnnotationValues::getString) diff --git a/java/dagger/hilt/processor/internal/uninstallmodules/BUILD b/java/dagger/hilt/processor/internal/uninstallmodules/BUILD index 4f944be48..876c4739f 100644 --- a/java/dagger/hilt/processor/internal/uninstallmodules/BUILD +++ b/java/dagger/hilt/processor/internal/uninstallmodules/BUILD @@ -36,11 +36,11 @@ java_library( "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/guava/collect", + "//third_party/java/incap", + "//third_party/java/javapoet", ], ) @@ -53,9 +53,11 @@ java_library( "//java/dagger/hilt/processor/internal:aggregated_elements", "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:processors", + "//java/dagger/hilt/processor/internal/root/ir", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:value", + "//third_party/java/auto:value", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/testing/BUILD b/java/dagger/hilt/testing/BUILD index 3ea1c5f4b..59a1e9447 100644 --- a/java/dagger/hilt/testing/BUILD +++ b/java/dagger/hilt/testing/BUILD @@ -21,7 +21,7 @@ java_library( name = "package_info", srcs = ["package-info.java"], deps = [ - "@google_bazel_common//third_party/java/jsr305_annotations", + "//third_party/java/jsr305_annotations", ], ) diff --git a/java/dagger/internal/DoubleCheck.java b/java/dagger/internal/DoubleCheck.java index ea07528bf..af7d7f69a 100644 --- a/java/dagger/internal/DoubleCheck.java +++ b/java/dagger/internal/DoubleCheck.java @@ -60,11 +60,8 @@ public final class DoubleCheck<T> implements Provider<T>, Lazy<T> { * new instance is the same as the current instance, return the instance. However, if the new * instance differs from the current instance, an {@link IllegalStateException} is thrown. */ - public static Object reentrantCheck(Object currentInstance, Object newInstance) { - boolean isReentrant = !(currentInstance == UNINITIALIZED - // This check is needed for fastInit's implementation, which uses MemoizedSentinel types. - || currentInstance instanceof MemoizedSentinel); - + private static Object reentrantCheck(Object currentInstance, Object newInstance) { + boolean isReentrant = currentInstance != UNINITIALIZED; if (isReentrant && currentInstance != newInstance) { throw new IllegalStateException("Scoped provider was invoked recursively returning " + "different results: " + currentInstance + " & " + newInstance + ". This is likely " diff --git a/java/dagger/internal/QualifierMetadata.java b/java/dagger/internal/QualifierMetadata.java new file mode 100644 index 000000000..aa6f0d0de --- /dev/null +++ b/java/dagger/internal/QualifierMetadata.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 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; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stores the qualifier information about a type after it's been processed. */ +@Retention(CLASS) +@Target(TYPE) +public @interface QualifierMetadata { + /** + * Returns the list of fully qualified qualifier names used in a particular context. + * + * <p>For example, when annotating Dagger's generated {@code _Factory} class for an inject + * constructor, it contains all qualifiers used on parameters within the constructor. When + * annotating Dagger's generated {@code _MembersInjector} class for inject fields and methods, it + * contains all qualifiers found on the fields and method parameters. When annotating Dagger's + * generated {@code _Factory} class for provision methods it includes all qualifiers used on the + * provision method and its parameters. + */ + String[] value() default {}; +} diff --git a/java/dagger/example/gradle/android/simple/app/src/main/java/dagger/example/gradle/android/simple/Model.java b/java/dagger/internal/ScopeMetadata.java index 30a973e03..a313b5e4f 100644 --- a/java/dagger/example/gradle/android/simple/app/src/main/java/dagger/example/gradle/android/simple/Model.java +++ b/java/dagger/internal/ScopeMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Dagger Authors. + * Copyright (C) 2022 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. @@ -14,16 +14,18 @@ * limitations under the License. */ -package dagger.example.gradle.android.simple; +package dagger.internal; -import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; -import java.lang.annotation.Documented; import java.lang.annotation.Retention; -import javax.inject.Qualifier; +import java.lang.annotation.Target; -/** Qualifies bindings relating to {@link android.os.Build#MODEL}. */ -@Qualifier -@Retention(RUNTIME) -@Documented -@interface Model {} +/** Stores the scope information about a type after it's been processed. */ +@Retention(CLASS) +@Target(TYPE) +public @interface ScopeMetadata { + /** The qualified name of the scope, if one exists, otherwise an empty string. */ + String value() default ""; +} diff --git a/java/dagger/internal/codegen/AssistedFactoryProcessingStep.java b/java/dagger/internal/codegen/AssistedFactoryProcessingStep.java index dbd2b3365..7242e267a 100644 --- a/java/dagger/internal/codegen/AssistedFactoryProcessingStep.java +++ b/java/dagger/internal/codegen/AssistedFactoryProcessingStep.java @@ -16,24 +16,31 @@ package dagger.internal.codegen; -import static com.google.auto.common.MoreElements.asType; -import static com.google.auto.common.MoreTypes.asDeclared; -import static com.google.auto.common.MoreTypes.asTypeElement; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedFactoryMethods; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; import static dagger.internal.codegen.javapoet.TypeNames.INSTANCE_FACTORY; import static dagger.internal.codegen.javapoet.TypeNames.providerOf; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XMethodElements.hasTypeParameters; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import static java.util.stream.Collectors.joining; -import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XMethodElement; +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; @@ -45,7 +52,6 @@ import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; -import dagger.assisted.AssistedFactory; import dagger.internal.codegen.base.SourceFileGenerationException; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.AssistedInjectionAnnotations; @@ -53,62 +59,55 @@ import dagger.internal.codegen.binding.AssistedInjectionAnnotations.AssistedFact import dagger.internal.codegen.binding.AssistedInjectionAnnotations.AssistedParameter; import dagger.internal.codegen.binding.BindingFactory; import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.validation.SuperficialValidator; import dagger.internal.codegen.validation.TypeCheckingProcessingStep; import dagger.internal.codegen.validation.ValidationReport; -import java.lang.annotation.Annotation; +import dagger.internal.codegen.xprocessing.MethodSpecs; import java.util.HashSet; import java.util.Optional; import java.util.Set; -import javax.annotation.processing.Filer; -import javax.annotation.processing.Messager; import javax.inject.Inject; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; /** An annotation processor for {@link dagger.assisted.AssistedFactory}-annotated types. */ -final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<TypeElement> { - private final Messager messager; - private final Filer filer; +final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<XTypeElement> { + private final XMessager messager; + private final XFiler filer; private final SourceVersion sourceVersion; private final DaggerElements elements; private final DaggerTypes types; private final BindingFactory bindingFactory; + private final SuperficialValidator superficialValidator; @Inject AssistedFactoryProcessingStep( - Messager messager, - Filer filer, + XMessager messager, + XFiler filer, SourceVersion sourceVersion, DaggerElements elements, DaggerTypes types, - BindingFactory bindingFactory) { - super(MoreElements::asType); + BindingFactory bindingFactory, + SuperficialValidator superficialValidator) { this.messager = messager; this.filer = filer; this.sourceVersion = sourceVersion; this.elements = elements; this.types = types; this.bindingFactory = bindingFactory; + this.superficialValidator = superficialValidator; } @Override - public ImmutableSet<Class<? extends Annotation>> annotations() { - return ImmutableSet.of(AssistedFactory.class); + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.ASSISTED_FACTORY); } @Override - protected void process( - TypeElement factory, ImmutableSet<Class<? extends Annotation>> annotations) { - ValidationReport<TypeElement> report = new AssistedFactoryValidator().validate(factory); + protected void process(XTypeElement factory, ImmutableSet<ClassName> annotations) { + ValidationReport report = new AssistedFactoryValidator().validate(factory); report.printMessagesTo(messager); if (report.isClean()) { try { @@ -121,10 +120,10 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ } private final class AssistedFactoryValidator { - ValidationReport<TypeElement> validate(TypeElement factory) { - ValidationReport.Builder<TypeElement> report = ValidationReport.about(factory); + ValidationReport validate(XTypeElement factory) { + ValidationReport.Builder report = ValidationReport.about(factory); - if (!factory.getModifiers().contains(ABSTRACT)) { + if (!factory.isAbstract()) { return report .addError( "The @AssistedFactory-annotated type must be either an abstract class or " @@ -133,12 +132,11 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ .build(); } - if (factory.getNestingKind().isNested() && !factory.getModifiers().contains(STATIC)) { + if (factory.isNested() && !factory.isStatic()) { report.addError("Nested @AssistedFactory-annotated types must be static. ", factory); } - ImmutableSet<ExecutableElement> abstractFactoryMethods = - AssistedInjectionAnnotations.assistedFactoryMethods(factory, elements); + ImmutableSet<XMethodElement> abstractFactoryMethods = assistedFactoryMethods(factory); if (abstractFactoryMethods.isEmpty()) { report.addError( @@ -147,17 +145,23 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ factory); } - for (ExecutableElement method : abstractFactoryMethods) { - ExecutableType methodType = types.resolveExecutableType(method, factory.asType()); - if (!isAssistedInjectionType(methodType.getReturnType())) { + for (XMethodElement method : abstractFactoryMethods) { + XType returnType = method.asMemberOf(factory.getType()).getReturnType(); + // The default superficial validation only applies to the @AssistedFactory-annotated + // element, so we have to manually check the superficial validation of the @AssistedInject + // element before using it to ensure it's ready for processing. + if (isDeclared(returnType)) { + superficialValidator.throwIfNearestEnclosingTypeNotValid(returnType.getTypeElement()); + } + if (!isAssistedInjectionType(returnType)) { report.addError( String.format( "Invalid return type: %s. An assisted factory's abstract method must return a " + "type with an @AssistedInject-annotated constructor.", - methodType.getReturnType()), + returnType), method); } - if (!method.getTypeParameters().isEmpty()) { + if (hasTypeParameters(method)) { report.addError( "@AssistedFactory does not currently support type parameters in the creator " + "method. See https://github.com/google/dagger/issues/2279", @@ -177,8 +181,7 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ return report.build(); } - AssistedFactoryMetadata metadata = - AssistedFactoryMetadata.create(factory.asType(), elements, types); + AssistedFactoryMetadata metadata = AssistedFactoryMetadata.create(factory.getType()); // Note: We check uniqueness of the @AssistedInject constructor parameters in // AssistedInjectProcessingStep. We need to check uniqueness for here too because we may @@ -188,7 +191,7 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ if (!uniqueAssistedParameters.add(assistedParameter)) { report.addError( "@AssistedFactory method has duplicate @Assisted types: " + assistedParameter, - assistedParameter.variableElement()); + assistedParameter.element()); } } @@ -203,7 +206,7 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ metadata.factory().getQualifiedName(), metadata.factoryMethod(), metadata.factory().getQualifiedName(), - metadata.factoryMethod().getSimpleName(), + getSimpleName(metadata.factoryMethod()), metadata.assistedInjectAssistedParameters().stream() .map(AssistedParameter::type) .map(Object::toString) @@ -214,9 +217,9 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ return report.build(); } - private boolean isAssistedInjectionType(TypeMirror type) { - return type.getKind() == TypeKind.DECLARED - && AssistedInjectionAnnotations.isAssistedInjectionType(asTypeElement(type)); + private boolean isAssistedInjectionType(XType type) { + return isDeclared(type) + && AssistedInjectionAnnotations.isAssistedInjectionType(type.getTypeElement()); } } @@ -227,7 +230,7 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ } @Override - public Element originatingElement(ProvisionBinding binding) { + public XElement originatingElement(ProvisionBinding binding) { return binding.bindingElement().get(); } @@ -265,25 +268,24 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ // } @Override public ImmutableList<TypeSpec.Builder> topLevelTypes(ProvisionBinding binding) { - TypeElement factory = asType(binding.bindingElement().get()); + XTypeElement factory = asTypeElement(binding.bindingElement().get()); ClassName name = generatedClassNameForBinding(binding); TypeSpec.Builder builder = TypeSpec.classBuilder(name) .addModifiers(PUBLIC, FINAL) .addTypeVariables( - factory.getTypeParameters().stream() + toJavac(factory).getTypeParameters().stream() .map(TypeVariableName::get) .collect(toImmutableList())); - if (factory.getKind() == ElementKind.INTERFACE) { - builder.addSuperinterface(factory.asType()); + if (factory.isInterface()) { + builder.addSuperinterface(factory.getType().getTypeName()); } else { - builder.superclass(factory.asType()); + builder.superclass(factory.getType().getTypeName()); } - AssistedFactoryMetadata metadata = - AssistedFactoryMetadata.create(asDeclared(factory.asType()), elements, types); + AssistedFactoryMetadata metadata = AssistedFactoryMetadata.create(factory.getType()); ParameterSpec delegateFactoryParam = ParameterSpec.builder( delegateFactoryTypeName(metadata.assistedInjectType()), "delegateFactory") @@ -299,7 +301,7 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ .addStatement("this.$1N = $1N", delegateFactoryParam) .build()) .addMethod( - MethodSpec.overriding(metadata.factoryMethod(), metadata.factoryType(), types) + MethodSpecs.overriding(metadata.factoryMethod(), metadata.factoryType()) .addStatement( "return $N.get($L)", delegateFactoryParam, @@ -307,7 +309,7 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ // use the parameter names of the @AssistedFactory method. metadata.assistedInjectAssistedParameters().stream() .map(metadata.assistedFactoryAssistedParametersMap()::get) - .map(param -> CodeBlock.of("$L", param.getSimpleName())) + .map(param -> CodeBlock.of("$L", getSimpleName(param))) .collect(toParametersCodeBlock())) .build()) .addMethod( @@ -315,10 +317,10 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ .addModifiers(PUBLIC, STATIC) .addParameter(delegateFactoryParam) .addTypeVariables( - metadata.assistedInjectElement().getTypeParameters().stream() + toJavac(metadata.assistedInjectElement()).getTypeParameters().stream() .map(TypeVariableName::get) .collect(toImmutableList())) - .returns(providerOf(TypeName.get(factory.asType()))) + .returns(providerOf(factory.getType().getTypeName())) .addStatement( "return $T.$Lcreate(new $T($N))", INSTANCE_FACTORY, @@ -333,13 +335,13 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ } /** Returns the generated factory {@link TypeName type} for an @AssistedInject constructor. */ - private TypeName delegateFactoryTypeName(DeclaredType assistedInjectType) { + private TypeName delegateFactoryTypeName(XType assistedInjectType) { // The name of the generated factory for the assisted inject type, // e.g. an @AssistedInject Foo(...) {...} constructor will generate a Foo_Factory class. ClassName generatedFactoryClassName = generatedClassNameForBinding( bindingFactory.injectionBinding( - getOnlyElement(assistedInjectedConstructors(asTypeElement(assistedInjectType))), + getOnlyElement(assistedInjectedConstructors(assistedInjectType.getTypeElement())), Optional.empty())); // Return the factory type resolved with the same type parameters as the assisted inject type. @@ -348,7 +350,7 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ : ParameterizedTypeName.get( generatedFactoryClassName, assistedInjectType.getTypeArguments().stream() - .map(TypeName::get) + .map(XType::getTypeName) .collect(toImmutableList()) .toArray(new TypeName[0])); } diff --git a/java/dagger/internal/codegen/AssistedInjectProcessingStep.java b/java/dagger/internal/codegen/AssistedInjectProcessingStep.java index 167c4aebe..df3a7d644 100644 --- a/java/dagger/internal/codegen/AssistedInjectProcessingStep.java +++ b/java/dagger/internal/codegen/AssistedInjectProcessingStep.java @@ -16,71 +16,60 @@ package dagger.internal.codegen; -import static com.google.auto.common.MoreTypes.asDeclared; -import static com.google.common.base.Preconditions.checkState; -import static dagger.internal.codegen.langmodel.DaggerElements.closestEnclosingTypeElement; +import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectAssistedParameters; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XType; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import dagger.assisted.AssistedInject; -import dagger.internal.codegen.binding.AssistedInjectionAnnotations; +import com.squareup.javapoet.ClassName; import dagger.internal.codegen.binding.AssistedInjectionAnnotations.AssistedParameter; -import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.validation.TypeCheckingProcessingStep; import dagger.internal.codegen.validation.ValidationReport; -import java.lang.annotation.Annotation; import java.util.HashSet; import java.util.Set; -import javax.annotation.processing.Messager; import javax.inject.Inject; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.DeclaredType; /** An annotation processor for {@link dagger.assisted.AssistedInject}-annotated elements. */ -final class AssistedInjectProcessingStep extends TypeCheckingProcessingStep<ExecutableElement> { - private final DaggerTypes types; - private final Messager messager; +final class AssistedInjectProcessingStep extends TypeCheckingProcessingStep<XConstructorElement> { + private final XMessager messager; @Inject - AssistedInjectProcessingStep(DaggerTypes types, Messager messager) { - super(MoreElements::asExecutable); - this.types = types; + AssistedInjectProcessingStep(XMessager messager) { this.messager = messager; } @Override - public ImmutableSet<Class<? extends Annotation>> annotations() { - return ImmutableSet.of(AssistedInject.class); + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.ASSISTED_INJECT); } @Override protected void process( - ExecutableElement assistedInjectElement, - ImmutableSet<Class<? extends Annotation>> annotations) { + XConstructorElement assistedInjectElement, ImmutableSet<ClassName> annotations) { new AssistedInjectValidator().validate(assistedInjectElement).printMessagesTo(messager); } private final class AssistedInjectValidator { - ValidationReport<ExecutableElement> validate(ExecutableElement constructor) { - checkState(constructor.getKind() == ElementKind.CONSTRUCTOR); - ValidationReport.Builder<ExecutableElement> report = ValidationReport.about(constructor); + ValidationReport validate(XConstructorElement constructor) { + ValidationReport.Builder report = ValidationReport.about(constructor); - DeclaredType assistedInjectType = - asDeclared(closestEnclosingTypeElement(constructor).asType()); + XType assistedInjectType = constructor.getEnclosingElement().getType(); ImmutableList<AssistedParameter> assistedParameters = - AssistedInjectionAnnotations.assistedInjectAssistedParameters(assistedInjectType, types); + assistedInjectAssistedParameters(assistedInjectType); Set<AssistedParameter> uniqueAssistedParameters = new HashSet<>(); for (AssistedParameter assistedParameter : assistedParameters) { if (!uniqueAssistedParameters.add(assistedParameter)) { report.addError( - String.format("@AssistedInject constructor has duplicate @Assisted type: %s. " - + "Consider setting an identifier on the parameter by using " - + "@Assisted(\"identifier\") in both the factory and @AssistedInject constructor", + String.format( + "@AssistedInject constructor has duplicate @Assisted type: %s. Consider setting" + + " an identifier on the parameter by using @Assisted(\"identifier\") in both" + + " the factory and @AssistedInject constructor", assistedParameter), - assistedParameter.variableElement()); + assistedParameter.element()); } } diff --git a/java/dagger/internal/codegen/AssistedProcessingStep.java b/java/dagger/internal/codegen/AssistedProcessingStep.java index 12a0d34b4..c44d2c550 100644 --- a/java/dagger/internal/codegen/AssistedProcessingStep.java +++ b/java/dagger/internal/codegen/AssistedProcessingStep.java @@ -16,67 +16,57 @@ package dagger.internal.codegen; -import static com.google.auto.common.MoreElements.isAnnotationPresent; -import static dagger.internal.codegen.langmodel.DaggerElements.closestEnclosingTypeElement; +import static androidx.room.compiler.processing.XElementKt.isConstructor; +import static androidx.room.compiler.processing.XElementKt.isMethod; +import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedFactoryMethod; +import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; +import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableSet; -import dagger.assisted.Assisted; -import dagger.assisted.AssistedInject; -import dagger.internal.codegen.binding.AssistedInjectionAnnotations; +import com.squareup.javapoet.ClassName; import dagger.internal.codegen.binding.InjectionAnnotations; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.validation.TypeCheckingProcessingStep; import dagger.internal.codegen.validation.ValidationReport; -import java.lang.annotation.Annotation; -import javax.annotation.processing.Messager; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; /** * An annotation processor for {@link dagger.assisted.Assisted}-annotated types. * * <p>This processing step should run after {@link AssistedFactoryProcessingStep}. */ -final class AssistedProcessingStep extends TypeCheckingProcessingStep<VariableElement> { - private final KotlinMetadataUtil kotlinMetadataUtil; +final class AssistedProcessingStep extends TypeCheckingProcessingStep<XExecutableParameterElement> { private final InjectionAnnotations injectionAnnotations; - private final DaggerElements elements; - private final Messager messager; + private final XMessager messager; @Inject - AssistedProcessingStep( - KotlinMetadataUtil kotlinMetadataUtil, - InjectionAnnotations injectionAnnotations, - DaggerElements elements, - Messager messager) { - super(MoreElements::asVariable); - this.kotlinMetadataUtil = kotlinMetadataUtil; + AssistedProcessingStep(InjectionAnnotations injectionAnnotations, XMessager messager) { this.injectionAnnotations = injectionAnnotations; - this.elements = elements; this.messager = messager; } @Override - public ImmutableSet<Class<? extends Annotation>> annotations() { - return ImmutableSet.of(Assisted.class); + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.ASSISTED); } @Override protected void process( - VariableElement assisted, ImmutableSet<Class<? extends Annotation>> annotations) { + XExecutableParameterElement assisted, ImmutableSet<ClassName> annotations) { new AssistedValidator().validate(assisted).printMessagesTo(messager); } private final class AssistedValidator { - ValidationReport<VariableElement> validate(VariableElement assisted) { - ValidationReport.Builder<VariableElement> report = ValidationReport.about(assisted); + ValidationReport validate(XExecutableParameterElement assisted) { + ValidationReport.Builder report = ValidationReport.about(assisted); - Element enclosingElement = assisted.getEnclosingElement(); + XExecutableElement enclosingElement = assisted.getEnclosingMethodElement(); if (!isAssistedInjectConstructor(enclosingElement) && !isAssistedFactoryCreateMethod(enclosingElement) // The generated java stubs for kotlin data classes contain a "copy" method that has @@ -99,30 +89,29 @@ final class AssistedProcessingStep extends TypeCheckingProcessingStep<VariableEl } } - private boolean isAssistedInjectConstructor(Element element) { - return element.getKind() == ElementKind.CONSTRUCTOR - && isAnnotationPresent(element, AssistedInject.class); + private boolean isAssistedInjectConstructor(XExecutableElement executableElement) { + return isConstructor(executableElement) + && executableElement.hasAnnotation(TypeNames.ASSISTED_INJECT); } - private boolean isAssistedFactoryCreateMethod(Element element) { - if (element.getKind() == ElementKind.METHOD) { - TypeElement enclosingElement = closestEnclosingTypeElement(element); - return AssistedInjectionAnnotations.isAssistedFactoryType(enclosingElement) + private boolean isAssistedFactoryCreateMethod(XExecutableElement executableElement) { + if (isMethod(executableElement)) { + XTypeElement enclosingElement = closestEnclosingTypeElement(executableElement); + return isAssistedFactoryType(enclosingElement) // This assumes we've already validated AssistedFactory and that a valid method exists. - && AssistedInjectionAnnotations.assistedFactoryMethod(enclosingElement, elements) - .equals(element); + && assistedFactoryMethod(enclosingElement).equals(executableElement); } return false; } - private boolean isKotlinDataClassCopyMethod(Element element) { + private boolean isKotlinDataClassCopyMethod(XExecutableElement executableElement) { // Note: This is a best effort. Technically, we could check the return type and parameters of // the copy method to verify it's the one associated with the constructor, but I'd rather keep // this simple to avoid encoding too many details of kapt's stubs. At worst, we'll be allowing // an @Assisted annotation that has no affect, which is already true for many of Dagger's other // annotations. - return element.getKind() == ElementKind.METHOD - && element.getSimpleName().contentEquals("copy") - && kotlinMetadataUtil.isDataClass(closestEnclosingTypeElement(element)); + return isMethod(executableElement) + && getSimpleName(asMethod(executableElement)).contentEquals("copy") + && closestEnclosingTypeElement(executableElement.getEnclosingElement()).isDataClass(); } } diff --git a/java/dagger/internal/codegen/BUILD b/java/dagger/internal/codegen/BUILD index e2f74e9e7..d1e01ae38 100644 --- a/java/dagger/internal/codegen/BUILD +++ b/java/dagger/internal/codegen/BUILD @@ -34,9 +34,6 @@ java_library( "//java/dagger/internal/codegen/bootstrap", ], tags = ["maven_coordinates=com.google.dagger:dagger-compiler:" + POM_VERSION], - exports = [ - "@google_bazel_common//third_party/java/jsr250_annotations", # Export for @Generated - ], deps = [ ":package_info", "//java/dagger:core", @@ -51,17 +48,19 @@ java_library( "//java/dagger/internal/codegen/langmodel", "//java/dagger/internal/codegen/validation", "//java/dagger/internal/codegen/writing", - "//java/dagger/internal/guava:collect", - "//java/dagger/producers", + "//java/dagger/internal/codegen/xprocessing", "//java/dagger/spi", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/error_prone:annotations", - "@google_bazel_common//third_party/java/google_java_format", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/jsr330_inject", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:service", + "//third_party/java/auto:value", + "//third_party/java/error_prone:annotations", + "//third_party/java/google_java_format", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/incap", + "//third_party/java/javapoet", + "//third_party/java/jsr330_inject", + "@bazel_tools//tools/jdk:langtools-neverlink", ], ) @@ -69,7 +68,7 @@ java_library( name = "package_info", srcs = ["package-info.java"], tags = ["maven:merged"], - deps = ["@google_bazel_common//third_party/java/error_prone:annotations"], + deps = ["//third_party/java/error_prone:annotations"], ) gen_maven_artifact( @@ -90,7 +89,6 @@ gen_maven_artifact( "//java/dagger/internal/codegen/langmodel", "//java/dagger/internal/codegen/validation", "//java/dagger/internal/codegen/writing", - "//java/dagger/model:internal-proxies", ], artifact_target_maven_deps = [ "com.google.auto:auto-common", @@ -102,19 +100,18 @@ gen_maven_artifact( "com.google.guava:failureaccess", "com.google.guava:guava", "com.squareup:javapoet", - "javax.annotation:jsr250-api", "javax.inject:javax.inject", "net.ltgt.gradle.incap:incap", "org.checkerframework:checker-compat-qual", "org.jetbrains.kotlin:kotlin-stdlib", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", "org.jetbrains.kotlinx:kotlinx-metadata-jvm", ], javadoc_root_packages = ["dagger.internal.codegen"], # The javadocs should only include ComponentProcessor.java, since that is the only class used # externally. Specifically, ComponentProcessor.forTesting() is required for testing SPI plugins. javadoc_srcs = ["ComponentProcessor.java"], - shaded_deps = ["@maven//:com_google_auto_auto_common"], - shaded_rules = ["rule com.google.auto.common.** dagger.shaded.auto.common.@1"], + # The shaded deps are inherited from dagger spi. For the shaded rules see util/deploy-dagger.sh ) java_plugin( diff --git a/java/dagger/internal/codegen/ComponentHjarProcessingStep.java b/java/dagger/internal/codegen/ComponentHjarProcessingStep.java index 260f65ad9..a75c40b64 100644 --- a/java/dagger/internal/codegen/ComponentHjarProcessingStep.java +++ b/java/dagger/internal/codegen/ComponentHjarProcessingStep.java @@ -16,14 +16,15 @@ package dagger.internal.codegen; -import static com.google.auto.common.MoreElements.asType; import static com.google.common.collect.Sets.union; import static dagger.internal.codegen.base.ComponentAnnotation.rootComponentAnnotations; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.rootComponentCreatorAnnotations; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.rootComponentCreatorAnnotations; import static java.util.Collections.disjoint; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ComponentDescriptor; @@ -32,11 +33,8 @@ import dagger.internal.codegen.validation.ComponentCreatorValidator; import dagger.internal.codegen.validation.ComponentValidator; import dagger.internal.codegen.validation.TypeCheckingProcessingStep; import dagger.internal.codegen.validation.ValidationReport; -import java.lang.annotation.Annotation; import java.util.Set; -import javax.annotation.processing.Messager; import javax.inject.Inject; -import javax.lang.model.element.TypeElement; /** * A processing step that emits the API of a generated component, without any actual implementation. @@ -51,8 +49,8 @@ import javax.lang.model.element.TypeElement; * <p>The components emitted by this processing step include all of the API elements exposed by the * normal step. Method bodies are omitted as Turbine ignores them entirely. */ -final class ComponentHjarProcessingStep extends TypeCheckingProcessingStep<TypeElement> { - private final Messager messager; +final class ComponentHjarProcessingStep extends TypeCheckingProcessingStep<XTypeElement> { + private final XMessager messager; private final ComponentValidator componentValidator; private final ComponentCreatorValidator creatorValidator; private final ComponentDescriptorFactory componentDescriptorFactory; @@ -60,12 +58,11 @@ final class ComponentHjarProcessingStep extends TypeCheckingProcessingStep<TypeE @Inject ComponentHjarProcessingStep( - Messager messager, + XMessager messager, ComponentValidator componentValidator, ComponentCreatorValidator creatorValidator, ComponentDescriptorFactory componentDescriptorFactory, SourceFileGenerator<ComponentDescriptor> componentGenerator) { - super(MoreElements::asType); this.messager = messager; this.componentValidator = componentValidator; this.creatorValidator = creatorValidator; @@ -74,7 +71,7 @@ final class ComponentHjarProcessingStep extends TypeCheckingProcessingStep<TypeE } @Override - public Set<Class<? extends Annotation>> annotations() { + public Set<ClassName> annotationClassNames() { return union(rootComponentAnnotations(), rootComponentCreatorAnnotations()); } @@ -83,8 +80,7 @@ final class ComponentHjarProcessingStep extends TypeCheckingProcessingStep<TypeE // clause for any exception that's not TypeNotPresentException and ignore the component entirely // in that case. @Override - protected void process( - TypeElement element, ImmutableSet<Class<? extends Annotation>> annotations) { + protected void process(XTypeElement element, ImmutableSet<ClassName> annotations) { if (!disjoint(annotations, rootComponentAnnotations())) { processRootComponent(element); } @@ -93,8 +89,8 @@ final class ComponentHjarProcessingStep extends TypeCheckingProcessingStep<TypeE } } - private void processRootComponent(TypeElement element) { - ValidationReport<TypeElement> validationReport = componentValidator.validate(element); + private void processRootComponent(XTypeElement element) { + ValidationReport validationReport = componentValidator.validate(element); validationReport.printMessagesTo(messager); if (validationReport.isClean()) { componentGenerator.generate( @@ -102,7 +98,7 @@ final class ComponentHjarProcessingStep extends TypeCheckingProcessingStep<TypeE } } - private void processRootCreator(TypeElement creator) { - creatorValidator.validate(asType(creator)).printMessagesTo(messager); + private void processRootCreator(XTypeElement creator) { + creatorValidator.validate(creator).printMessagesTo(messager); } } diff --git a/java/dagger/internal/codegen/ComponentProcessingStep.java b/java/dagger/internal/codegen/ComponentProcessingStep.java index 3125ce7f4..6a1f09521 100644 --- a/java/dagger/internal/codegen/ComponentProcessingStep.java +++ b/java/dagger/internal/codegen/ComponentProcessingStep.java @@ -16,18 +16,19 @@ package dagger.internal.codegen; -import static com.google.auto.common.MoreElements.asType; import static com.google.common.collect.Sets.union; import static dagger.internal.codegen.base.ComponentAnnotation.allComponentAnnotations; import static dagger.internal.codegen.base.ComponentAnnotation.rootComponentAnnotations; import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotations; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.allCreatorAnnotations; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.allCreatorAnnotations; import static java.util.Collections.disjoint; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; -import com.google.auto.common.MoreElements; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.squareup.javapoet.ClassName; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.BindingGraphFactory; @@ -39,19 +40,15 @@ import dagger.internal.codegen.validation.ComponentDescriptorValidator; import dagger.internal.codegen.validation.ComponentValidator; import dagger.internal.codegen.validation.TypeCheckingProcessingStep; import dagger.internal.codegen.validation.ValidationReport; -import java.lang.annotation.Annotation; import java.util.Set; -import javax.annotation.processing.Messager; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; /** * A {@link ProcessingStep} that is responsible for dealing with a component or production component * as part of the {@link ComponentProcessor}. */ -final class ComponentProcessingStep extends TypeCheckingProcessingStep<TypeElement> { - private final Messager messager; +final class ComponentProcessingStep extends TypeCheckingProcessingStep<XTypeElement> { + private final XMessager messager; private final ComponentValidator componentValidator; private final ComponentCreatorValidator creatorValidator; private final ComponentDescriptorValidator componentDescriptorValidator; @@ -62,7 +59,7 @@ final class ComponentProcessingStep extends TypeCheckingProcessingStep<TypeEleme @Inject ComponentProcessingStep( - Messager messager, + XMessager messager, ComponentValidator componentValidator, ComponentCreatorValidator creatorValidator, ComponentDescriptorValidator componentDescriptorValidator, @@ -70,7 +67,6 @@ final class ComponentProcessingStep extends TypeCheckingProcessingStep<TypeEleme BindingGraphFactory bindingGraphFactory, SourceFileGenerator<BindingGraph> componentGenerator, BindingGraphValidator bindingGraphValidator) { - super(MoreElements::asType); this.messager = messager; this.componentValidator = componentValidator; this.creatorValidator = creatorValidator; @@ -82,13 +78,12 @@ final class ComponentProcessingStep extends TypeCheckingProcessingStep<TypeEleme } @Override - public Set<Class<? extends Annotation>> annotations() { + public Set<ClassName> annotationClassNames() { return union(allComponentAnnotations(), allCreatorAnnotations()); } @Override - protected void process( - TypeElement element, ImmutableSet<Class<? extends Annotation>> annotations) { + protected void process(XTypeElement element, ImmutableSet<ClassName> annotations) { if (!disjoint(annotations, rootComponentAnnotations())) { processRootComponent(element); } @@ -100,7 +95,7 @@ final class ComponentProcessingStep extends TypeCheckingProcessingStep<TypeEleme } } - private void processRootComponent(TypeElement component) { + private void processRootComponent(XTypeElement component) { if (!isComponentValid(component)) { return; } @@ -118,7 +113,7 @@ final class ComponentProcessingStep extends TypeCheckingProcessingStep<TypeEleme } } - private void processSubcomponent(TypeElement subcomponent) { + private void processSubcomponent(XTypeElement subcomponent) { if (!isComponentValid(subcomponent)) { return; } @@ -132,20 +127,20 @@ final class ComponentProcessingStep extends TypeCheckingProcessingStep<TypeEleme componentGenerator.generate(bindingGraph, messager); } - private void processCreator(Element creator) { - creatorValidator.validate(MoreElements.asType(creator)).printMessagesTo(messager); + private void processCreator(XTypeElement creator) { + creatorValidator.validate(creator).printMessagesTo(messager); } - private boolean isComponentValid(Element component) { - ValidationReport<?> report = componentValidator.validate(asType(component)); + private boolean isComponentValid(XTypeElement component) { + ValidationReport report = componentValidator.validate(component); report.printMessagesTo(messager); return report.isClean(); } @CanIgnoreReturnValue private boolean validateFullBindingGraph(ComponentDescriptor componentDescriptor) { - TypeElement component = componentDescriptor.typeElement(); - if (!bindingGraphValidator.shouldDoFullBindingGraphValidation(component)) { + if (!bindingGraphValidator.shouldDoFullBindingGraphValidation( + componentDescriptor.typeElement())) { return true; } BindingGraph fullBindingGraph = bindingGraphFactory.create(componentDescriptor, true); @@ -153,7 +148,7 @@ final class ComponentProcessingStep extends TypeCheckingProcessingStep<TypeEleme } private boolean isValid(ComponentDescriptor componentDescriptor) { - ValidationReport<TypeElement> componentDescriptorReport = + ValidationReport componentDescriptorReport = componentDescriptorValidator.validate(componentDescriptor); componentDescriptorReport.printMessagesTo(messager); return componentDescriptorReport.isClean(); diff --git a/java/dagger/internal/codegen/ComponentProcessor.java b/java/dagger/internal/codegen/ComponentProcessor.java index e392252a2..facb04d1f 100644 --- a/java/dagger/internal/codegen/ComponentProcessor.java +++ b/java/dagger/internal/codegen/ComponentProcessor.java @@ -18,18 +18,21 @@ package dagger.internal.codegen; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; -import com.google.auto.common.BasicAnnotationProcessor; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XProcessingEnvConfig; +import androidx.room.compiler.processing.XProcessingStep; +import androidx.room.compiler.processing.XRoundEnv; +import androidx.room.compiler.processing.compat.XConverters; +import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor; import com.google.auto.service.AutoService; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; import com.google.errorprone.annotations.CheckReturnValue; import dagger.BindsInstance; import dagger.Component; import dagger.Module; import dagger.Provides; -import dagger.internal.codegen.SpiModule.TestingPlugins; import dagger.internal.codegen.base.ClearableCache; import dagger.internal.codegen.base.SourceFileGenerationException; import dagger.internal.codegen.base.SourceFileGenerator; @@ -40,20 +43,20 @@ import dagger.internal.codegen.bindinggraphvalidation.BindingGraphValidationModu import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions; import dagger.internal.codegen.componentgenerator.ComponentGeneratorModule; -import dagger.internal.codegen.validation.BindingGraphPlugins; import dagger.internal.codegen.validation.BindingMethodProcessingStep; import dagger.internal.codegen.validation.BindingMethodValidatorsModule; import dagger.internal.codegen.validation.BindsInstanceProcessingStep; +import dagger.internal.codegen.validation.ExternalBindingGraphPlugins; import dagger.internal.codegen.validation.InjectBindingRegistryModule; import dagger.internal.codegen.validation.MonitoringModuleProcessingStep; import dagger.internal.codegen.validation.MultibindingAnnotationsProcessingStep; +import dagger.internal.codegen.validation.ValidationBindingGraphPlugins; import dagger.spi.BindingGraphPlugin; import java.util.Arrays; +import java.util.Map; import java.util.Optional; import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; -import javax.annotation.processing.RoundEnvironment; import javax.inject.Inject; import javax.inject.Singleton; import javax.lang.model.SourceVersion; @@ -67,21 +70,28 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; */ @IncrementalAnnotationProcessor(ISOLATING) @AutoService(Processor.class) -public class ComponentProcessor extends BasicAnnotationProcessor { +public class ComponentProcessor extends JavacBasicAnnotationProcessor { + private static XProcessingEnvConfig envConfig(Map<String, String> options) { + return new XProcessingEnvConfig.Builder().disableAnnotatedElementValidation(true).build(); + } + private final Optional<ImmutableSet<BindingGraphPlugin>> testingPlugins; @Inject InjectBindingRegistry injectBindingRegistry; @Inject SourceFileGenerator<ProvisionBinding> factoryGenerator; @Inject SourceFileGenerator<MembersInjectionBinding> membersInjectorGenerator; - @Inject ImmutableList<ProcessingStep> processingSteps; - @Inject BindingGraphPlugins bindingGraphPlugins; + @Inject ImmutableList<XProcessingStep> processingSteps; + @Inject ValidationBindingGraphPlugins validationBindingGraphPlugins; + @Inject ExternalBindingGraphPlugins externalBindingGraphPlugins; @Inject Set<ClearableCache> clearableCaches; public ComponentProcessor() { + super(ComponentProcessor::envConfig); this.testingPlugins = Optional.empty(); } private ComponentProcessor(Iterable<BindingGraphPlugin> testingPlugins) { + super(ComponentProcessor::envConfig); this.testingPlugins = Optional.of(ImmutableSet.copyOf(testingPlugins)); } @@ -104,27 +114,38 @@ public class ComponentProcessor extends BasicAnnotationProcessor { } @Override + public void initialize(XProcessingEnv env) { + ProcessorComponent.factory() + .create(env, testingPlugins.orElseGet(this::loadExternalPlugins)) + .inject(this); + } + + @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override - public Set<String> getSupportedOptions() { - return Sets.union( - ProcessingEnvironmentCompilerOptions.supportedOptions(), - bindingGraphPlugins.allSupportedOptions()) - .immutableCopy(); + public ImmutableSet<String> getSupportedOptions() { + return ImmutableSet.<String>builder() + .addAll(ProcessingEnvironmentCompilerOptions.supportedOptions()) + .addAll(validationBindingGraphPlugins.allSupportedOptions()) + .addAll(externalBindingGraphPlugins.allSupportedOptions()) + .build(); } @Override - protected Iterable<? extends ProcessingStep> initSteps() { - ProcessorComponent.factory().create(processingEnv, testingPlugins).inject(this); - - bindingGraphPlugins.initializePlugins(); + public Iterable<XProcessingStep> processingSteps() { + validationBindingGraphPlugins.initializePlugins(); + externalBindingGraphPlugins.initializePlugins(); return processingSteps; } + private ImmutableSet<BindingGraphPlugin> loadExternalPlugins() { + return ServiceLoaders.load(processingEnv, BindingGraphPlugin.class); + } + @Singleton @Component( modules = { @@ -136,7 +157,6 @@ public class ComponentProcessor extends BasicAnnotationProcessor { ProcessingRoundCacheModule.class, ProcessingStepsModule.class, SourceFileGeneratorsModule.class, - SpiModule.class }) interface ProcessorComponent { void inject(ComponentProcessor processor); @@ -149,15 +169,15 @@ public class ComponentProcessor extends BasicAnnotationProcessor { interface Factory { @CheckReturnValue ProcessorComponent create( - @BindsInstance ProcessingEnvironment processingEnv, - @BindsInstance @TestingPlugins Optional<ImmutableSet<BindingGraphPlugin>> testingPlugins); + @BindsInstance XProcessingEnv xProcessingEnv, + @BindsInstance ImmutableSet<BindingGraphPlugin> externalPlugins); } } @Module interface ProcessingStepsModule { @Provides - static ImmutableList<ProcessingStep> processingSteps( + static ImmutableList<XProcessingStep> processingSteps( MapKeyProcessingStep mapKeyProcessingStep, InjectProcessingStep injectProcessingStep, AssistedInjectProcessingStep assistedInjectProcessingStep, @@ -189,13 +209,14 @@ public class ComponentProcessor extends BasicAnnotationProcessor { } @Override - protected void postRound(RoundEnvironment roundEnv) { - if (!roundEnv.processingOver()) { + public void postRound(XProcessingEnv env, XRoundEnv roundEnv) { + // TODO(bcorso): Add a way to determine if processing is over without converting to Javac here. + if (!XConverters.toJavac(roundEnv).processingOver()) { try { injectBindingRegistry.generateSourcesForRequiredBindings( factoryGenerator, membersInjectorGenerator); } catch (SourceFileGenerationException e) { - e.printMessageTo(processingEnv.getMessager()); + e.printMessageTo(env.getMessager()); } } clearableCaches.forEach(ClearableCache::clearCache); diff --git a/java/dagger/internal/codegen/InjectProcessingStep.java b/java/dagger/internal/codegen/InjectProcessingStep.java index 537459235..a201afec0 100644 --- a/java/dagger/internal/codegen/InjectProcessingStep.java +++ b/java/dagger/internal/codegen/InjectProcessingStep.java @@ -16,73 +16,59 @@ package dagger.internal.codegen; -import com.google.auto.common.MoreElements; +import static androidx.room.compiler.processing.XElementKt.isConstructor; +import static androidx.room.compiler.processing.XElementKt.isField; +import static androidx.room.compiler.processing.XElementKt.isMethod; +import static dagger.internal.codegen.xprocessing.XElements.asConstructor; +import static dagger.internal.codegen.xprocessing.XElements.asField; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; + +import androidx.room.compiler.processing.XElement; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; -import dagger.assisted.AssistedInject; +import com.squareup.javapoet.ClassName; import dagger.internal.codegen.binding.InjectBindingRegistry; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.validation.TypeCheckingProcessingStep; -import java.lang.annotation.Annotation; import java.util.Set; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementVisitor; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.util.ElementKindVisitor8; /** * An annotation processor for generating Dagger implementation code based on the {@link Inject} * annotation. */ // TODO(gak): add some error handling for bad source files -final class InjectProcessingStep extends TypeCheckingProcessingStep<Element> { - private final ElementVisitor<Void, Void> visitor; - private final Set<Element> processedElements = Sets.newLinkedHashSet(); +// TODO(bcorso): Add support in TypeCheckingProcessingStep to perform custom validation and use +// SuperficialInjectValidator rather than SuperficialValidator. +final class InjectProcessingStep extends TypeCheckingProcessingStep<XElement> { + private final InjectBindingRegistry injectBindingRegistry; + private final Set<XElement> processedElements = Sets.newHashSet(); @Inject InjectProcessingStep(InjectBindingRegistry injectBindingRegistry) { - super(e -> e); - this.visitor = - new ElementKindVisitor8<Void, Void>() { - @Override - public Void visitExecutableAsConstructor( - ExecutableElement constructorElement, Void aVoid) { - injectBindingRegistry.tryRegisterConstructor(constructorElement); - return null; - } - - @Override - public Void visitVariableAsField(VariableElement fieldElement, Void aVoid) { - injectBindingRegistry.tryRegisterMembersInjectedType( - MoreElements.asType(fieldElement.getEnclosingElement())); - return null; - } - - @Override - public Void visitExecutableAsMethod(ExecutableElement methodElement, Void aVoid) { - injectBindingRegistry.tryRegisterMembersInjectedType( - MoreElements.asType(methodElement.getEnclosingElement())); - return null; - } - }; + this.injectBindingRegistry = injectBindingRegistry; } @Override - public Set<Class<? extends Annotation>> annotations() { - return ImmutableSet.of(Inject.class, AssistedInject.class); + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.INJECT, TypeNames.INJECT_JAVAX, TypeNames.ASSISTED_INJECT); } @Override - protected void process( - Element injectElement, ImmutableSet<Class<? extends Annotation>> annotations) { + protected void process(XElement injectElement, ImmutableSet<ClassName> annotations) { // Only process an element once to avoid getting duplicate errors when an element is annotated // with multiple inject annotations. if (processedElements.contains(injectElement)) { return; } - injectElement.accept(visitor, null); + if (isConstructor(injectElement)) { + injectBindingRegistry.tryRegisterInjectConstructor(asConstructor(injectElement)); + } else if (isField(injectElement)) { + injectBindingRegistry.tryRegisterInjectField(asField(injectElement)); + } else if (isMethod(injectElement)) { + injectBindingRegistry.tryRegisterInjectMethod(asMethod(injectElement)); + } processedElements.add(injectElement); } diff --git a/java/dagger/internal/codegen/MapKeyProcessingStep.java b/java/dagger/internal/codegen/MapKeyProcessingStep.java index 50693da14..4d1f3b822 100644 --- a/java/dagger/internal/codegen/MapKeyProcessingStep.java +++ b/java/dagger/internal/codegen/MapKeyProcessingStep.java @@ -17,77 +17,64 @@ package dagger.internal.codegen; import static dagger.internal.codegen.binding.MapKeys.getUnwrappedMapKeyType; -import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.MapKey; -import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.validation.MapKeyValidator; import dagger.internal.codegen.validation.TypeCheckingProcessingStep; import dagger.internal.codegen.validation.ValidationReport; import dagger.internal.codegen.writing.AnnotationCreatorGenerator; import dagger.internal.codegen.writing.UnwrappedMapKeyGenerator; -import java.lang.annotation.Annotation; -import java.util.Set; -import javax.annotation.processing.Messager; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; /** * The annotation processor responsible for validating the mapKey annotation and auto-generate * implementation of annotations marked with {@link MapKey @MapKey} where necessary. */ -final class MapKeyProcessingStep extends TypeCheckingProcessingStep<TypeElement> { - private final Messager messager; - private final DaggerTypes types; +final class MapKeyProcessingStep extends TypeCheckingProcessingStep<XTypeElement> { + private final XMessager messager; private final MapKeyValidator mapKeyValidator; private final AnnotationCreatorGenerator annotationCreatorGenerator; private final UnwrappedMapKeyGenerator unwrappedMapKeyGenerator; @Inject MapKeyProcessingStep( - Messager messager, - DaggerTypes types, + XMessager messager, MapKeyValidator mapKeyValidator, AnnotationCreatorGenerator annotationCreatorGenerator, UnwrappedMapKeyGenerator unwrappedMapKeyGenerator) { - super(MoreElements::asType); this.messager = messager; - this.types = types; this.mapKeyValidator = mapKeyValidator; this.annotationCreatorGenerator = annotationCreatorGenerator; this.unwrappedMapKeyGenerator = unwrappedMapKeyGenerator; } @Override - public Set<Class<? extends Annotation>> annotations() { - return ImmutableSet.<Class<? extends Annotation>>of(MapKey.class); + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.MAP_KEY); } @Override - protected void process( - TypeElement mapKeyAnnotationType, ImmutableSet<Class<? extends Annotation>> annotations) { - ValidationReport<Element> mapKeyReport = mapKeyValidator.validate(mapKeyAnnotationType); + protected void process(XTypeElement mapAnnotation, ImmutableSet<ClassName> annotations) { + ValidationReport mapKeyReport = mapKeyValidator.validate(mapAnnotation); mapKeyReport.printMessagesTo(messager); if (mapKeyReport.isClean()) { - MapKey mapkey = mapKeyAnnotationType.getAnnotation(MapKey.class); - if (!mapkey.unwrapValue()) { - annotationCreatorGenerator.generate(mapKeyAnnotationType, messager); - } else if (unwrappedValueKind(mapKeyAnnotationType).equals(ANNOTATION_TYPE)) { - unwrappedMapKeyGenerator.generate(mapKeyAnnotationType, messager); + if (!mapAnnotation.getAnnotation(TypeNames.MAP_KEY).getAsBoolean("unwrapValue")) { + annotationCreatorGenerator.generate(mapAnnotation, messager); + } else if (isAnnotationType(getUnwrappedMapKeyType(mapAnnotation.getType()))) { + unwrappedMapKeyGenerator.generate(mapAnnotation, messager); } } } - private ElementKind unwrappedValueKind(TypeElement mapKeyAnnotationType) { - DeclaredType unwrappedMapKeyType = - getUnwrappedMapKeyType(MoreTypes.asDeclared(mapKeyAnnotationType.asType()), types); - return unwrappedMapKeyType.asElement().getKind(); + private boolean isAnnotationType(XType type) { + return isDeclared(type) && type.getTypeElement().isAnnotationClass(); } } diff --git a/java/dagger/internal/codegen/ModuleProcessingStep.java b/java/dagger/internal/codegen/ModuleProcessingStep.java index 2bf33545c..811d35a10 100644 --- a/java/dagger/internal/codegen/ModuleProcessingStep.java +++ b/java/dagger/internal/codegen/ModuleProcessingStep.java @@ -16,70 +16,59 @@ package dagger.internal.codegen; -import static com.google.auto.common.MoreElements.isAnnotationPresent; -import static javax.lang.model.util.ElementFilter.methodsIn; -import static javax.lang.model.util.ElementFilter.typesIn; +import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; -import com.google.auto.common.MoreElements; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; -import dagger.Binds; -import dagger.Module; -import dagger.Provides; +import com.squareup.javapoet.ClassName; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.BindingFactory; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.DelegateDeclaration; -import dagger.internal.codegen.binding.DelegateDeclaration.Factory; import dagger.internal.codegen.binding.ProductionBinding; import dagger.internal.codegen.binding.ProvisionBinding; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.validation.ModuleValidator; import dagger.internal.codegen.validation.TypeCheckingProcessingStep; import dagger.internal.codegen.validation.ValidationReport; import dagger.internal.codegen.writing.InaccessibleMapKeyProxyGenerator; import dagger.internal.codegen.writing.ModuleGenerator; -import dagger.producers.ProducerModule; -import dagger.producers.Produces; -import java.lang.annotation.Annotation; -import java.util.List; +import java.util.Map; import java.util.Set; -import javax.annotation.processing.Messager; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; /** * A {@link ProcessingStep} that validates module classes and generates factories for binding * methods. */ -final class ModuleProcessingStep extends TypeCheckingProcessingStep<TypeElement> { - private final Messager messager; +final class ModuleProcessingStep extends TypeCheckingProcessingStep<XTypeElement> { + private final XMessager messager; private final ModuleValidator moduleValidator; private final BindingFactory bindingFactory; private final SourceFileGenerator<ProvisionBinding> factoryGenerator; private final SourceFileGenerator<ProductionBinding> producerFactoryGenerator; - private final SourceFileGenerator<TypeElement> moduleConstructorProxyGenerator; + private final SourceFileGenerator<XTypeElement> moduleConstructorProxyGenerator; private final InaccessibleMapKeyProxyGenerator inaccessibleMapKeyProxyGenerator; private final DelegateDeclaration.Factory delegateDeclarationFactory; - private final KotlinMetadataUtil metadataUtil; - private final Set<TypeElement> processedModuleElements = Sets.newLinkedHashSet(); + private final Set<XTypeElement> processedModuleElements = Sets.newLinkedHashSet(); @Inject ModuleProcessingStep( - Messager messager, + XMessager messager, ModuleValidator moduleValidator, BindingFactory bindingFactory, SourceFileGenerator<ProvisionBinding> factoryGenerator, SourceFileGenerator<ProductionBinding> producerFactoryGenerator, - @ModuleGenerator SourceFileGenerator<TypeElement> moduleConstructorProxyGenerator, + @ModuleGenerator SourceFileGenerator<XTypeElement> moduleConstructorProxyGenerator, InaccessibleMapKeyProxyGenerator inaccessibleMapKeyProxyGenerator, - Factory delegateDeclarationFactory, - KotlinMetadataUtil metadataUtil) { - super(MoreElements::asType); + DelegateDeclaration.Factory delegateDeclarationFactory) { this.messager = messager; this.moduleValidator = moduleValidator; this.bindingFactory = bindingFactory; @@ -88,54 +77,57 @@ final class ModuleProcessingStep extends TypeCheckingProcessingStep<TypeElement> this.moduleConstructorProxyGenerator = moduleConstructorProxyGenerator; this.inaccessibleMapKeyProxyGenerator = inaccessibleMapKeyProxyGenerator; this.delegateDeclarationFactory = delegateDeclarationFactory; - this.metadataUtil = metadataUtil; } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(Module.class, ProducerModule.class); + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.MODULE, TypeNames.PRODUCER_MODULE); } @Override - public ImmutableSet<Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { - List<TypeElement> modules = typesIn(elementsByAnnotation.values()); - moduleValidator.addKnownModules(modules); - return super.process(elementsByAnnotation); + public ImmutableSet<XElement> process( + XProcessingEnv env, Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) { + moduleValidator.addKnownModules( + elementsByAnnotation.values().stream() + .flatMap(Set::stream) + // This cast is safe because @Module has @Target(ElementType.TYPE) + .map(XTypeElement.class::cast) + .collect(toImmutableSet())); + return super.process(env, elementsByAnnotation); } @Override - protected void process( - TypeElement module, ImmutableSet<Class<? extends Annotation>> annotations) { + protected void process(XTypeElement module, ImmutableSet<ClassName> annotations) { if (processedModuleElements.contains(module)) { return; } // For backwards compatibility, we allow a companion object to be annotated with @Module even // though it's no longer required. However, we skip processing the companion object itself // because it will now be processed when processing the companion object's enclosing class. - if (metadataUtil.isCompanionObjectClass(module)) { + if (module.isCompanionObject()) { // TODO(danysantiago): Be strict about annotating companion objects with @Module, // i.e. tell user to annotate parent instead. return; } - ValidationReport<TypeElement> report = moduleValidator.validate(module); + ValidationReport report = moduleValidator.validate(module); report.printMessagesTo(messager); if (report.isClean()) { generateForMethodsIn(module); - if (metadataUtil.hasEnclosedCompanionObject(module)) { - generateForMethodsIn(metadataUtil.getEnclosedCompanionObject(module)); - } + module.getEnclosedTypeElements().stream() + .filter(XTypeElement::isCompanionObject) + .collect(toOptional()) + .ifPresent(this::generateForMethodsIn); } processedModuleElements.add(module); } - private void generateForMethodsIn(TypeElement module) { - for (ExecutableElement method : methodsIn(module.getEnclosedElements())) { - if (isAnnotationPresent(method, Provides.class)) { + private void generateForMethodsIn(XTypeElement module) { + for (XMethodElement method : module.getDeclaredMethods()) { + if (method.hasAnnotation(TypeNames.PROVIDES)) { generate(factoryGenerator, bindingFactory.providesMethodBinding(method, module)); - } else if (isAnnotationPresent(method, Produces.class)) { + } else if (method.hasAnnotation(TypeNames.PRODUCES)) { generate(producerFactoryGenerator, bindingFactory.producesMethodBinding(method, module)); - } else if (isAnnotationPresent(method, Binds.class)) { + } else if (method.hasAnnotation(TypeNames.BINDS)) { inaccessibleMapKeyProxyGenerator.generate(bindsMethodBinding(module, method), messager); } } @@ -148,7 +140,7 @@ final class ModuleProcessingStep extends TypeCheckingProcessingStep<TypeElement> inaccessibleMapKeyProxyGenerator.generate(binding, messager); } - private ContributionBinding bindsMethodBinding(TypeElement module, ExecutableElement method) { + private ContributionBinding bindsMethodBinding(XTypeElement module, XMethodElement method) { return bindingFactory.unresolvedDelegateBinding( delegateDeclarationFactory.create(method, module)); } diff --git a/java/dagger/internal/codegen/ProcessingEnvironmentModule.java b/java/dagger/internal/codegen/ProcessingEnvironmentModule.java index 120714b33..1561c7c88 100644 --- a/java/dagger/internal/codegen/ProcessingEnvironmentModule.java +++ b/java/dagger/internal/codegen/ProcessingEnvironmentModule.java @@ -16,28 +16,27 @@ package dagger.internal.codegen; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.compat.XConverters; import com.google.googlejavaformat.java.filer.FormattingFiler; import dagger.Binds; import dagger.Module; import dagger.Provides; import dagger.Reusable; -import dagger.internal.codegen.SpiModule.ProcessorClassLoader; import dagger.internal.codegen.base.ClearableCache; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions; import dagger.internal.codegen.compileroption.ProcessingOptions; import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.langmodel.DaggerTypes; import dagger.multibindings.IntoSet; -import dagger.spi.BindingGraphPlugin; import java.util.Map; -import javax.annotation.processing.Filer; -import javax.annotation.processing.Messager; -import javax.annotation.processing.ProcessingEnvironment; import javax.inject.Singleton; import javax.lang.model.SourceVersion; -import javax.lang.model.util.Types; -/** Bindings that depend on the {@link ProcessingEnvironment}. */ +/** Bindings that depend on the {@link XProcessingEnv}. */ @Module interface ProcessingEnvironmentModule { @Binds @@ -47,47 +46,43 @@ interface ProcessingEnvironmentModule { @Provides @ProcessingOptions - static Map<String, String> processingOptions(ProcessingEnvironment processingEnvironment) { - return processingEnvironment.getOptions(); + static Map<String, String> processingOptions(XProcessingEnv xProcessingEnv) { + return xProcessingEnv.getOptions(); } @Provides - static Messager messager(ProcessingEnvironment processingEnvironment) { - return processingEnvironment.getMessager(); + static XMessager messager(XProcessingEnv xProcessingEnv) { + return xProcessingEnv.getMessager(); } @Provides - static Filer filer(CompilerOptions compilerOptions, ProcessingEnvironment processingEnvironment) { - if (compilerOptions.headerCompilation() || !compilerOptions.formatGeneratedSource()) { - return processingEnvironment.getFiler(); - } else { - return new FormattingFiler(processingEnvironment.getFiler()); - } + static XFiler filer(CompilerOptions compilerOptions, XProcessingEnv xProcessingEnv) { + return compilerOptions.headerCompilation() || !compilerOptions.formatGeneratedSource() + ? xProcessingEnv.getFiler() + : XConverters.toXProcessing( + new FormattingFiler(XConverters.toJavac(xProcessingEnv.getFiler())), xProcessingEnv); } @Provides - static Types types(ProcessingEnvironment processingEnvironment) { - return processingEnvironment.getTypeUtils(); + static SourceVersion sourceVersion(XProcessingEnv xProcessingEnv) { + return XConverters.toJavac(xProcessingEnv).getSourceVersion(); } @Provides - static SourceVersion sourceVersion(ProcessingEnvironment processingEnvironment) { - return processingEnvironment.getSourceVersion(); + @Singleton + static DaggerElements daggerElements(XProcessingEnv xProcessingEnv) { + return new DaggerElements( + XConverters.toJavac(xProcessingEnv).getElementUtils(), + XConverters.toJavac(xProcessingEnv).getTypeUtils()); } @Provides @Singleton - static DaggerElements daggerElements(ProcessingEnvironment processingEnvironment) { - return new DaggerElements(processingEnvironment); + static DaggerTypes daggerTypes(XProcessingEnv xProcessingEnv, DaggerElements elements) { + return new DaggerTypes(XConverters.toJavac(xProcessingEnv).getTypeUtils(), elements); } @Binds @IntoSet ClearableCache daggerElementAsClearableCache(DaggerElements elements); - - @Provides - @ProcessorClassLoader - static ClassLoader processorClassloader(ProcessingEnvironment processingEnvironment) { - return BindingGraphPlugin.class.getClassLoader(); - } } diff --git a/java/dagger/internal/codegen/ProcessingRoundCacheModule.java b/java/dagger/internal/codegen/ProcessingRoundCacheModule.java index 2373cd291..0d9b02a3b 100644 --- a/java/dagger/internal/codegen/ProcessingRoundCacheModule.java +++ b/java/dagger/internal/codegen/ProcessingRoundCacheModule.java @@ -26,6 +26,7 @@ import dagger.internal.codegen.validation.AnyBindingMethodValidator; import dagger.internal.codegen.validation.ComponentCreatorValidator; import dagger.internal.codegen.validation.ComponentValidator; import dagger.internal.codegen.validation.InjectValidator; +import dagger.internal.codegen.validation.SuperficialValidator; import dagger.multibindings.IntoSet; /** @@ -61,4 +62,8 @@ interface ProcessingRoundCacheModule { @Binds @IntoSet ClearableCache kotlinMetadata(KotlinMetadataFactory cache); + + @Binds + @IntoSet + ClearableCache superficialValidator(SuperficialValidator cache); } diff --git a/java/dagger/internal/codegen/ServiceLoaders.java b/java/dagger/internal/codegen/ServiceLoaders.java new file mode 100644 index 000000000..0f9b3555e --- /dev/null +++ b/java/dagger/internal/codegen/ServiceLoaders.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 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 com.google.common.collect.ImmutableSet; +import java.util.ServiceLoader; +import javax.annotation.processing.ProcessingEnvironment; + +/** A class that loads services for the {@link ComponentProcessor}. */ +final class ServiceLoaders { + + private ServiceLoaders() {} + + static <T> ImmutableSet<T> load(ProcessingEnvironment processingEnvironment, Class<T> clazz) { + return ImmutableSet.copyOf( + ServiceLoader.load(clazz, classloaderFor(processingEnvironment, clazz))); + } + + private static ClassLoader classloaderFor( + ProcessingEnvironment processingEnvironment, Class<?> clazz) { + return clazz.getClassLoader(); + } +} diff --git a/java/dagger/internal/codegen/SourceFileGeneratorsModule.java b/java/dagger/internal/codegen/SourceFileGeneratorsModule.java index 426afae37..1ba7351bb 100644 --- a/java/dagger/internal/codegen/SourceFileGeneratorsModule.java +++ b/java/dagger/internal/codegen/SourceFileGeneratorsModule.java @@ -16,6 +16,7 @@ package dagger.internal.codegen; +import androidx.room.compiler.processing.XTypeElement; import dagger.Module; import dagger.Provides; import dagger.internal.codegen.base.SourceFileGenerator; @@ -29,7 +30,6 @@ import dagger.internal.codegen.writing.MembersInjectorGenerator; import dagger.internal.codegen.writing.ModuleGenerator; import dagger.internal.codegen.writing.ModuleProxies.ModuleConstructorProxyGenerator; import dagger.internal.codegen.writing.ProducerFactoryGenerator; -import javax.lang.model.element.TypeElement; @Module abstract class SourceFileGeneratorsModule { @@ -54,7 +54,7 @@ abstract class SourceFileGeneratorsModule { @Provides @ModuleGenerator - static SourceFileGenerator<TypeElement> moduleConstructorProxyGenerator( + static SourceFileGenerator<XTypeElement> moduleConstructorProxyGenerator( ModuleConstructorProxyGenerator generator, CompilerOptions compilerOptions) { return hjarWrapper(generator, compilerOptions); } diff --git a/java/dagger/internal/codegen/SpiModule.java b/java/dagger/internal/codegen/SpiModule.java deleted file mode 100644 index a4f537bff..000000000 --- a/java/dagger/internal/codegen/SpiModule.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2018 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 java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import com.google.common.collect.ImmutableSet; -import dagger.Module; -import dagger.Provides; -import dagger.internal.codegen.validation.BindingGraphValidator; -import dagger.spi.BindingGraphPlugin; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import java.util.Optional; -import java.util.ServiceLoader; -import javax.inject.Qualifier; -import javax.inject.Singleton; - -/** Contains the bindings for {@link BindingGraphValidator} from external SPI providers. */ -@Module -abstract class SpiModule { - private SpiModule() {} - - @Provides - @Singleton - static ImmutableSet<BindingGraphPlugin> externalPlugins( - @TestingPlugins Optional<ImmutableSet<BindingGraphPlugin>> testingPlugins, - @ProcessorClassLoader ClassLoader processorClassLoader) { - return testingPlugins.orElseGet( - () -> - ImmutableSet.copyOf( - ServiceLoader.load(BindingGraphPlugin.class, processorClassLoader))); - } - - @Qualifier - @Retention(RUNTIME) - @Target({FIELD, PARAMETER, METHOD}) - @interface TestingPlugins {} - - @Qualifier - @Retention(RUNTIME) - @Target({PARAMETER, METHOD}) - @interface ProcessorClassLoader {} -} diff --git a/java/dagger/internal/codegen/base/BUILD b/java/dagger/internal/codegen/base/BUILD index 0bcbb128e..69e84d037 100644 --- a/java/dagger/internal/codegen/base/BUILD +++ b/java/dagger/internal/codegen/base/BUILD @@ -35,20 +35,20 @@ java_library( tags = ["maven:merged"], exports = [":shared"], deps = [ - ":shared", "//java/dagger:core", + "//java/dagger/internal/codegen/compileroption", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/javapoet", "//java/dagger/internal/codegen/langmodel", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", - "//java/dagger/producers", + "//java/dagger/internal/codegen/xprocessing", "//java/dagger/spi", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/jsr330_inject", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/graph", + "//third_party/java/javapoet", + "//third_party/java/jsr330_inject", ], ) @@ -59,8 +59,8 @@ java_library( tags = ["maven:merged"], deps = [ "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", ], ) diff --git a/java/dagger/internal/codegen/base/ComponentAnnotation.java b/java/dagger/internal/codegen/base/ComponentAnnotation.java index b70ef54bb..e0871eb7f 100644 --- a/java/dagger/internal/codegen/base/ComponentAnnotation.java +++ b/java/dagger/internal/codegen/base/ComponentAnnotation.java @@ -16,31 +16,22 @@ package dagger.internal.codegen.base; -import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; -import static com.google.auto.common.MoreElements.asType; -import static com.google.auto.common.MoreTypes.asTypeElements; -import static com.google.auto.common.MoreTypes.isTypeOf; -import static dagger.internal.codegen.base.MoreAnnotationValues.asAnnotationValues; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; -import static dagger.internal.codegen.javapoet.TypeNames.PRODUCER_MODULE; -import static dagger.internal.codegen.langmodel.DaggerElements.getAnyAnnotation; - +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static dagger.internal.codegen.xprocessing.XAnnotations.getClassName; +import static dagger.internal.codegen.xprocessing.XElements.getAnyAnnotation; + +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; -import dagger.Component; -import dagger.Subcomponent; -import dagger.producers.ProductionComponent; -import dagger.producers.ProductionSubcomponent; -import java.lang.annotation.Annotation; +import dagger.internal.codegen.javapoet.TypeNames; import java.util.Collection; import java.util.Optional; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; /** * A {@code @Component}, {@code @Subcomponent}, {@code @ProductionComponent}, or @@ -48,74 +39,82 @@ import javax.lang.model.type.TypeMirror; * annotation that is being treated as a component annotation when validating full binding graphs * for modules. */ +@AutoValue public abstract class ComponentAnnotation { /** The root component annotation types. */ - private static final ImmutableSet<Class<? extends Annotation>> ROOT_COMPONENT_ANNOTATIONS = - ImmutableSet.of(Component.class, ProductionComponent.class); + private static final ImmutableSet<ClassName> ROOT_COMPONENT_ANNOTATIONS = + ImmutableSet.of(TypeNames.COMPONENT, TypeNames.PRODUCTION_COMPONENT); /** The subcomponent annotation types. */ - private static final ImmutableSet<Class<? extends Annotation>> SUBCOMPONENT_ANNOTATIONS = - ImmutableSet.of(Subcomponent.class, ProductionSubcomponent.class); - - // TODO(erichang): Move ComponentCreatorAnnotation into /base and use that here? - /** The component/subcomponent creator annotation types. */ - private static final ImmutableSet<Class<? extends Annotation>> CREATOR_ANNOTATIONS = - ImmutableSet.of( - Component.Builder.class, - Component.Factory.class, - ProductionComponent.Builder.class, - ProductionComponent.Factory.class, - Subcomponent.Builder.class, - Subcomponent.Factory.class, - ProductionSubcomponent.Builder.class, - ProductionSubcomponent.Factory.class); + private static final ImmutableSet<ClassName> SUBCOMPONENT_ANNOTATIONS = + ImmutableSet.of(TypeNames.SUBCOMPONENT, TypeNames.PRODUCTION_SUBCOMPONENT); /** All component annotation types. */ - private static final ImmutableSet<Class<? extends Annotation>> ALL_COMPONENT_ANNOTATIONS = - ImmutableSet.<Class<? extends Annotation>>builder() - .addAll(ROOT_COMPONENT_ANNOTATIONS) - .addAll(SUBCOMPONENT_ANNOTATIONS) - .build(); + private static final ImmutableSet<ClassName> ALL_COMPONENT_ANNOTATIONS = + ImmutableSet.<ClassName>builder() + .addAll(ROOT_COMPONENT_ANNOTATIONS) + .addAll(SUBCOMPONENT_ANNOTATIONS) + .build(); /** All component and creator annotation types. */ - private static final ImmutableSet<Class<? extends Annotation>> - ALL_COMPONENT_AND_CREATOR_ANNOTATIONS = ImmutableSet.<Class<? extends Annotation>>builder() - .addAll(ALL_COMPONENT_ANNOTATIONS) - .addAll(CREATOR_ANNOTATIONS) - .build(); + private static final ImmutableSet<ClassName> ALL_COMPONENT_AND_CREATOR_ANNOTATIONS = + ImmutableSet.<ClassName>builder() + .addAll(ALL_COMPONENT_ANNOTATIONS) + .addAll(ComponentCreatorAnnotation.allCreatorAnnotations()) + .build(); + + /** All production annotation types. */ + private static final ImmutableSet<ClassName> PRODUCTION_ANNOTATIONS = + ImmutableSet.of( + TypeNames.PRODUCTION_COMPONENT, + TypeNames.PRODUCTION_SUBCOMPONENT, + TypeNames.PRODUCER_MODULE); + + private XAnnotation annotation; /** The annotation itself. */ - public abstract AnnotationMirror annotation(); + public final XAnnotation annotation() { + return annotation; + } + + /** Returns the {@link ClassName} name of the annotation. */ + public abstract ClassName className(); /** The simple name of the annotation type. */ - public String simpleName() { - return MoreAnnotationMirrors.simpleName(annotation()).toString(); + public final String simpleName() { + return className().simpleName(); } /** * Returns {@code true} if the annotation is a {@code @Subcomponent} or * {@code @ProductionSubcomponent}. */ - public abstract boolean isSubcomponent(); + public final boolean isSubcomponent() { + return SUBCOMPONENT_ANNOTATIONS.contains(className()); + } /** * Returns {@code true} if the annotation is a {@code @ProductionComponent}, * {@code @ProductionSubcomponent}, or {@code @ProducerModule}. */ - public abstract boolean isProduction(); + public final boolean isProduction() { + return PRODUCTION_ANNOTATIONS.contains(className()); + } /** * Returns {@code true} if the annotation is a real component annotation and not a module * annotation. */ - public abstract boolean isRealComponent(); - - /** The values listed as {@code dependencies}. */ - public abstract ImmutableList<AnnotationValue> dependencyValues(); + public final boolean isRealComponent() { + return ALL_COMPONENT_ANNOTATIONS.contains(className()); + } /** The types listed as {@code dependencies}. */ - public ImmutableList<TypeMirror> dependencyTypes() { - return dependencyValues().stream().map(MoreAnnotationValues::asType).collect(toImmutableList()); + @Memoized + public ImmutableList<XType> dependencyTypes() { + return isRootComponent() + ? ImmutableList.copyOf(annotation.getAsTypeList("dependencies")) + : ImmutableList.of(); } /** @@ -123,229 +122,100 @@ public abstract class ComponentAnnotation { * * @throws IllegalArgumentException if any of {@link #dependencyTypes()} are error types */ - public ImmutableList<TypeElement> dependencies() { - return asTypeElements(dependencyTypes()).asList(); - } - - /** The values listed as {@code modules}. */ - public abstract ImmutableList<AnnotationValue> moduleValues(); - - /** The types listed as {@code modules}. */ - public ImmutableList<TypeMirror> moduleTypes() { - return moduleValues().stream().map(MoreAnnotationValues::asType).collect(toImmutableList()); + @Memoized + public ImmutableSet<XTypeElement> dependencies() { + return dependencyTypes().stream().map(XType::getTypeElement).collect(toImmutableSet()); } /** * The types listed as {@code modules}. * - * @throws IllegalArgumentException if any of {@link #moduleTypes()} are error types + * @throws IllegalArgumentException if any module is an error type. */ - public ImmutableSet<TypeElement> modules() { - return asTypeElements(moduleTypes()); + @Memoized + public ImmutableSet<XTypeElement> modules() { + return annotation.getAsTypeList(isRealComponent() ? "modules" : "includes").stream() + .map(XType::getTypeElement) + .collect(toImmutableSet()); } - protected final ImmutableList<AnnotationValue> getAnnotationValues(String parameterName) { - return asAnnotationValues(getAnnotationValue(annotation(), parameterName)); + private final boolean isRootComponent() { + return ROOT_COMPONENT_ANNOTATIONS.contains(className()); } /** * Returns an object representing a root component annotation, not a subcomponent annotation, if * one is present on {@code typeElement}. */ - public static Optional<ComponentAnnotation> rootComponentAnnotation(TypeElement typeElement) { - return anyComponentAnnotation(typeElement, ROOT_COMPONENT_ANNOTATIONS); + public static Optional<ComponentAnnotation> rootComponentAnnotation( + XTypeElement typeElement, DaggerSuperficialValidation superficialValidation) { + return anyComponentAnnotation(typeElement, ROOT_COMPONENT_ANNOTATIONS, superficialValidation); } /** * Returns an object representing a subcomponent annotation, if one is present on {@code * typeElement}. */ - public static Optional<ComponentAnnotation> subcomponentAnnotation(TypeElement typeElement) { - return anyComponentAnnotation(typeElement, SUBCOMPONENT_ANNOTATIONS); + public static Optional<ComponentAnnotation> subcomponentAnnotation( + XTypeElement typeElement, DaggerSuperficialValidation superficialValidation) { + return anyComponentAnnotation(typeElement, SUBCOMPONENT_ANNOTATIONS, superficialValidation); } /** * Returns an object representing a root component or subcomponent annotation, if one is present * on {@code typeElement}. */ - public static Optional<ComponentAnnotation> anyComponentAnnotation(TypeElement typeElement) { - return anyComponentAnnotation(typeElement, ALL_COMPONENT_ANNOTATIONS); + public static Optional<ComponentAnnotation> anyComponentAnnotation( + XElement element, DaggerSuperficialValidation superficialValidation) { + return anyComponentAnnotation(element, ALL_COMPONENT_ANNOTATIONS, superficialValidation); } private static Optional<ComponentAnnotation> anyComponentAnnotation( - TypeElement typeElement, Collection<Class<? extends Annotation>> annotations) { - return getAnyAnnotation(typeElement, annotations).map(ComponentAnnotation::componentAnnotation); + XElement element, + Collection<ClassName> annotations, + DaggerSuperficialValidation superficialValidation) { + return getAnyAnnotation(element, annotations) + .map( + annotation -> { + superficialValidation.validateAnnotationOf(element, annotation); + return create(annotation); + }); } /** Returns {@code true} if the argument is a component annotation. */ - public static boolean isComponentAnnotation(AnnotationMirror annotation) { - return ALL_COMPONENT_ANNOTATIONS.stream() - .anyMatch(annotationClass -> isTypeOf(annotationClass, annotation.getAnnotationType())); - } - - /** Creates an object representing a component or subcomponent annotation. */ - public static ComponentAnnotation componentAnnotation(AnnotationMirror annotation) { - RealComponentAnnotation.Builder annotationBuilder = - RealComponentAnnotation.builder().annotation(annotation); - - if (isTypeOf(Component.class, annotation.getAnnotationType())) { - return annotationBuilder.isProduction(false).isSubcomponent(false).build(); - } - if (isTypeOf(Subcomponent.class, annotation.getAnnotationType())) { - return annotationBuilder.isProduction(false).isSubcomponent(true).build(); - } - if (isTypeOf(ProductionComponent.class, annotation.getAnnotationType())) { - return annotationBuilder.isProduction(true).isSubcomponent(false).build(); - } - if (isTypeOf(ProductionSubcomponent.class, annotation.getAnnotationType())) { - return annotationBuilder.isProduction(true).isSubcomponent(true).build(); - } - throw new IllegalArgumentException( - annotation - + " must be a Component, Subcomponent, ProductionComponent, " - + "or ProductionSubcomponent annotation"); + public static boolean isComponentAnnotation(XAnnotation annotation) { + return ALL_COMPONENT_ANNOTATIONS.contains(getClassName(annotation)); } /** Creates a fictional component annotation representing a module. */ public static ComponentAnnotation fromModuleAnnotation(ModuleAnnotation moduleAnnotation) { - return new AutoValue_ComponentAnnotation_FictionalComponentAnnotation(moduleAnnotation); + return create(moduleAnnotation.annotation()); + } + + private static ComponentAnnotation create(XAnnotation annotation) { + ComponentAnnotation componentAnnotation = + new AutoValue_ComponentAnnotation(getClassName(annotation)); + componentAnnotation.annotation = annotation; + return componentAnnotation; } /** The root component annotation types. */ - public static ImmutableSet<Class<? extends Annotation>> rootComponentAnnotations() { + public static ImmutableSet<ClassName> rootComponentAnnotations() { return ROOT_COMPONENT_ANNOTATIONS; } /** The subcomponent annotation types. */ - public static ImmutableSet<Class<? extends Annotation>> subcomponentAnnotations() { + public static ImmutableSet<ClassName> subcomponentAnnotations() { return SUBCOMPONENT_ANNOTATIONS; } /** All component annotation types. */ - public static ImmutableSet<Class<? extends Annotation>> allComponentAnnotations() { + public static ImmutableSet<ClassName> allComponentAnnotations() { return ALL_COMPONENT_ANNOTATIONS; } /** All component and creator annotation types. */ - public static ImmutableSet<Class<? extends Annotation>> allComponentAndCreatorAnnotations() { + public static ImmutableSet<ClassName> allComponentAndCreatorAnnotations() { return ALL_COMPONENT_AND_CREATOR_ANNOTATIONS; } - - /** - * An actual component annotation. - * - * @see FictionalComponentAnnotation - */ - @AutoValue - abstract static class RealComponentAnnotation extends ComponentAnnotation { - - @Override - @Memoized - public ImmutableList<AnnotationValue> dependencyValues() { - return isSubcomponent() ? ImmutableList.of() : getAnnotationValues("dependencies"); - } - - @Override - @Memoized - public ImmutableList<TypeMirror> dependencyTypes() { - return super.dependencyTypes(); - } - - @Override - @Memoized - public ImmutableList<TypeElement> dependencies() { - return super.dependencies(); - } - - @Override - public boolean isRealComponent() { - return true; - } - - @Override - @Memoized - public ImmutableList<AnnotationValue> moduleValues() { - return getAnnotationValues("modules"); - } - - @Override - @Memoized - public ImmutableList<TypeMirror> moduleTypes() { - return super.moduleTypes(); - } - - @Override - @Memoized - public ImmutableSet<TypeElement> modules() { - return super.modules(); - } - - static Builder builder() { - return new AutoValue_ComponentAnnotation_RealComponentAnnotation.Builder(); - } - - @AutoValue.Builder - interface Builder { - Builder annotation(AnnotationMirror annotation); - - Builder isSubcomponent(boolean isSubcomponent); - - Builder isProduction(boolean isProduction); - - RealComponentAnnotation build(); - } - } - - /** - * A fictional component annotation used to represent modules or other collections of bindings as - * a component. - */ - @AutoValue - abstract static class FictionalComponentAnnotation extends ComponentAnnotation { - - @Override - public AnnotationMirror annotation() { - return moduleAnnotation().annotation(); - } - - @Override - public boolean isSubcomponent() { - return false; - } - - @Override - public boolean isProduction() { - return ClassName.get(asType(moduleAnnotation().annotation().getAnnotationType().asElement())) - .equals(PRODUCER_MODULE); - } - - @Override - public boolean isRealComponent() { - return false; - } - - @Override - public ImmutableList<AnnotationValue> dependencyValues() { - return ImmutableList.of(); - } - - @Override - public ImmutableList<AnnotationValue> moduleValues() { - return moduleAnnotation().includesAsAnnotationValues(); - } - - @Override - @Memoized - public ImmutableList<TypeMirror> moduleTypes() { - return super.moduleTypes(); - } - - @Override - @Memoized - public ImmutableSet<TypeElement> modules() { - return super.modules(); - } - - public abstract ModuleAnnotation moduleAnnotation(); - } } diff --git a/java/dagger/internal/codegen/binding/ComponentCreatorAnnotation.java b/java/dagger/internal/codegen/base/ComponentCreatorAnnotation.java index 6297188d0..621d66f64 100644 --- a/java/dagger/internal/codegen/binding/ComponentCreatorAnnotation.java +++ b/java/dagger/internal/codegen/base/ComponentCreatorAnnotation.java @@ -14,68 +14,62 @@ * limitations under the License. */ -package dagger.internal.codegen.binding; +package dagger.internal.codegen.base; -import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.Ascii.toUpperCase; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.extension.DaggerStreams.valuesOf; import static java.util.stream.Collectors.mapping; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableSet; -import dagger.Component; -import dagger.Subcomponent; -import dagger.internal.codegen.base.ComponentAnnotation; -import dagger.producers.ProductionComponent; -import dagger.producers.ProductionSubcomponent; -import java.lang.annotation.Annotation; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.javapoet.TypeNames; import java.util.stream.Collector; import java.util.stream.Stream; -import javax.lang.model.element.TypeElement; /** Simple representation of a component creator annotation type. */ public enum ComponentCreatorAnnotation { - COMPONENT_BUILDER(Component.Builder.class), - COMPONENT_FACTORY(Component.Factory.class), - SUBCOMPONENT_BUILDER(Subcomponent.Builder.class), - SUBCOMPONENT_FACTORY(Subcomponent.Factory.class), - PRODUCTION_COMPONENT_BUILDER(ProductionComponent.Builder.class), - PRODUCTION_COMPONENT_FACTORY(ProductionComponent.Factory.class), - PRODUCTION_SUBCOMPONENT_BUILDER(ProductionSubcomponent.Builder.class), - PRODUCTION_SUBCOMPONENT_FACTORY(ProductionSubcomponent.Factory.class), + COMPONENT_BUILDER(TypeNames.COMPONENT_BUILDER), + COMPONENT_FACTORY(TypeNames.COMPONENT_FACTORY), + SUBCOMPONENT_BUILDER(TypeNames.SUBCOMPONENT_BUILDER), + SUBCOMPONENT_FACTORY(TypeNames.SUBCOMPONENT_FACTORY), + PRODUCTION_COMPONENT_BUILDER(TypeNames.PRODUCTION_COMPONENT_BUILDER), + PRODUCTION_COMPONENT_FACTORY(TypeNames.PRODUCTION_COMPONENT_FACTORY), + PRODUCTION_SUBCOMPONENT_BUILDER(TypeNames.PRODUCTION_SUBCOMPONENT_BUILDER), + PRODUCTION_SUBCOMPONENT_FACTORY(TypeNames.PRODUCTION_SUBCOMPONENT_FACTORY), ; - private final Class<? extends Annotation> annotation; + private final ClassName annotation; private final ComponentCreatorKind creatorKind; - private final Class<? extends Annotation> componentAnnotation; + private final ClassName componentAnnotation; - @SuppressWarnings("unchecked") // Builder/factory annotations live within their parent annotation. - ComponentCreatorAnnotation(Class<? extends Annotation> annotation) { + ComponentCreatorAnnotation(ClassName annotation) { this.annotation = annotation; - this.creatorKind = ComponentCreatorKind.valueOf(toUpperCase(annotation.getSimpleName())); - this.componentAnnotation = (Class<? extends Annotation>) annotation.getEnclosingClass(); + this.creatorKind = ComponentCreatorKind.valueOf(toUpperCase(annotation.simpleName())); + this.componentAnnotation = annotation.enclosingClassName(); } /** The actual annotation type. */ - public Class<? extends Annotation> annotation() { + public ClassName annotation() { return annotation; } /** The component annotation type that encloses this creator annotation type. */ - public final Class<? extends Annotation> componentAnnotation() { + public final ClassName componentAnnotation() { return componentAnnotation; } /** Returns {@code true} if the creator annotation is for a subcomponent. */ public final boolean isSubcomponentCreatorAnnotation() { - return componentAnnotation().getSimpleName().endsWith("Subcomponent"); + return componentAnnotation().simpleName().endsWith("Subcomponent"); } /** * Returns {@code true} if the creator annotation is for a production component or subcomponent. */ public final boolean isProductionCreatorAnnotation() { - return componentAnnotation().getSimpleName().startsWith("Production"); + return componentAnnotation().simpleName().startsWith("Production"); } /** The creator kind the annotation is associated with. */ @@ -86,16 +80,16 @@ public enum ComponentCreatorAnnotation { @Override public final String toString() { - return annotation().getName(); + return annotation().canonicalName(); } /** Returns all component creator annotations. */ - public static ImmutableSet<Class<? extends Annotation>> allCreatorAnnotations() { + public static ImmutableSet<ClassName> allCreatorAnnotations() { return stream().collect(toAnnotationClasses()); } /** Returns all root component creator annotations. */ - public static ImmutableSet<Class<? extends Annotation>> rootComponentCreatorAnnotations() { + public static ImmutableSet<ClassName> rootComponentCreatorAnnotations() { return stream() .filter( componentCreatorAnnotation -> @@ -104,7 +98,7 @@ public enum ComponentCreatorAnnotation { } /** Returns all subcomponent creator annotations. */ - public static ImmutableSet<Class<? extends Annotation>> subcomponentCreatorAnnotations() { + public static ImmutableSet<ClassName> subcomponentCreatorAnnotations() { return stream() .filter( componentCreatorAnnotation -> @@ -113,7 +107,7 @@ public enum ComponentCreatorAnnotation { } /** Returns all production component creator annotations. */ - public static ImmutableSet<Class<? extends Annotation>> productionCreatorAnnotations() { + public static ImmutableSet<ClassName> productionCreatorAnnotations() { return stream() .filter( componentCreatorAnnotation -> @@ -122,30 +116,28 @@ public enum ComponentCreatorAnnotation { } /** Returns the legal creator annotations for the given {@code componentAnnotation}. */ - public static ImmutableSet<Class<? extends Annotation>> creatorAnnotationsFor( + public static ImmutableSet<ClassName> creatorAnnotationsFor( ComponentAnnotation componentAnnotation) { return stream() .filter( creatorAnnotation -> creatorAnnotation .componentAnnotation() - .getSimpleName() + .simpleName() .equals(componentAnnotation.simpleName())) .collect(toAnnotationClasses()); } /** Returns all creator annotations present on the given {@code type}. */ - public static ImmutableSet<ComponentCreatorAnnotation> getCreatorAnnotations(TypeElement type) { - return stream() - .filter(cca -> isAnnotationPresent(type, cca.annotation())) - .collect(toImmutableSet()); + public static ImmutableSet<ComponentCreatorAnnotation> getCreatorAnnotations(XTypeElement type) { + return stream().filter(cca -> type.hasAnnotation(cca.annotation())).collect(toImmutableSet()); } private static Stream<ComponentCreatorAnnotation> stream() { return valuesOf(ComponentCreatorAnnotation.class); } - private static Collector<ComponentCreatorAnnotation, ?, ImmutableSet<Class<? extends Annotation>>> + private static Collector<ComponentCreatorAnnotation, ?, ImmutableSet<ClassName>> toAnnotationClasses() { return mapping(ComponentCreatorAnnotation::annotation, toImmutableSet()); } diff --git a/java/dagger/internal/codegen/binding/ComponentCreatorKind.java b/java/dagger/internal/codegen/base/ComponentCreatorKind.java index b2581d685..3af94bbc2 100644 --- a/java/dagger/internal/codegen/binding/ComponentCreatorKind.java +++ b/java/dagger/internal/codegen/base/ComponentCreatorKind.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package dagger.internal.codegen.binding; +package dagger.internal.codegen.base; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; diff --git a/java/dagger/internal/codegen/base/ComponentKind.java b/java/dagger/internal/codegen/base/ComponentKind.java new file mode 100644 index 000000000..9d087636a --- /dev/null +++ b/java/dagger/internal/codegen/base/ComponentKind.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2014 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.google.common.collect.Sets.immutableEnumSet; +import static dagger.internal.codegen.extension.DaggerStreams.stream; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static dagger.internal.codegen.extension.DaggerStreams.valuesOf; +import static java.util.EnumSet.allOf; + +import androidx.room.compiler.processing.XTypeElement; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.javapoet.TypeNames; +import java.util.Optional; + +/** Enumeration of the different kinds of components. */ +public enum ComponentKind { + COMPONENT(TypeNames.COMPONENT), + SUBCOMPONENT(TypeNames.SUBCOMPONENT), + PRODUCTION_COMPONENT(TypeNames.PRODUCTION_COMPONENT), + PRODUCTION_SUBCOMPONENT(TypeNames.PRODUCTION_SUBCOMPONENT), + MODULE(TypeNames.MODULE), + PRODUCER_MODULE(TypeNames.PRODUCER_MODULE); + + private static final ImmutableSet<ComponentKind> PRODUCER_KINDS = + ImmutableSet.of(PRODUCTION_COMPONENT, PRODUCTION_SUBCOMPONENT, PRODUCER_MODULE); + + /** Returns the annotations for components of the given kinds. */ + public static ImmutableSet<ClassName> annotationsFor(Iterable<ComponentKind> kinds) { + return stream(kinds).map(ComponentKind::annotation).collect(toImmutableSet()); + } + + /** Returns the set of component kinds the given {@code element} has annotations for. */ + public static ImmutableSet<ComponentKind> getComponentKinds(XTypeElement element) { + return valuesOf(ComponentKind.class) + .filter(kind -> element.hasAnnotation(kind.annotation())) + .collect(toImmutableSet()); + } + + /** + * Returns the kind of an annotated element if it is annotated with one of the {@linkplain + * #annotation() annotations}. + * + * @throws IllegalArgumentException if the element is annotated with more than one of the + * annotations + */ + public static Optional<ComponentKind> forAnnotatedElement(XTypeElement element) { + ImmutableSet<ComponentKind> kinds = getComponentKinds(element); + if (kinds.size() > 1) { + throw new IllegalArgumentException( + element + " cannot be annotated with more than one of " + annotationsFor(kinds)); + } + return kinds.stream().findAny(); + } + + private final ClassName annotation; + + ComponentKind(ClassName annotation) { + this.annotation = annotation; + } + + /** Returns the annotation that marks a component of this kind. */ + public ClassName annotation() { + return annotation; + } + + /** Returns the kinds of modules that can be used with a component of this kind. */ + public ImmutableSet<ModuleKind> legalModuleKinds() { + return isProducer() + ? immutableEnumSet(allOf(ModuleKind.class)) + : immutableEnumSet(ModuleKind.MODULE); + } + + /** Returns the kinds of subcomponents a component of this kind can have. */ + public ImmutableSet<ComponentKind> legalSubcomponentKinds() { + return isProducer() + ? immutableEnumSet(PRODUCTION_SUBCOMPONENT) + : immutableEnumSet(SUBCOMPONENT, PRODUCTION_SUBCOMPONENT); + } + + /** Returns true if this is a production component. */ + public boolean isProducer() { + return PRODUCER_KINDS.contains(this); + } +} diff --git a/java/dagger/internal/codegen/base/ContributionType.java b/java/dagger/internal/codegen/base/ContributionType.java index c046daaf2..93d423e4b 100644 --- a/java/dagger/internal/codegen/base/ContributionType.java +++ b/java/dagger/internal/codegen/base/ContributionType.java @@ -16,8 +16,10 @@ package dagger.internal.codegen.base; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.auto.common.MoreElements.isAnnotationPresent; +import androidx.room.compiler.processing.XElement; import dagger.multibindings.ElementsIntoSet; import dagger.multibindings.IntoMap; import dagger.multibindings.IntoSet; @@ -54,6 +56,17 @@ public enum ContributionType { * dagger.internal.codegen.validation.BindsInstanceProcessingStep} validate correctness on their * own. */ + public static ContributionType fromBindingElement(XElement element) { + return fromBindingElement(toJavac(element)); + } + + /** + * The contribution type from a binding element's annotations. Presumes a well-formed binding + * element (at most one of @IntoSet, @IntoMap, @ElementsIntoSet and @Provides.type). {@link + * dagger.internal.codegen.validation.BindingMethodValidator} and {@link + * dagger.internal.codegen.validation.BindsInstanceProcessingStep} validate correctness on their + * own. + */ public static ContributionType fromBindingElement(Element element) { // TODO(bcorso): Replace these class references with ClassName. if (isAnnotationPresent(element, IntoMap.class)) { diff --git a/java/dagger/internal/codegen/base/DaggerSuperficialValidation.java b/java/dagger/internal/codegen/base/DaggerSuperficialValidation.java new file mode 100644 index 000000000..2f53a71fc --- /dev/null +++ b/java/dagger/internal/codegen/base/DaggerSuperficialValidation.java @@ -0,0 +1,823 @@ +/* + * Copyright (C) 2021 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 androidx.room.compiler.processing.compat.XConverters.toJavac; +import static com.google.auto.common.MoreElements.asType; +import static com.google.auto.common.MoreElements.isType; +import static com.google.auto.common.MoreTypes.asDeclared; + +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import com.google.auto.common.AnnotationMirrors; +import com.google.auto.common.MoreTypes; +import com.google.common.base.Ascii; +import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.ClassName; +import dagger.Reusable; +import dagger.internal.codegen.compileroption.CompilerOptions; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.inject.Inject; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.AnnotationValueVisitor; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ElementVisitor; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ErrorType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVisitor; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.AbstractElementVisitor8; +import javax.lang.model.util.SimpleAnnotationValueVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; + +/** + * A fork of {@link com.google.auto.common.SuperficialValidation}. + * + * <p>This fork makes a couple changes from the original: + * + * <ul> + * <li>Throws {@link ValidationException} rather than returning {@code false} for invalid types. + * <li>Fixes a bug that incorrectly validates error types in annotations (b/213880825) + * <li>Exposes extra methods needed to validate various parts of an element rather than just the + * entire element. + * </ul> + */ +@Reusable +public final class DaggerSuperficialValidation { + + /** + * Returns the type element with the given class name or throws {@link ValidationException} if it + * is not accessible in the current compilation. + */ + public static XTypeElement requireTypeElement(XProcessingEnv processingEnv, ClassName className) { + return requireTypeElement(processingEnv, className.canonicalName()); + } + + /** + * Returns the type element with the given class name or throws {@link ValidationException} if it + * is not accessible in the current compilation. + */ + public static XTypeElement requireTypeElement(XProcessingEnv processingEnv, String className) { + XTypeElement type = processingEnv.findTypeElement(className); + if (type == null) { + throw new ValidationException.KnownErrorType(className); + } + return type; + } + + private final boolean isStrictValidationEnabled; + + @Inject + DaggerSuperficialValidation(CompilerOptions compilerOptions) { + this(compilerOptions.strictSuperficialValidation()); + } + + private DaggerSuperficialValidation(boolean isStrictValidationEnabled) { + this.isStrictValidationEnabled = isStrictValidationEnabled; + } + + /** + * Validates the {@link XElement#getType()} type of the given element. + * + * <p>Validating the type also validates any types it references, such as any type arguments or + * type bounds. For an {@link ExecutableType}, the parameter and return types must be fully + * defined, as must types declared in a {@code throws} clause or in the bounds of any type + * parameters. + */ + public void validateTypeOf(XElement element) { + validateTypeOf(toJavac(element)); + } + + private void validateTypeOf(Element element) { + try { + validateType(Ascii.toLowerCase(element.getKind().name()), element.asType()); + } catch (RuntimeException exception) { + throw ValidationException.from(exception).append(element); + } + } + + /** + * Validates the {@link XElement#getSuperType()} type of the given element. + * + * <p>Validating the type also validates any types it references, such as any type arguments or + * type bounds. + */ + public void validateSuperTypeOf(XTypeElement element) { + validateSuperTypeOf(toJavac(element)); + } + + private void validateSuperTypeOf(TypeElement element) { + try { + validateType("superclass", element.getSuperclass()); + } catch (RuntimeException exception) { + throw ValidationException.from(exception).append(element); + } + } + + /** + * Validates the {@link XExecutableElement#getThrownTypes()} types of the given element. + * + * <p>Validating the type also validates any types it references, such as any type arguments or + * type bounds. + */ + public void validateThrownTypesOf(XExecutableElement element) { + validateThrownTypesOf(toJavac(element)); + } + + private void validateThrownTypesOf(ExecutableElement element) { + try { + validateTypes("thrown type", element.getThrownTypes()); + } catch (RuntimeException exception) { + throw ValidationException.from(exception).append(element); + } + } + + /** + * Validates the annotation types of the given element. + * + * <p>Note: this method does not validate annotation values. This method is useful if you care + * about the annotation's annotations (e.g. to check for {@code Scope} or {@code Qualifier}). In + * such cases, we just need to validate the annotation's type. + */ + public void validateAnnotationTypesOf(XElement element) { + validateAnnotationTypesOf(toJavac(element)); + } + + /** + * Validates the annotation types of the given element. + * + * <p>Note: this method does not validate annotation values. This method is useful if you care + * about the annotation's annotations (e.g. to check for {@code Scope} or {@code Qualifier}). In + * such cases, we just need to validate the annotation's type. + */ + public void validateAnnotationTypesOf(Element element) { + element + .getAnnotationMirrors() + .forEach(annotation -> validateAnnotationTypeOf(element, annotation)); + } + + /** + * Validates the type of the given annotation. + * + * <p>The annotation is assumed to be annotating the given element, but this is not checked. The + * element is only in the error message if a {@link ValidatationException} is thrown. + * + * <p>Note: this method does not validate annotation values. This method is useful if you care + * about the annotation's annotations (e.g. to check for {@code Scope} or {@code Qualifier}). In + * such cases, we just need to validate the annotation's type. + */ + // TODO(bcorso): See CL/427767370 for suggestions to make this API clearer. + public void validateAnnotationTypeOf(XElement element, XAnnotation annotation) { + validateAnnotationTypeOf(toJavac(element), toJavac(annotation)); + } + + /** + * Validates the type of the given annotation. + * + * <p>The annotation is assumed to be annotating the given element, but this is not checked. The + * element is only in the error message if a {@link ValidatationException} is thrown. + * + * <p>Note: this method does not validate annotation values. This method is useful if you care + * about the annotation's annotations (e.g. to check for {@code Scope} or {@code Qualifier}). In + * such cases, we just need to validate the annotation's type. + */ + public void validateAnnotationTypeOf(Element element, AnnotationMirror annotation) { + try { + validateType("annotation type", annotation.getAnnotationType()); + } catch (RuntimeException exception) { + throw ValidationException.from(exception).append(annotation).append(element); + } + } + + /** Validate the annotations of the given element. */ + public void validateAnnotationsOf(XElement element) { + validateAnnotationsOf(toJavac(element)); + } + + public void validateAnnotationsOf(Element element) { + try { + validateAnnotations(element.getAnnotationMirrors()); + } catch (RuntimeException exception) { + throw ValidationException.from(exception).append(element); + } + } + + public void validateAnnotationOf(XElement element, XAnnotation annotation) { + validateAnnotationOf(toJavac(element), toJavac(annotation)); + } + + public void validateAnnotationOf(Element element, AnnotationMirror annotation) { + try { + validateAnnotation(annotation); + } catch (RuntimeException exception) { + throw ValidationException.from(exception).append(element); + } + } + + /** + * Validate the type hierarchy for the given type (with the given type description) within the + * given element. + * + * <p>Validation includes all superclasses, interfaces, and type parameters of those types. + */ + public void validateTypeHierarchyOf(String typeDescription, XElement element, XType type) { + try { + validateTypeHierarchy(typeDescription, type); + } catch (RuntimeException exception) { + throw ValidationException.from(exception).append(toJavac(element)); + } + } + + private void validateTypeHierarchy(String desc, XType type) { + validateType(desc, toJavac(type)); + try { + type.getSuperTypes().forEach(supertype -> validateTypeHierarchy("supertype", supertype)); + } catch (RuntimeException exception) { + throw ValidationException.from(exception).append(desc, toJavac(type)); + } + } + + /** + * Returns true if all of the given elements return true from {@link #validateElement(Element)}. + */ + public void validateElements(Iterable<? extends Element> elements) { + for (Element element : elements) { + validateElement(element); + } + } + + private final ElementVisitor<Void, Void> elementValidatingVisitor = + new AbstractElementVisitor8<Void, Void>() { + @Override + public Void visitPackage(PackageElement e, Void p) { + // don't validate enclosed elements because it will return types in the package + validateAnnotations(e.getAnnotationMirrors()); + return null; + } + + @Override + public Void visitType(TypeElement e, Void p) { + validateBaseElement(e); + validateElements(e.getTypeParameters()); + validateTypes("interface", e.getInterfaces()); + validateType("superclass", e.getSuperclass()); + return null; + } + + @Override + public Void visitVariable(VariableElement e, Void p) { + validateBaseElement(e); + return null; + } + + @Override + public Void visitExecutable(ExecutableElement e, Void p) { + AnnotationValue defaultValue = e.getDefaultValue(); + validateBaseElement(e); + if (defaultValue != null) { + validateAnnotationValue(defaultValue, e.getReturnType()); + } + validateType("return type", e.getReturnType()); + validateTypes("thrown type", e.getThrownTypes()); + validateElements(e.getTypeParameters()); + validateElements(e.getParameters()); + return null; + } + + @Override + public Void visitTypeParameter(TypeParameterElement e, Void p) { + validateBaseElement(e); + validateTypes("bound type", e.getBounds()); + return null; + } + + @Override + public Void visitUnknown(Element e, Void p) { + // just assume that unknown elements are OK + return null; + } + }; + + /** + * Returns true if all types referenced by the given element are defined. The exact meaning of + * this depends on the kind of element. For packages, it means that all annotations on the package + * are fully defined. For other element kinds, it means that types referenced by the element, + * anything it contains, and any of its annotations element are all defined. + */ + public void validateElement(XElement element) { + validateElement(toJavac(element)); + } + + /** + * Returns true if all types referenced by the given element are defined. The exact meaning of + * this depends on the kind of element. For packages, it means that all annotations on the package + * are fully defined. For other element kinds, it means that types referenced by the element, + * anything it contains, and any of its annotations element are all defined. + */ + public void validateElement(Element element) { + try { + element.accept(elementValidatingVisitor, null); + } catch (RuntimeException exception) { + throw ValidationException.from(exception).append(element); + } + } + + private void validateBaseElement(Element e) { + validateType(Ascii.toLowerCase(e.getKind().name()), e.asType()); + validateAnnotations(e.getAnnotationMirrors()); + validateElements(e.getEnclosedElements()); + } + + private void validateTypes(String desc, Iterable<? extends TypeMirror> types) { + for (TypeMirror type : types) { + validateType(desc, type); + } + } + + /* + * This visitor does not test type variables specifically, but it seems that that is not actually + * an issue. Javac turns the whole type parameter into an error type if it can't figure out the + * bounds. + */ + private final TypeVisitor<Void, Void> typeValidatingVisitor = + new SimpleTypeVisitor8<Void, Void>() { + @Override + protected Void defaultAction(TypeMirror t, Void p) { + return null; + } + + @Override + public Void visitArray(ArrayType t, Void p) { + validateType("array component type", t.getComponentType()); + return null; + } + + @Override + public Void visitDeclared(DeclaredType t, Void p) { + if (isStrictValidationEnabled) { + // There's a bug in TypeVisitor which will visit the visitDeclared() method rather than + // visitError() even when it's an ERROR kind. Thus, we check the kind directly here and + // fail validation if it's an ERROR kind (see b/213880825). + if (t.getKind() == TypeKind.ERROR) { + throw new ValidationException.KnownErrorType(t); + } + } + validateTypes("type argument", t.getTypeArguments()); + return null; + } + + @Override + public Void visitError(ErrorType t, Void p) { + throw new ValidationException.KnownErrorType(t); + } + + @Override + public Void visitUnknown(TypeMirror t, Void p) { + // just make the default choice for unknown types + return defaultAction(t, p); + } + + @Override + public Void visitWildcard(WildcardType t, Void p) { + TypeMirror extendsBound = t.getExtendsBound(); + TypeMirror superBound = t.getSuperBound(); + if (extendsBound != null) { + validateType("extends bound type", extendsBound); + } + if (superBound != null) { + validateType("super bound type", superBound); + } + return null; + } + + @Override + public Void visitExecutable(ExecutableType t, Void p) { + validateTypes("parameter type", t.getParameterTypes()); + validateType("return type", t.getReturnType()); + validateTypes("thrown type", t.getThrownTypes()); + validateTypes("type variable", t.getTypeVariables()); + return null; + } + }; + + /** + * Returns true if the given type is fully defined. This means that the type itself is defined, as + * are any types it references, such as any type arguments or type bounds. For an {@link + * ExecutableType}, the parameter and return types must be fully defined, as must types declared + * in a {@code throws} clause or in the bounds of any type parameters. + */ + private void validateType(String desc, TypeMirror type) { + try { + type.accept(typeValidatingVisitor, null); + if (isStrictValidationEnabled) { + // Note: We don't actually expect to get an ERROR type here as it should have been caught + // by the visitError() or visitDeclared() methods above. However, we check here as a last + // resort. + if (type.getKind() == TypeKind.ERROR) { + // In this case, the type is not guaranteed to be a DeclaredType, so we report the + // toString() of the type. We could report using UnknownErrorType but the type's toString + // may actually contain useful information. + throw new ValidationException.KnownErrorType(type.toString()); + } + } + } catch (RuntimeException e) { + throw ValidationException.from(e).append(desc, type); + } + } + + private void validateAnnotations(Iterable<? extends AnnotationMirror> annotationMirrors) { + for (AnnotationMirror annotationMirror : annotationMirrors) { + validateAnnotation(annotationMirror); + } + } + + private void validateAnnotation(AnnotationMirror annotationMirror) { + try { + validateType("annotation type", annotationMirror.getAnnotationType()); + validateAnnotationValues(annotationMirror.getElementValues()); + } catch (RuntimeException exception) { + throw ValidationException.from(exception).append(annotationMirror); + } + } + + private void validateAnnotationValues( + Map<? extends ExecutableElement, ? extends AnnotationValue> valueMap) { + valueMap.forEach( + (method, annotationValue) -> { + try { + TypeMirror expectedType = method.getReturnType(); + validateAnnotationValue(annotationValue, expectedType); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(String.format("annotation method: %s %s", method.getReturnType(), method)); + } + }); + } + + private void validateAnnotationValue(AnnotationValue annotationValue, TypeMirror expectedType) { + annotationValue.accept(valueValidatingVisitor, expectedType); + } + + private final AnnotationValueVisitor<Void, TypeMirror> valueValidatingVisitor = + new SimpleAnnotationValueVisitor8<Void, TypeMirror>() { + @Override + protected Void defaultAction(Object o, TypeMirror expectedType) { + try { + validateIsTypeOf(o.getClass(), expectedType); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("DEFAULT", o, expectedType)); + } + return null; + } + + @Override + public Void visitString(String str, TypeMirror expectedType) { + try { + if (!MoreTypes.isTypeOf(String.class, expectedType)) { + if (str.contentEquals("<error>")) { + // Invalid annotation value types will visit visitString() with a value of "<error>" + // Technically, we don't know the error type in this case, but it will be referred + // to as "<error>" in the dependency trace, so we use that. + throw new ValidationException.KnownErrorType("<error>"); + } else { + throw new ValidationException.UnknownErrorType(); + } + } + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("STRING", str, expectedType)); + } + return null; + } + + @Override + public Void visitUnknown(AnnotationValue av, TypeMirror expectedType) { + // just take the default action for the unknown + defaultAction(av, expectedType); + return null; + } + + @Override + public Void visitAnnotation(AnnotationMirror a, TypeMirror expectedType) { + try { + validateIsEquivalentType(a.getAnnotationType(), expectedType); + validateAnnotation(a); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("ANNOTATION", a, expectedType)); + } + return null; + } + + @Override + public Void visitArray(List<? extends AnnotationValue> values, TypeMirror expectedType) { + try { + if (!expectedType.getKind().equals(TypeKind.ARRAY)) { + throw new ValidationException.UnknownErrorType(); + } + TypeMirror componentType = MoreTypes.asArray(expectedType).getComponentType(); + for (AnnotationValue value : values) { + value.accept(this, componentType); + } + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("ARRAY", values, expectedType)); + } + return null; + } + + @Override + public Void visitEnumConstant(VariableElement enumConstant, TypeMirror expectedType) { + try { + validateIsEquivalentType(asDeclared(enumConstant.asType()), expectedType); + validateElement(enumConstant); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("ENUM_CONSTANT", enumConstant, expectedType)); + } + return null; + } + + @Override + public Void visitType(TypeMirror type, TypeMirror expectedType) { + try { + // We could check assignability here, but would require a Types instance. Since this + // isn't really the sort of thing that shows up in a bad AST from upstream compilation + // we ignore the expected type and just validate the type. It might be wrong, but + // it's valid. + validateType("annotation value type", type); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("TYPE", type, expectedType)); + } + return null; + } + + @Override + public Void visitBoolean(boolean b, TypeMirror expectedType) { + try { + validateIsTypeOf(Boolean.TYPE, expectedType); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("BOOLEAN", b, expectedType)); + } + return null; + } + + @Override + public Void visitByte(byte b, TypeMirror expectedType) { + try { + validateIsTypeOf(Byte.TYPE, expectedType); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("BYTE", b, expectedType)); + } + return null; + } + + @Override + public Void visitChar(char c, TypeMirror expectedType) { + try { + validateIsTypeOf(Character.TYPE, expectedType); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("CHAR", c, expectedType)); + } + return null; + } + + @Override + public Void visitDouble(double d, TypeMirror expectedType) { + try { + validateIsTypeOf(Double.TYPE, expectedType); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("DOUBLE", d, expectedType)); + } + return null; + } + + @Override + public Void visitFloat(float f, TypeMirror expectedType) { + try { + validateIsTypeOf(Float.TYPE, expectedType); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("FLOAT", f, expectedType)); + } + return null; + } + + @Override + public Void visitInt(int i, TypeMirror expectedType) { + try { + validateIsTypeOf(Integer.TYPE, expectedType); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("INT", i, expectedType)); + } + return null; + } + + @Override + public Void visitLong(long l, TypeMirror expectedType) { + try { + validateIsTypeOf(Long.TYPE, expectedType); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("LONG", l, expectedType)); + } + return null; + } + + @Override + public Void visitShort(short s, TypeMirror expectedType) { + try { + validateIsTypeOf(Short.TYPE, expectedType); + } catch (RuntimeException exception) { + throw ValidationException.from(exception) + .append(exceptionMessage("SHORT", s, expectedType)); + } + return null; + } + + private <T> String exceptionMessage(String valueType, T value, TypeMirror expectedType) { + return String.format( + "annotation value (%s): value '%s' with expected type %s", + valueType, value, expectedType); + } + }; + + private void validateIsTypeOf(Class<?> clazz, TypeMirror expectedType) { + if (!MoreTypes.isTypeOf(clazz, expectedType)) { + throw new ValidationException.UnknownErrorType(); + } + } + + private void validateIsEquivalentType(DeclaredType type, TypeMirror expectedType) { + if (!MoreTypes.equivalence().equivalent(type, expectedType)) { + throw new ValidationException.KnownErrorType(type); + } + } + + /** + * A runtime exception that can be used during superficial validation to collect information about + * unexpected exceptions during validation. + */ + public abstract static class ValidationException extends RuntimeException { + /** A {@link ValidationException} that originated from an unexpected exception. */ + public static final class UnexpectedException extends ValidationException { + private UnexpectedException(Throwable throwable) { + super(throwable); + } + } + + /** A {@link ValidationException} that originated from a known error type. */ + public static final class KnownErrorType extends ValidationException { + private final String errorTypeName; + + private KnownErrorType(DeclaredType errorType) { + Element errorElement = errorType.asElement(); + this.errorTypeName = + isType(errorElement) + ? asType(errorElement).getQualifiedName().toString() + // Maybe this case should be handled by UnknownErrorType? + : errorElement.getSimpleName().toString(); + } + + private KnownErrorType(String errorTypeName) { + this.errorTypeName = errorTypeName; + } + + public String getErrorTypeName() { + return errorTypeName; + } + } + + /** A {@link ValidationException} that originated from an unknown error type. */ + public static final class UnknownErrorType extends ValidationException {} + + private static ValidationException from(Throwable throwable) { + // We only ever create one instance of the ValidationException. + return throwable instanceof ValidationException + ? ((ValidationException) throwable) + : new UnexpectedException(throwable); + } + + private Optional<Element> lastReportedElement = Optional.empty(); + private final List<String> messages = new ArrayList<>(); + + private ValidationException() { + super(""); + } + + private ValidationException(Throwable throwable) { + super("", throwable); + } + + /** + * Appends a message for the given element and returns this instance of {@link + * ValidationException} + */ + private ValidationException append(Element element) { + lastReportedElement = Optional.of(element); + return append(getMessageForElement(element)); + } + + /** + * Appends a message for the given type mirror and returns this instance of {@link + * ValidationException} + */ + private ValidationException append(String desc, TypeMirror type) { + return append(String.format("type (%s %s): %s", type.getKind().name(), desc, type)); + } + + /** + * Appends a message for the given annotation mirror and returns this instance of {@link + * ValidationException} + */ + private ValidationException append(AnnotationMirror annotationMirror) { + // Note: Calling #toString() directly on the annotation throws NPE (b/216180336). + return append(String.format("annotation: %s", AnnotationMirrors.toString(annotationMirror))); + } + + /** Appends the given message and returns this instance of {@link ValidationException} */ + private ValidationException append(String message) { + messages.add(message); + return this; + } + + @Override + public String getMessage() { + return String.format("\n Validation trace:\n => %s", getTrace()); + } + + public String getTrace() { + return String.join("\n => ", getMessageInternal().reverse()); + } + + private ImmutableList<String> getMessageInternal() { + if (!lastReportedElement.isPresent()) { + return ImmutableList.copyOf(messages); + } + // Append any enclosing element information if needed. + List<String> newMessages = new ArrayList<>(messages); + Element element = lastReportedElement.get(); + while (shouldAppendEnclosingElement(element)) { + element = element.getEnclosingElement(); + newMessages.add(getMessageForElement(element)); + } + return ImmutableList.copyOf(newMessages); + } + + private static boolean shouldAppendEnclosingElement(Element element) { + return element.getEnclosingElement() != null + // We don't report enclosing elements for types because the type name should contain any + // enclosing type and package information we need. + && !isType(element) + && (isExecutable(element.getEnclosingElement()) || isType(element.getEnclosingElement())); + } + + private static boolean isExecutable(Element element) { + return element.getKind() == ElementKind.METHOD + || element.getKind() == ElementKind.CONSTRUCTOR; + } + + private String getMessageForElement(Element element) { + return String.format("element (%s): %s", element.getKind().name(), element); + } + } +} diff --git a/java/dagger/internal/codegen/base/ElementFormatter.java b/java/dagger/internal/codegen/base/ElementFormatter.java index f85fbfde8..65f9385c1 100644 --- a/java/dagger/internal/codegen/base/ElementFormatter.java +++ b/java/dagger/internal/codegen/base/ElementFormatter.java @@ -16,9 +16,11 @@ package dagger.internal.codegen.base; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.auto.common.MoreElements.asExecutable; import static java.util.stream.Collectors.joining; +import androidx.room.compiler.processing.XElement; import javax.inject.Inject; import javax.lang.model.element.Element; import javax.lang.model.element.ElementVisitor; @@ -50,6 +52,17 @@ public final class ElementFormatter extends Formatter<Element> { * * <p>Parameters are given with their enclosing executable, with other parameters elided. */ + public static String elementToString(XElement element) { + return elementToString(toJavac(element)); + } + + /** + * Returns a useful string form for an element. + * + * <p>Elements directly enclosed by a type are preceded by the enclosing type's qualified name. + * + * <p>Parameters are given with their enclosing executable, with other parameters elided. + */ public static String elementToString(Element element) { return element.accept(ELEMENT_TO_STRING, null); } diff --git a/java/dagger/internal/codegen/base/FrameworkTypes.java b/java/dagger/internal/codegen/base/FrameworkTypes.java index 4cd54a33d..2336256e2 100644 --- a/java/dagger/internal/codegen/base/FrameworkTypes.java +++ b/java/dagger/internal/codegen/base/FrameworkTypes.java @@ -16,16 +16,16 @@ package dagger.internal.codegen.base; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.auto.common.MoreTypes.isType; +import static dagger.internal.codegen.langmodel.DaggerTypes.isTypeOf; +import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XType; import com.google.common.collect.ImmutableSet; -import dagger.Lazy; -import dagger.MembersInjector; -import dagger.producers.Produced; -import dagger.producers.Producer; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.javapoet.TypeNames; import java.util.Set; -import javax.inject.Provider; import javax.lang.model.type.TypeMirror; /** @@ -33,17 +33,22 @@ import javax.lang.model.type.TypeMirror; * type that the framework itself defines. */ public final class FrameworkTypes { - private static final ImmutableSet<Class<?>> PROVISION_TYPES = - ImmutableSet.of(Provider.class, Lazy.class, MembersInjector.class); + private static final ImmutableSet<ClassName> PROVISION_TYPES = + ImmutableSet.of(TypeNames.PROVIDER, TypeNames.LAZY, TypeNames.MEMBERS_INJECTOR); // NOTE(beder): ListenableFuture is not considered a producer framework type because it is not // defined by the framework, so we can't treat it specially in ordinary Dagger. - private static final ImmutableSet<Class<?>> PRODUCTION_TYPES = - ImmutableSet.of(Produced.class, Producer.class); + private static final ImmutableSet<ClassName> PRODUCTION_TYPES = + ImmutableSet.of(TypeNames.PRODUCED, TypeNames.PRODUCER); /** Returns true if the type represents a producer-related framework type. */ - public static boolean isProducerType(TypeMirror type) { - return isType(type) && typeIsOneOf(PRODUCTION_TYPES, type); + public static boolean isProducerType(XType type) { + return PRODUCTION_TYPES.stream().anyMatch(className -> isTypeOf(type, className)); + } + + /** Returns true if the type represents a framework type. */ + public static boolean isFrameworkType(XType type) { + return isFrameworkType(toJavac(type)); } /** Returns true if the type represents a framework type. */ @@ -53,13 +58,8 @@ public final class FrameworkTypes { || typeIsOneOf(PRODUCTION_TYPES, type)); } - private static boolean typeIsOneOf(Set<Class<?>> classes, TypeMirror type) { - for (Class<?> clazz : classes) { - if (MoreTypes.isTypeOf(clazz, type)) { - return true; - } - } - return false; + private static boolean typeIsOneOf(Set<ClassName> classNames, TypeMirror type) { + return classNames.stream().anyMatch(className -> isTypeOf(className, type)); } private FrameworkTypes() {} diff --git a/java/dagger/internal/codegen/base/Keys.java b/java/dagger/internal/codegen/base/Keys.java index 4d139266d..dc061fe2a 100644 --- a/java/dagger/internal/codegen/base/Keys.java +++ b/java/dagger/internal/codegen/base/Keys.java @@ -16,38 +16,33 @@ package dagger.internal.codegen.base; -import static com.google.auto.common.MoreTypes.asTypeElement; import static dagger.internal.codegen.base.ComponentAnnotation.allComponentAndCreatorAnnotations; -import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent; +import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isRawParameterizedType; -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Key; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import dagger.spi.model.DaggerAnnotation; +import dagger.spi.model.Key; import java.util.Optional; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleTypeVisitor6; /** Utility methods related to {@link Key}s. */ public final class Keys { public static boolean isValidMembersInjectionKey(Key key) { return !key.qualifier().isPresent() && !key.multibindingContributionIdentifier().isPresent() - && key.type().getKind().equals(TypeKind.DECLARED); + && isDeclared(key.type().xprocessing()); } /** * Returns {@code true} if this is valid as an implicit key (that is, if it's valid for a * just-in-time binding by discovering an {@code @Inject} constructor). */ - public static boolean isValidImplicitProvisionKey(Key key, DaggerTypes types) { - return isValidImplicitProvisionKey(key.qualifier(), key.type(), types); + public static boolean isValidImplicitProvisionKey(Key key) { + return isValidImplicitProvisionKey( + key.qualifier().map(DaggerAnnotation::xprocessing), key.type().xprocessing()); } /** @@ -55,42 +50,36 @@ public final class Keys { * key (that is, if it's valid for a just-in-time binding by discovering an {@code @Inject} * constructor). */ - public static boolean isValidImplicitProvisionKey( - Optional<? extends AnnotationMirror> qualifier, TypeMirror type, final DaggerTypes types) { + public static boolean isValidImplicitProvisionKey(Optional<XAnnotation> qualifier, XType type) { // Qualifiers disqualify implicit provisioning. if (qualifier.isPresent()) { return false; } - return type.accept( - new SimpleTypeVisitor6<Boolean, Void>(false) { - @Override - public Boolean visitDeclared(DeclaredType type, Void ignored) { - // Non-classes or abstract classes aren't allowed. - TypeElement element = MoreElements.asType(type.asElement()); - if (!element.getKind().equals(ElementKind.CLASS) - || element.getModifiers().contains(Modifier.ABSTRACT)) { - return false; - } + // A provision type must be a declared type + if (!isDeclared(type)) { + return false; + } + + // Non-classes or abstract classes aren't allowed. + XTypeElement typeElement = type.getTypeElement(); + if (!typeElement.isClass() || typeElement.isAbstract()) { + return false; + } - // If the key has type arguments, validate that each type argument is declared. - // Otherwise the type argument may be a wildcard (or other type), and we can't - // resolve that to actual types. - for (TypeMirror arg : type.getTypeArguments()) { - if (arg.getKind() != TypeKind.DECLARED) { - return false; - } - } + // If the key has type arguments, validate that each type argument is declared. + // Otherwise the type argument may be a wildcard (or other type), and we can't + // resolve that to actual types. + for (XType arg : type.getTypeArguments()) { + if (!isDeclared(arg)) { + return false; + } + } - // Also validate that the key is not the erasure of a generic type. - // If it is, that means the user referred to Foo<T> as just 'Foo', - // which we don't allow. (This is a judgement call -- we *could* - // allow it and instantiate the type bounds... but we don't.) - return MoreTypes.asDeclared(element.asType()).getTypeArguments().isEmpty() - || !types.isSameType(types.erasure(element.asType()), type); - } - }, - null); + // Also validate that if the type represents a parameterized type the user didn't refer to its + // raw type, which we don't allow. (This is a judgement call -- we *could* allow it and + // instantiate the type bounds... but we don't.) + return !isRawParameterizedType(type); } /** @@ -99,7 +88,8 @@ public final class Keys { */ public static boolean isComponentOrCreator(Key key) { return !key.qualifier().isPresent() - && key.type().getKind() == TypeKind.DECLARED - && isAnyAnnotationPresent(asTypeElement(key.type()), allComponentAndCreatorAnnotations()); + && isDeclared(key.type().xprocessing()) + && hasAnyAnnotation( + key.type().xprocessing().getTypeElement(), allComponentAndCreatorAnnotations()); } } diff --git a/java/dagger/internal/codegen/base/MapType.java b/java/dagger/internal/codegen/base/MapType.java index 4e2307a6c..c48b17036 100644 --- a/java/dagger/internal/codegen/base/MapType.java +++ b/java/dagger/internal/codegen/base/MapType.java @@ -18,38 +18,33 @@ package dagger.internal.codegen.base; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +import static dagger.internal.codegen.langmodel.DaggerTypes.unwrapType; +import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XType; import com.google.auto.value.AutoValue; -import com.google.common.base.Equivalence; -import dagger.model.Key; -import java.util.Map; -import javax.lang.model.type.DeclaredType; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.Key; import javax.lang.model.type.TypeMirror; -/** - * Information about a {@link Map} {@link TypeMirror}. - */ +/** Information about a {@link java.util.Map} {@link TypeMirror}. */ @AutoValue public abstract class MapType { - /** - * The map type itself, wrapped using {@link MoreTypes#equivalence()}. Use - * {@link #declaredMapType()} instead. - */ - protected abstract Equivalence.Wrapper<DeclaredType> wrappedDeclaredMapType(); + private XType type; - /** - * The map type itself. - */ - private DeclaredType declaredMapType() { - return wrappedDeclaredMapType().get(); + /** The map type itself. */ + abstract TypeName typeName(); + + /** The map type itself. */ + private XType type() { + return type; } - /** - * {@code true} if the map type is the raw {@link Map} type. - */ + /** {@code true} if the map type is the raw {@link java.util.Map} type. */ public boolean isRawType() { - return declaredMapType().getTypeArguments().isEmpty(); + return type().getTypeArguments().isEmpty(); } /** @@ -57,9 +52,9 @@ public abstract class MapType { * * @throws IllegalStateException if {@link #isRawType()} is true. */ - public TypeMirror keyType() { + public XType keyType() { checkState(!isRawType()); - return declaredMapType().getTypeArguments().get(0); + return type().getTypeArguments().get(0); } /** @@ -67,24 +62,17 @@ public abstract class MapType { * * @throws IllegalStateException if {@link #isRawType()} is true. */ - public TypeMirror valueType() { + public XType valueType() { checkState(!isRawType()); - return declaredMapType().getTypeArguments().get(1); + return type().getTypeArguments().get(1); } - /** - * {@code true} if {@link #valueType()} is a {@code clazz}. - * - * @throws IllegalStateException if {@link #isRawType()} is true. - */ - public boolean valuesAreTypeOf(Class<?> clazz) { - return MoreTypes.isType(valueType()) && MoreTypes.isTypeOf(clazz, valueType()); + /** Returns {@code true} if the raw type of {@link #valueType()} is {@code className}. */ + public boolean valuesAreTypeOf(ClassName className) { + return !isRawType() && isTypeOf(valueType(), className); } - /** - * Returns {@code true} if the {@linkplain #valueType() value type} of the {@link Map} is a - * {@linkplain FrameworkTypes#isFrameworkType(TypeMirror) framework type}. - */ + /** Returns {@code true} if the raw type of {@link #valueType()} is a framework type. */ public boolean valuesAreFrameworkType() { return FrameworkTypes.isFrameworkType(valueType()); } @@ -96,9 +84,8 @@ public abstract class MapType { * @throws IllegalStateException if {@link #isRawType()} is true or {@link #valueType()} is not a * framework type */ - public TypeMirror unwrappedFrameworkValueType() { - checkState( - valuesAreFrameworkType(), "called unwrappedFrameworkValueType() on %s", declaredMapType()); + public XType unwrappedFrameworkValueType() { + checkState(valuesAreFrameworkType(), "called unwrappedFrameworkValueType() on %s", type()); return uncheckedUnwrappedValueType(); } @@ -107,52 +94,45 @@ public abstract class MapType { * * @throws IllegalStateException if {@link #isRawType()} is true or {@link #valueType()} is not a * {@code WrappingClass<V>} - * @throws IllegalArgumentException if {@code wrappingClass} does not have exactly one type - * parameter */ - public TypeMirror unwrappedValueType(Class<?> wrappingClass) { - checkArgument( - wrappingClass.getTypeParameters().length == 1, - "%s must have exactly one type parameter", - wrappingClass); + // TODO(b/202033221): Consider using stricter input type, e.g. FrameworkType. + public XType unwrappedValueType(ClassName wrappingClass) { checkState(valuesAreTypeOf(wrappingClass), "expected values to be %s: %s", wrappingClass, this); return uncheckedUnwrappedValueType(); } - private TypeMirror uncheckedUnwrappedValueType() { - return MoreTypes.asDeclared(valueType()).getTypeArguments().get(0); + private XType uncheckedUnwrappedValueType() { + return unwrapType(valueType()); } - /** - * {@code true} if {@code type} is a {@link Map} type. - */ - public static boolean isMap(TypeMirror type) { - return MoreTypes.isType(type) && MoreTypes.isTypeOf(Map.class, type); + /** {@code true} if {@code type} is a {@link java.util.Map} type. */ + public static boolean isMap(XType type) { + return isTypeOf(type, TypeNames.MAP); } - /** - * {@code true} if {@code key.type()} is a {@link Map} type. - */ + /** {@code true} if {@code key.type()} is a {@link java.util.Map} type. */ public static boolean isMap(Key key) { - return isMap(key.type()); + return isMap(key.type().xprocessing()); } /** * Returns a {@link MapType} for {@code type}. * - * @throws IllegalArgumentException if {@code type} is not a {@link Map} type + * @throws IllegalArgumentException if {@code type} is not a {@link java.util.Map} type */ - public static MapType from(TypeMirror type) { + public static MapType from(XType type) { checkArgument(isMap(type), "%s is not a Map", type); - return new AutoValue_MapType(MoreTypes.equivalence().wrap(MoreTypes.asDeclared(type))); + MapType mapType = new AutoValue_MapType(type.getTypeName()); + mapType.type = type; + return mapType; } /** * Returns a {@link MapType} for {@code key}'s {@link Key#type() type}. * - * @throws IllegalArgumentException if {@code key.type()} is not a {@link Map} type + * @throws IllegalArgumentException if {@code key.type()} is not a {@link java.util.Map} type */ public static MapType from(Key key) { - return from(key.type()); + return from(key.type().xprocessing()); } } diff --git a/java/dagger/internal/codegen/base/ModuleAnnotation.java b/java/dagger/internal/codegen/base/ModuleAnnotation.java index 168873682..faca1d395 100644 --- a/java/dagger/internal/codegen/base/ModuleAnnotation.java +++ b/java/dagger/internal/codegen/base/ModuleAnnotation.java @@ -16,40 +16,42 @@ package dagger.internal.codegen.base; -import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; -import static com.google.auto.common.MoreTypes.asTypeElement; import static com.google.common.base.Preconditions.checkArgument; -import static dagger.internal.codegen.base.MoreAnnotationValues.asAnnotationValues; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; -import static dagger.internal.codegen.langmodel.DaggerElements.getAnyAnnotation; +import static dagger.internal.codegen.xprocessing.XAnnotations.getClassName; +import static dagger.internal.codegen.xprocessing.XElements.getAnyAnnotation; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import dagger.Module; -import dagger.producers.ProducerModule; -import java.lang.annotation.Annotation; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.javapoet.TypeNames; import java.util.Optional; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.TypeElement; /** A {@code @Module} or {@code @ProducerModule} annotation. */ @AutoValue public abstract class ModuleAnnotation { - private static final ImmutableSet<Class<? extends Annotation>> MODULE_ANNOTATIONS = - ImmutableSet.of(Module.class, ProducerModule.class); + private static final ImmutableSet<ClassName> MODULE_ANNOTATIONS = + ImmutableSet.of(TypeNames.MODULE, TypeNames.PRODUCER_MODULE); + + private XAnnotation annotation; /** The annotation itself. */ - // This does not use AnnotationMirrors.equivalence() because we want the actual annotation - // instance. - public abstract AnnotationMirror annotation(); + public final XAnnotation annotation() { + return annotation; + } + + /** Returns the {@link ClassName} name of the annotation. */ + public abstract ClassName className(); /** The simple name of the annotation. */ - public String annotationName() { - return annotation().getAnnotationType().asElement().getSimpleName().toString(); + public String simpleName() { + return className().simpleName(); } /** @@ -58,70 +60,55 @@ public abstract class ModuleAnnotation { * @throws IllegalArgumentException if any of the values are error types */ @Memoized - public ImmutableList<TypeElement> includes() { - return includesAsAnnotationValues().stream() - .map(MoreAnnotationValues::asType) - .map(MoreTypes::asTypeElement) + public ImmutableList<XTypeElement> includes() { + return annotation.getAsTypeList("includes").stream() + .map(XType::getTypeElement) .collect(toImmutableList()); } - /** The values specified in the {@code includes} attribute. */ - @Memoized - public ImmutableList<AnnotationValue> includesAsAnnotationValues() { - return asAnnotationValues(getAnnotationValue(annotation(), "includes")); - } - /** * The types specified in the {@code subcomponents} attribute. * * @throws IllegalArgumentException if any of the values are error types */ @Memoized - public ImmutableList<TypeElement> subcomponents() { - return subcomponentsAsAnnotationValues().stream() - .map(MoreAnnotationValues::asType) - .map(MoreTypes::asTypeElement) + public ImmutableList<XTypeElement> subcomponents() { + return annotation.getAsTypeList("subcomponents").stream() + .map(XType::getTypeElement) .collect(toImmutableList()); } - /** The values specified in the {@code subcomponents} attribute. */ - @Memoized - public ImmutableList<AnnotationValue> subcomponentsAsAnnotationValues() { - return asAnnotationValues(getAnnotationValue(annotation(), "subcomponents")); - } - /** Returns {@code true} if the argument is a {@code @Module} or {@code @ProducerModule}. */ - public static boolean isModuleAnnotation(AnnotationMirror annotation) { - return MODULE_ANNOTATIONS.stream() - .map(Class::getCanonicalName) - .anyMatch(asTypeElement(annotation.getAnnotationType()).getQualifiedName()::contentEquals); + public static boolean isModuleAnnotation(XAnnotation annotation) { + return MODULE_ANNOTATIONS.contains(getClassName(annotation)); } /** The module annotation types. */ - public static ImmutableSet<Class<? extends Annotation>> moduleAnnotations() { + public static ImmutableSet<ClassName> moduleAnnotations() { return MODULE_ANNOTATIONS; } - /** - * Creates an object that represents a {@code @Module} or {@code @ProducerModule}. - * - * @throws IllegalArgumentException if {@link #isModuleAnnotation(AnnotationMirror)} returns - * {@code false} - */ - public static ModuleAnnotation moduleAnnotation(AnnotationMirror annotation) { + private static ModuleAnnotation create(XAnnotation annotation) { checkArgument( isModuleAnnotation(annotation), "%s is not a Module or ProducerModule annotation", annotation); - return new AutoValue_ModuleAnnotation(annotation); + ModuleAnnotation moduleAnnotation = new AutoValue_ModuleAnnotation(getClassName(annotation)); + moduleAnnotation.annotation = annotation; + return moduleAnnotation; } /** * Returns an object representing the {@code @Module} or {@code @ProducerModule} annotation if one * annotates {@code typeElement}. */ - public static Optional<ModuleAnnotation> moduleAnnotation(TypeElement typeElement) { - return getAnyAnnotation(typeElement, Module.class, ProducerModule.class) - .map(ModuleAnnotation::moduleAnnotation); + public static Optional<ModuleAnnotation> moduleAnnotation( + XElement element, DaggerSuperficialValidation superficialValidation) { + return getAnyAnnotation(element, TypeNames.MODULE, TypeNames.PRODUCER_MODULE) + .map( + annotation -> { + superficialValidation.validateAnnotationOf(element, annotation); + return create(annotation); + }); } } diff --git a/java/dagger/internal/codegen/binding/ModuleKind.java b/java/dagger/internal/codegen/base/ModuleKind.java index 6b52f049e..7d08aee92 100644 --- a/java/dagger/internal/codegen/binding/ModuleKind.java +++ b/java/dagger/internal/codegen/base/ModuleKind.java @@ -14,36 +14,31 @@ * limitations under the License. */ -package dagger.internal.codegen.binding; +package dagger.internal.codegen.base; -import static com.google.auto.common.MoreElements.asType; import static com.google.common.base.Preconditions.checkArgument; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; -import dagger.Module; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.producers.ProducerModule; -import java.lang.annotation.Annotation; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.javapoet.TypeNames; import java.util.EnumSet; import java.util.Optional; import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.TypeElement; /** Enumeration of the kinds of modules. */ public enum ModuleKind { /** {@code @Module} */ - MODULE(Module.class), + MODULE(TypeNames.MODULE), /** {@code @ProducerModule} */ - PRODUCER_MODULE(ProducerModule.class); + PRODUCER_MODULE(TypeNames.PRODUCER_MODULE); /** Returns the annotations for modules of the given kinds. */ - public static ImmutableSet<Class<? extends Annotation>> annotationsFor(Set<ModuleKind> kinds) { + private static ImmutableSet<ClassName> annotationsFor(Set<ModuleKind> kinds) { return kinds.stream().map(ModuleKind::annotation).collect(toImmutableSet()); } @@ -54,10 +49,10 @@ public enum ModuleKind { * @throws IllegalArgumentException if the element is annotated with more than one of the module * annotations */ - public static Optional<ModuleKind> forAnnotatedElement(TypeElement element) { + public static Optional<ModuleKind> forAnnotatedElement(XTypeElement element) { Set<ModuleKind> kinds = EnumSet.noneOf(ModuleKind.class); for (ModuleKind kind : values()) { - if (MoreElements.isAnnotationPresent(element, kind.annotation())) { + if (element.hasAnnotation(kind.annotation())) { kinds.add(kind); } } @@ -69,19 +64,19 @@ public enum ModuleKind { return kinds.stream().findAny(); } - public static void checkIsModule(TypeElement moduleElement, KotlinMetadataUtil metadataUtil) { + public static void checkIsModule(XTypeElement moduleElement) { // If the type element is a Kotlin companion object, then assert it is a module if its enclosing // type is a module. - if (metadataUtil.isCompanionObjectClass(moduleElement)) { - checkArgument(forAnnotatedElement(asType(moduleElement.getEnclosingElement())).isPresent()); + if (moduleElement.isCompanionObject()) { + checkArgument(forAnnotatedElement(moduleElement.getEnclosingTypeElement()).isPresent()); } else { checkArgument(forAnnotatedElement(moduleElement).isPresent()); } } - private final Class<? extends Annotation> moduleAnnotation; + private final ClassName moduleAnnotation; - ModuleKind(Class<? extends Annotation> moduleAnnotation) { + ModuleKind(ClassName moduleAnnotation) { this.moduleAnnotation = moduleAnnotation; } @@ -90,15 +85,17 @@ public enum ModuleKind { * * @throws IllegalArgumentException if the annotation is not present on the type */ - public AnnotationMirror getModuleAnnotation(TypeElement element) { - Optional<AnnotationMirror> result = getAnnotationMirror(element, moduleAnnotation); + public XAnnotation getModuleAnnotation(XTypeElement element) { checkArgument( - result.isPresent(), "annotation %s is not present on type %s", moduleAnnotation, element); - return result.get(); + element.hasAnnotation(moduleAnnotation), + "annotation %s is not present on type %s", + moduleAnnotation, + element); + return element.getAnnotation(moduleAnnotation); } /** Returns the annotation that marks a module of this kind. */ - public Class<? extends Annotation> annotation() { + public ClassName annotation() { return moduleAnnotation; } diff --git a/java/dagger/internal/codegen/base/MoreAnnotationMirrors.java b/java/dagger/internal/codegen/base/MoreAnnotationMirrors.java index 234ecc10e..708b9dc39 100644 --- a/java/dagger/internal/codegen/base/MoreAnnotationMirrors.java +++ b/java/dagger/internal/codegen/base/MoreAnnotationMirrors.java @@ -16,24 +16,16 @@ package dagger.internal.codegen.base; -import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; -import static dagger.internal.codegen.base.MoreAnnotationValues.asAnnotationValues; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; - import com.google.auto.common.AnnotationMirrors; import com.google.common.base.Equivalence; -import com.google.common.collect.ImmutableList; import java.util.Optional; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Name; -import javax.lang.model.type.TypeMirror; /** * A utility class for working with {@link AnnotationMirror} instances, similar to {@link * AnnotationMirrors}. */ public final class MoreAnnotationMirrors { - private MoreAnnotationMirrors() {} /** @@ -53,21 +45,4 @@ public final class MoreAnnotationMirrors { Optional<Equivalence.Wrapper<AnnotationMirror>> wrappedOptional) { return wrappedOptional.map(Equivalence.Wrapper::get); } - - public static Name simpleName(AnnotationMirror annotationMirror) { - return annotationMirror.getAnnotationType().asElement().getSimpleName(); - } - - /** - * Returns the list of types that is the value named {@code name} from {@code annotationMirror}. - * - * @throws IllegalArgumentException unless that member represents an array of types - */ - public static ImmutableList<TypeMirror> getTypeListValue( - AnnotationMirror annotationMirror, String name) { - return asAnnotationValues(getAnnotationValue(annotationMirror, name)) - .stream() - .map(MoreAnnotationValues::asType) - .collect(toImmutableList()); - } } diff --git a/java/dagger/internal/codegen/base/MoreAnnotationValues.java b/java/dagger/internal/codegen/base/MoreAnnotationValues.java index 1ee50da11..26175b88d 100644 --- a/java/dagger/internal/codegen/base/MoreAnnotationValues.java +++ b/java/dagger/internal/codegen/base/MoreAnnotationValues.java @@ -25,7 +25,6 @@ import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.AnnotationValueVisitor; -import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleAnnotationValueVisitor8; /** Utility methods for working with {@link AnnotationValue} instances. */ @@ -54,28 +53,6 @@ public final class MoreAnnotationValues { } }; - /** - * Returns the type represented by an annotation value. - * - * @throws IllegalArgumentException unless {@code annotationValue} represents a single type - */ - public static TypeMirror asType(AnnotationValue annotationValue) { - return AS_TYPE.visit(annotationValue); - } - - private static final AnnotationValueVisitor<TypeMirror, Void> AS_TYPE = - new SimpleAnnotationValueVisitor8<TypeMirror, Void>() { - @Override - public TypeMirror visitType(TypeMirror t, Void p) { - return t; - } - - @Override - protected TypeMirror defaultAction(Object o, Void p) { - throw new TypeNotPresentException(o.toString(), null); - } - }; - /** Returns the int value of an annotation */ public static int getIntValue(AnnotationMirror annotation, String valueName) { return (int) getAnnotationValue(annotation, valueName).getValue(); diff --git a/java/dagger/internal/codegen/base/MultibindingAnnotations.java b/java/dagger/internal/codegen/base/MultibindingAnnotations.java deleted file mode 100644 index 424f92a20..000000000 --- a/java/dagger/internal/codegen/base/MultibindingAnnotations.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2016 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 dagger.internal.codegen.langmodel.DaggerElements.getAllAnnotations; - -import com.google.common.collect.ImmutableSet; -import dagger.multibindings.ElementsIntoSet; -import dagger.multibindings.IntoMap; -import dagger.multibindings.IntoSet; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; - -/** - * Utility methods related to processing {@link IntoSet}, {@link ElementsIntoSet}, and {@link - * IntoMap}. - */ -public final class MultibindingAnnotations { - public static ImmutableSet<AnnotationMirror> forElement(Element method) { - return getAllAnnotations(method, IntoSet.class, ElementsIntoSet.class, IntoMap.class); - } -} diff --git a/java/dagger/internal/codegen/base/OptionalType.java b/java/dagger/internal/codegen/base/OptionalType.java index 15056828e..d4f3c0536 100644 --- a/java/dagger/internal/codegen/base/OptionalType.java +++ b/java/dagger/internal/codegen/base/OptionalType.java @@ -17,22 +17,21 @@ package dagger.internal.codegen.base; import static com.google.common.base.Preconditions.checkArgument; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; +import static dagger.internal.codegen.extension.DaggerStreams.valuesOf; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; -import com.google.common.base.Equivalence; +import com.google.common.collect.ImmutableMap; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; -import dagger.model.Key; -import java.util.Optional; -import javax.lang.model.element.Name; -import javax.lang.model.type.DeclaredType; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.Key; import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVisitor; -import javax.lang.model.util.SimpleTypeVisitor8; /** * Information about an {@code Optional} {@link TypeMirror}. @@ -41,44 +40,58 @@ import javax.lang.model.util.SimpleTypeVisitor8; */ @AutoValue public abstract class OptionalType { + private XType type; /** A variant of {@code Optional}. */ public enum OptionalKind { /** {@link com.google.common.base.Optional}. */ - GUAVA_OPTIONAL(com.google.common.base.Optional.class, "absent"), + GUAVA_OPTIONAL(TypeNames.GUAVA_OPTIONAL, "absent"), /** {@link java.util.Optional}. */ - JDK_OPTIONAL(java.util.Optional.class, "empty"), - ; + JDK_OPTIONAL(TypeNames.JDK_OPTIONAL, "empty"); - private final Class<?> clazz; - private final String absentFactoryMethodName; + // Keep a cache from class name to OptionalKind for quick look-up. + private static final ImmutableMap<ClassName, OptionalKind> OPTIONAL_KIND_BY_CLASS_NAME = + valuesOf(OptionalKind.class) + .collect(toImmutableMap(value -> value.className, value -> value)); - OptionalKind(Class<?> clazz, String absentFactoryMethodName) { - this.clazz = clazz; - this.absentFactoryMethodName = absentFactoryMethodName; + private final ClassName className; + private final String absentMethodName; + + OptionalKind(ClassName className, String absentMethodName) { + this.className = className; + this.absentMethodName = absentMethodName; + } + + private static boolean isOptionalKind(XTypeElement type) { + return OPTIONAL_KIND_BY_CLASS_NAME.containsKey(type.getClassName()); + } + + private static OptionalKind of(XTypeElement type) { + return OPTIONAL_KIND_BY_CLASS_NAME.get(type.getClassName()); } /** Returns {@code valueType} wrapped in the correct class. */ public ParameterizedTypeName of(TypeName valueType) { - return ParameterizedTypeName.get(ClassName.get(clazz), valueType); + return ParameterizedTypeName.get(className, valueType); } /** Returns an expression for the absent/empty value. */ public CodeBlock absentValueExpression() { - return CodeBlock.of("$T.$L()", clazz, absentFactoryMethodName); + return CodeBlock.of("$T.$L()", className, absentMethodName); } /** * Returns an expression for the absent/empty value, parameterized with {@link #valueType()}. */ public CodeBlock parameterizedAbsentValueExpression(OptionalType optionalType) { - return CodeBlock.of("$T.<$T>$L()", clazz, optionalType.valueType(), absentFactoryMethodName); + return CodeBlock.of( + "$T.<$T>$L()", className, optionalType.valueType().getTypeName(), absentMethodName); } /** Returns an expression for the present {@code value}. */ public CodeBlock presentExpression(CodeBlock value) { - return CodeBlock.of("$T.of($L)", clazz, value); + return CodeBlock.of("$T.of($L)", className, value); } /** @@ -86,56 +99,36 @@ public abstract class OptionalType { * matter what type the value is. */ public CodeBlock presentObjectExpression(CodeBlock value) { - return CodeBlock.of("$T.<$T>of($L)", clazz, Object.class, value); + return CodeBlock.of("$T.<$T>of($L)", className, TypeName.OBJECT, value); } } - private static final TypeVisitor<Optional<OptionalKind>, Void> OPTIONAL_KIND = - new SimpleTypeVisitor8<Optional<OptionalKind>, Void>(Optional.empty()) { - @Override - public Optional<OptionalKind> visitDeclared(DeclaredType t, Void p) { - for (OptionalKind optionalKind : OptionalKind.values()) { - Name qualifiedName = MoreElements.asType(t.asElement()).getQualifiedName(); - if (qualifiedName.contentEquals(optionalKind.clazz.getCanonicalName())) { - return Optional.of(optionalKind); - } - } - return Optional.empty(); - } - }; - - /** - * The optional type itself, wrapped using {@link MoreTypes#equivalence()}. - * - * @deprecated Use {@link #declaredOptionalType()} instead. - */ - @Deprecated - protected abstract Equivalence.Wrapper<DeclaredType> wrappedDeclaredOptionalType(); + /** The optional type itself. */ + abstract TypeName typeName(); /** The optional type itself. */ - @SuppressWarnings("deprecation") - private DeclaredType declaredOptionalType() { - return wrappedDeclaredOptionalType().get(); + private XType type() { + return type; } /** Which {@code Optional} type is used. */ public OptionalKind kind() { - return declaredOptionalType().accept(OPTIONAL_KIND, null).get(); + return OptionalKind.of(type().getTypeElement()); } /** The value type. */ - public TypeMirror valueType() { - return declaredOptionalType().getTypeArguments().get(0); + public XType valueType() { + return type().getTypeArguments().get(0); } /** Returns {@code true} if {@code type} is an {@code Optional} type. */ - private static boolean isOptional(TypeMirror type) { - return type.accept(OPTIONAL_KIND, null).isPresent(); + private static boolean isOptional(XType type) { + return isDeclared(type) && OptionalKind.isOptionalKind(type.getTypeElement()); } /** Returns {@code true} if {@code key.type()} is an {@code Optional} type. */ public static boolean isOptional(Key key) { - return isOptional(key.type()); + return isOptional(key.type().xprocessing()); } /** @@ -143,9 +136,11 @@ public abstract class OptionalType { * * @throws IllegalArgumentException if {@code type} is not an {@code Optional} type */ - public static OptionalType from(TypeMirror type) { + public static OptionalType from(XType type) { checkArgument(isOptional(type), "%s must be an Optional", type); - return new AutoValue_OptionalType(MoreTypes.equivalence().wrap(MoreTypes.asDeclared(type))); + OptionalType optionalType = new AutoValue_OptionalType(type.getTypeName()); + optionalType.type = type; + return optionalType; } /** @@ -154,6 +149,6 @@ public abstract class OptionalType { * @throws IllegalArgumentException if {@code key.type()} is not an {@code Optional} type */ public static OptionalType from(Key key) { - return from(key.type()); + return from(key.type().xprocessing()); } } diff --git a/java/dagger/internal/codegen/base/ProducerAnnotations.java b/java/dagger/internal/codegen/base/ProducerAnnotations.java new file mode 100644 index 000000000..ad3b9b39f --- /dev/null +++ b/java/dagger/internal/codegen/base/ProducerAnnotations.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 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 androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XProcessingEnv; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.javapoet.TypeNames; + +/** + * Helper methods for getting types of producer annotations. + * + * <p>Note:These should only be used for cases where the annotations don't exist in the user's code. + * For example, all producer components implicitly have {@code @ProductionScope}, but it doesn't + * appear in the user's code. We need to get a reference to the scope annotation though to reuse + * classes from regular Dagger like the {@code ComponentDescriptor}. + */ +public final class ProducerAnnotations { + private static final ClassName ANNOTATION_USAGES = + ClassName.get("dagger.producers.internal", "AnnotationUsages"); + private static final ClassName PRODUCTION_USAGE = + ANNOTATION_USAGES.nestedClass("ProductionUsage"); + private static final ClassName PRODUCTION_IMPLEMENTATION_USAGE = + ANNOTATION_USAGES.nestedClass("ProductionImplementationUsage"); + private static final ClassName PRODUCTION_SCOPE_USAGE = + ANNOTATION_USAGES.nestedClass("ProductionScopeUsage"); + + /** Returns a {@link dagger.producers.internal.ProductionImplementation} qualifier. */ + // TODO(bcorso): We could probably remove the need for this if we define a new type, + // "ProductionImplementationExecutor", rather than binding "@ProductionImplementation Executor". + public static XAnnotation productionImplementationQualifier(XProcessingEnv processingEnv) { + return processingEnv.findTypeElement(PRODUCTION_IMPLEMENTATION_USAGE) + .getAnnotation(TypeNames.PRODUCTION_IMPLEMENTATION); + } + + /** Returns a {@link dagger.producers.Production} qualifier. */ + // TODO(bcorso): We could probably remove the need for this. It's currently only used in + // "DependsOnProductionExecutorValidator", but we could implement that without this. + public static XAnnotation productionQualifier(XProcessingEnv processingEnv) { + return processingEnv.findTypeElement(PRODUCTION_USAGE).getAnnotation(TypeNames.PRODUCTION); + } + + /** Returns a {@link dagger.producers.ProductionScope} scope. */ + // TODO(bcorso): We could probably remove the need for this, but it would require changing + // Dagger SPI's public API. In particular, Scope should probably only require a ClassName rather + // than an actual annotation type. + public static XAnnotation productionScope(XProcessingEnv processingEnv) { + return processingEnv.findTypeElement(PRODUCTION_SCOPE_USAGE) + .getAnnotation(TypeNames.PRODUCTION_SCOPE); + } + + private ProducerAnnotations() {} +} diff --git a/java/dagger/internal/codegen/base/RequestKinds.java b/java/dagger/internal/codegen/base/RequestKinds.java index 95d5ef4f9..b4a17647f 100644 --- a/java/dagger/internal/codegen/base/RequestKinds.java +++ b/java/dagger/internal/codegen/base/RequestKinds.java @@ -16,9 +16,9 @@ package dagger.internal.codegen.base; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.auto.common.MoreTypes.asDeclared; import static com.google.auto.common.MoreTypes.isType; -import static com.google.auto.common.MoreTypes.isTypeOf; import static com.google.common.base.Preconditions.checkArgument; import static dagger.internal.codegen.javapoet.TypeNames.lazyOf; import static dagger.internal.codegen.javapoet.TypeNames.listenableFutureOf; @@ -26,22 +26,22 @@ import static dagger.internal.codegen.javapoet.TypeNames.producedOf; import static dagger.internal.codegen.javapoet.TypeNames.producerOf; import static dagger.internal.codegen.javapoet.TypeNames.providerOf; import static dagger.internal.codegen.langmodel.DaggerTypes.checkTypePresent; -import static dagger.model.RequestKind.LAZY; -import static dagger.model.RequestKind.PRODUCED; -import static dagger.model.RequestKind.PRODUCER; -import static dagger.model.RequestKind.PROVIDER; -import static dagger.model.RequestKind.PROVIDER_OF_LAZY; +import static dagger.internal.codegen.langmodel.DaggerTypes.isTypeOf; +import static dagger.internal.codegen.langmodel.DaggerTypes.unwrapType; +import static dagger.spi.model.RequestKind.LAZY; +import static dagger.spi.model.RequestKind.PRODUCED; +import static dagger.spi.model.RequestKind.PRODUCER; +import static dagger.spi.model.RequestKind.PROVIDER; +import static dagger.spi.model.RequestKind.PROVIDER_OF_LAZY; import static javax.lang.model.type.TypeKind.DECLARED; +import androidx.room.compiler.processing.XType; import com.google.common.collect.ImmutableMap; -import com.google.common.util.concurrent.ListenableFuture; +import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; -import dagger.Lazy; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.RequestKind; -import dagger.producers.Produced; -import dagger.producers.Producer; -import javax.inject.Provider; +import dagger.spi.model.RequestKind; import javax.lang.model.type.TypeMirror; /** Utility methods for {@link RequestKind}s. */ @@ -55,13 +55,13 @@ public final class RequestKinds { return type; case PROVIDER_OF_LAZY: - return types.wrapType(requestType(LAZY, type, types), Provider.class); + return types.wrapType(requestType(LAZY, type, types), TypeNames.PROVIDER); case FUTURE: - return types.wrapType(type, ListenableFuture.class); + return types.wrapType(type, TypeNames.LISTENABLE_FUTURE); default: - return types.wrapType(type, frameworkClass(requestKind)); + return types.wrapType(type, frameworkClassName(requestKind)); } } @@ -94,12 +94,17 @@ public final class RequestKinds { } } - private static final ImmutableMap<RequestKind, Class<?>> FRAMEWORK_CLASSES = + private static final ImmutableMap<RequestKind, ClassName> FRAMEWORK_CLASSES = ImmutableMap.of( - PROVIDER, Provider.class, - LAZY, Lazy.class, - PRODUCER, Producer.class, - PRODUCED, Produced.class); + PROVIDER, TypeNames.PROVIDER, + LAZY, TypeNames.LAZY, + PRODUCER, TypeNames.PRODUCER, + PRODUCED, TypeNames.PRODUCED); + + /** Returns the {@link RequestKind} that matches the wrapping types (if any) of {@code type}. */ + public static RequestKind getRequestKind(XType type) { + return getRequestKind(toJavac(type)); + } /** Returns the {@link RequestKind} that matches the wrapping types (if any) of {@code type}. */ public static RequestKind getRequestKind(TypeMirror type) { @@ -112,8 +117,8 @@ public final class RequestKinds { return RequestKind.INSTANCE; } for (RequestKind kind : FRAMEWORK_CLASSES.keySet()) { - if (isTypeOf(frameworkClass(kind), type)) { - if (kind.equals(PROVIDER) && getRequestKind(DaggerTypes.unwrapType(type)).equals(LAZY)) { + if (isTypeOf(frameworkClassName(kind), type)) { + if (kind.equals(PROVIDER) && getRequestKind(unwrapType(type)).equals(LAZY)) { return PROVIDER_OF_LAZY; } return kind; @@ -131,6 +136,30 @@ public final class RequestKinds { * @throws IllegalArgumentException if {@code type} is not wrapped with {@code requestKind}'s * framework class(es). */ + public static XType extractKeyType(XType type) { + return extractKeyType(getRequestKind(type), type); + } + + private static XType extractKeyType(RequestKind requestKind, XType type) { + switch (requestKind) { + case INSTANCE: + return type; + case PROVIDER_OF_LAZY: + return extractKeyType(LAZY, extractKeyType(PROVIDER, type)); + default: + return unwrapType(type); + } + } + + /** + * Unwraps the framework class(es) of {@code requestKind} from {@code type}. If {@code + * requestKind} is {@link RequestKind#INSTANCE}, this acts as an identity function. + * + * @throws TypeNotPresentException if {@code type} is an {@link javax.lang.model.type.ErrorType}, + * which may mean that the type will be generated in a later round of processing + * @throws IllegalArgumentException if {@code type} is not wrapped with {@code requestKind}'s + * framework class(es). + */ public static TypeMirror extractKeyType(TypeMirror type) { return extractKeyType(getRequestKind(type), type); } @@ -143,13 +172,13 @@ public final class RequestKinds { return extractKeyType(LAZY, extractKeyType(PROVIDER, type)); default: checkArgument(isType(type)); - return DaggerTypes.unwrapType(type); + return unwrapType(type); } } /** * A dagger- or {@code javax.inject}-defined class for {@code requestKind} that that can wrap - * another type but share the same {@link dagger.model.Key}. + * another type but share the same {@link dagger.spi.model.Key}. * * <p>For example, {@code Provider<String>} and {@code Lazy<String>} can both be requested if a * key exists for {@code String}; they all share the same key. @@ -159,10 +188,10 @@ public final class RequestKinds { * classes, and {@link RequestKind#FUTURE} is wrapped with a {@link ListenableFuture}, but for * historical/implementation reasons has not had an associated framework class. */ - public static Class<?> frameworkClass(RequestKind requestKind) { - Class<?> result = FRAMEWORK_CLASSES.get(requestKind); - checkArgument(result != null, "no framework class for %s", requestKind); - return result; + public static ClassName frameworkClassName(RequestKind requestKind) { + checkArgument( + FRAMEWORK_CLASSES.containsKey(requestKind), "no framework class for %s", requestKind); + return FRAMEWORK_CLASSES.get(requestKind); } /** diff --git a/java/dagger/internal/codegen/base/Scopes.java b/java/dagger/internal/codegen/base/Scopes.java index f2c39cea0..b4cc323f0 100644 --- a/java/dagger/internal/codegen/base/Scopes.java +++ b/java/dagger/internal/codegen/base/Scopes.java @@ -16,48 +16,17 @@ package dagger.internal.codegen.base; -import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.DiagnosticFormatting.stripCommonTypePrefixes; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import com.google.auto.common.AnnotationMirrors; -import com.google.common.collect.ImmutableSet; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.model.Scope; -import dagger.producers.ProductionScope; -import java.lang.annotation.Annotation; -import java.util.Optional; -import javax.inject.Singleton; -import javax.lang.model.element.Element; +import androidx.room.compiler.processing.XProcessingEnv; +import dagger.spi.model.DaggerAnnotation; +import dagger.spi.model.Scope; /** Common names and convenience methods for {@link Scope}s. */ public final class Scopes { - - /** Returns a representation for {@link ProductionScope @ProductionScope} scope. */ - public static Scope productionScope(DaggerElements elements) { - return scope(elements, ProductionScope.class); - } - - /** Returns a representation for {@link Singleton @Singleton} scope. */ - public static Scope singletonScope(DaggerElements elements) { - return scope(elements, Singleton.class); - } - - /** - * Creates a {@link Scope} object from the {@link javax.inject.Scope}-annotated annotation type. - */ - private static Scope scope( - DaggerElements elements, Class<? extends Annotation> scopeAnnotationClass) { - return Scope.scope(SimpleAnnotationMirror.of(elements.getTypeElement(scopeAnnotationClass))); - } - - /** - * Returns at most one associated scoped annotation from the source code element, throwing an - * exception if there are more than one. - */ - public static Optional<Scope> uniqueScopeOf(Element element) { - // TODO(ronshapiro): Use MoreCollectors.toOptional() once we can use guava-jre - return Optional.ofNullable(getOnlyElement(scopesOf(element), null)); + /** Returns a representation for {@link dagger.producers.ProductionScope} scope. */ + public static Scope productionScope(XProcessingEnv processingEnv) { + return Scope.scope(DaggerAnnotation.from(ProducerAnnotations.productionScope(processingEnv))); } /** @@ -69,12 +38,4 @@ public final class Scopes { public static String getReadableSource(Scope scope) { return stripCommonTypePrefixes(scope.toString()); } - - /** Returns all of the associated scopes for a source code element. */ - public static ImmutableSet<Scope> scopesOf(Element element) { - return AnnotationMirrors.getAnnotatedAnnotations(element, javax.inject.Scope.class) - .stream() - .map(Scope::scope) - .collect(toImmutableSet()); - } } diff --git a/java/dagger/internal/codegen/base/SetType.java b/java/dagger/internal/codegen/base/SetType.java index a75a6d316..54d4228e4 100644 --- a/java/dagger/internal/codegen/base/SetType.java +++ b/java/dagger/internal/codegen/base/SetType.java @@ -16,105 +16,96 @@ package dagger.internal.codegen.base; +import static com.google.auto.common.MoreTypes.isType; import static com.google.common.base.Preconditions.checkArgument; +import static dagger.internal.codegen.langmodel.DaggerTypes.isTypeOf; +import static dagger.internal.codegen.langmodel.DaggerTypes.unwrapType; +import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XType; import com.google.auto.value.AutoValue; -import com.google.common.base.Equivalence; -import dagger.model.Key; -import java.util.Set; -import javax.lang.model.type.DeclaredType; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.Key; import javax.lang.model.type.TypeMirror; -/** - * Information about a {@link Set} {@link TypeMirror}. - */ +/** Information about a {@link java.util.Set} {@link TypeMirror}. */ @AutoValue public abstract class SetType { - /** - * The set type itself, wrapped using {@link MoreTypes#equivalence()}. Use - * {@link #declaredSetType()} instead. - */ - protected abstract Equivalence.Wrapper<DeclaredType> wrappedDeclaredSetType(); + private XType type; - /** - * The set type itself. - */ - private DeclaredType declaredSetType() { - return wrappedDeclaredSetType().get(); + /** The set type itself. */ + abstract TypeName typeName(); + + /** The set type itself. */ + private XType type() { + return type; } - /** - * {@code true} if the set type is the raw {@link Set} type. - */ + /** {@code true} if the set type is the raw {@link java.util.Set} type. */ public boolean isRawType() { - return declaredSetType().getTypeArguments().isEmpty(); + return type().getTypeArguments().isEmpty(); } - /** - * The element type. - */ - public TypeMirror elementType() { - return declaredSetType().getTypeArguments().get(0); + /** Returns the element type. */ + public XType elementType() { + return unwrapType(type()); } - /** - * {@code true} if {@link #elementType()} is a {@code clazz}. - */ - public boolean elementsAreTypeOf(Class<?> clazz) { - return MoreTypes.isType(elementType()) && MoreTypes.isTypeOf(clazz, elementType()); + /** Returns {@code true} if {@link #elementType()} is of type {@code className}. */ + public boolean elementsAreTypeOf(ClassName className) { + return !isRawType() && isTypeOf(elementType(), className); } /** * {@code T} if {@link #elementType()} is a {@code WrappingClass<T>}. * * @throws IllegalStateException if {@link #elementType()} is not a {@code WrappingClass<T>} - * @throws IllegalArgumentException if {@code wrappingClass} does not have exactly one type - * parameter */ - public TypeMirror unwrappedElementType(Class<?> wrappingClass) { - checkArgument( - wrappingClass.getTypeParameters().length == 1, - "%s must have exactly one type parameter", - wrappingClass); + // TODO(b/202033221): Consider using stricter input type, e.g. FrameworkType. + public XType unwrappedElementType(ClassName wrappingClass) { checkArgument( elementsAreTypeOf(wrappingClass), "expected elements to be %s, but this type is %s", wrappingClass, - declaredSetType()); - return MoreTypes.asDeclared(elementType()).getTypeArguments().get(0); + type()); + return unwrapType(elementType()); } - /** - * {@code true} if {@code type} is a {@link Set} type. - */ + /** {@code true} if {@code type} is a {@link java.util.Set} type. */ + public static boolean isSet(XType type) { + return isTypeOf(type, TypeNames.SET); + } + + /** {@code true} if {@code type} is a {@link java.util.Set} type. */ public static boolean isSet(TypeMirror type) { - return MoreTypes.isType(type) && MoreTypes.isTypeOf(Set.class, type); + return isType(type) && isTypeOf(TypeNames.SET, type); } - /** - * {@code true} if {@code key.type()} is a {@link Set} type. - */ + /** {@code true} if {@code key.type()} is a {@link java.util.Set} type. */ public static boolean isSet(Key key) { - return isSet(key.type()); + return isSet(key.type().xprocessing()); } /** * Returns a {@link SetType} for {@code type}. * - * @throws IllegalArgumentException if {@code type} is not a {@link Set} type + * @throws IllegalArgumentException if {@code type} is not a {@link java.util.Set} type */ - public static SetType from(TypeMirror type) { + public static SetType from(XType type) { checkArgument(isSet(type), "%s must be a Set", type); - return new AutoValue_SetType(MoreTypes.equivalence().wrap(MoreTypes.asDeclared(type))); + SetType setType = new AutoValue_SetType(type.getTypeName()); + setType.type = type; + return setType; } /** * Returns a {@link SetType} for {@code key}'s {@link Key#type() type}. * - * @throws IllegalArgumentException if {@code key.type()} is not a {@link Set} type + * @throws IllegalArgumentException if {@code key.type()} is not a {@link java.util.Set} type */ public static SetType from(Key key) { - return from (key.type()); + return from(key.type().xprocessing()); } } diff --git a/java/dagger/internal/codegen/base/SimpleAnnotationMirror.java b/java/dagger/internal/codegen/base/SimpleAnnotationMirror.java deleted file mode 100644 index 9690691eb..000000000 --- a/java/dagger/internal/codegen/base/SimpleAnnotationMirror.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2016 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.google.common.base.Preconditions.checkArgument; -import static javax.lang.model.util.ElementFilter.methodsIn; - -import com.google.auto.common.MoreTypes; -import com.google.common.base.Functions; -import com.google.common.base.Joiner; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; -import java.util.Map; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; - -/** A representation of an annotation. */ -public final class SimpleAnnotationMirror implements AnnotationMirror { - private final TypeElement annotationType; - private final ImmutableMap<String, ? extends AnnotationValue> namedValues; - private final ImmutableMap<ExecutableElement, ? extends AnnotationValue> elementValues; - - private SimpleAnnotationMirror( - TypeElement annotationType, Map<String, ? extends AnnotationValue> namedValues) { - checkArgument( - annotationType.getKind().equals(ElementKind.ANNOTATION_TYPE), - "annotationType must be an annotation: %s", - annotationType); - checkArgument( - FluentIterable.from(methodsIn(annotationType.getEnclosedElements())) - .transform(element -> element.getSimpleName().toString()) - .toSet() - .equals(namedValues.keySet()), - "namedValues must have values for exactly the members in %s: %s", - annotationType, - namedValues); - this.annotationType = annotationType; - this.namedValues = ImmutableMap.copyOf(namedValues); - this.elementValues = - Maps.toMap( - methodsIn(annotationType.getEnclosedElements()), - Functions.compose( - Functions.forMap(namedValues), element -> element.getSimpleName().toString())); - } - - @Override - public DeclaredType getAnnotationType() { - return MoreTypes.asDeclared(annotationType.asType()); - } - - @Override - public Map<ExecutableElement, ? extends AnnotationValue> getElementValues() { - return elementValues; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder("@").append(annotationType.getQualifiedName()); - if (!namedValues.isEmpty()) { - builder - .append('(') - .append(Joiner.on(", ").withKeyValueSeparator(" = ").join(namedValues)) - .append(')'); - } - return builder.toString(); - } - - /** - * An object representing an annotation instance. - * - * @param annotationType must be an annotation type with no members - */ - public static AnnotationMirror of(TypeElement annotationType) { - return of(annotationType, ImmutableMap.<String, AnnotationValue>of()); - } - - /** - * An object representing an annotation instance. - * - * @param annotationType must be an annotation type - * @param namedValues a value for every annotation member, including those with defaults, indexed - * by simple name - */ - private static AnnotationMirror of( - TypeElement annotationType, Map<String, ? extends AnnotationValue> namedValues) { - return new SimpleAnnotationMirror(annotationType, namedValues); - } -} diff --git a/java/dagger/internal/codegen/base/SimpleTypeAnnotationValue.java b/java/dagger/internal/codegen/base/SimpleTypeAnnotationValue.java deleted file mode 100644 index d595bcb70..000000000 --- a/java/dagger/internal/codegen/base/SimpleTypeAnnotationValue.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2016 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 javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.AnnotationValueVisitor; -import javax.lang.model.type.TypeMirror; - -/** An {@link AnnotationValue} that contains a {@link TypeMirror}. */ -final class SimpleTypeAnnotationValue implements AnnotationValue { - private final TypeMirror value; - - SimpleTypeAnnotationValue(TypeMirror value) { - this.value = value; - } - - @Override - public TypeMirror getValue() { - return value; - } - - @Override - public String toString() { - return value + ".class"; - } - - @Override - public <R, P> R accept(AnnotationValueVisitor<R, P> visitor, P parameter) { - return visitor.visitType(getValue(), parameter); - } -} diff --git a/java/dagger/internal/codegen/base/SourceFileGenerationException.java b/java/dagger/internal/codegen/base/SourceFileGenerationException.java index 5553dd85b..9ca72b835 100644 --- a/java/dagger/internal/codegen/base/SourceFileGenerationException.java +++ b/java/dagger/internal/codegen/base/SourceFileGenerationException.java @@ -19,10 +19,10 @@ package dagger.internal.codegen.base; import static com.google.common.base.Preconditions.checkNotNull; import static javax.tools.Diagnostic.Kind.ERROR; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMessager; import com.squareup.javapoet.ClassName; import java.util.Optional; -import javax.annotation.processing.Messager; -import javax.lang.model.element.Element; /** * An exception thrown to indicate that a source file could not be generated. @@ -32,10 +32,10 @@ import javax.lang.model.element.Element; * for that. */ public final class SourceFileGenerationException extends Exception { - private final Element associatedElement; + private final XElement associatedElement; SourceFileGenerationException( - Optional<ClassName> generatedClassName, Throwable cause, Element associatedElement) { + Optional<ClassName> generatedClassName, Throwable cause, XElement associatedElement) { super(createMessage(generatedClassName, cause.getMessage()), cause); this.associatedElement = checkNotNull(associatedElement); } @@ -48,7 +48,7 @@ public final class SourceFileGenerationException extends Exception { message); } - public void printMessageTo(Messager messager) { + public void printMessageTo(XMessager messager) { messager.printMessage(ERROR, getMessage(), associatedElement); } } diff --git a/java/dagger/internal/codegen/base/SourceFileGenerator.java b/java/dagger/internal/codegen/base/SourceFileGenerator.java index ada67d30d..d55ac1113 100644 --- a/java/dagger/internal/codegen/base/SourceFileGenerator.java +++ b/java/dagger/internal/codegen/base/SourceFileGenerator.java @@ -16,12 +16,17 @@ package dagger.internal.codegen.base; - +import static androidx.room.compiler.processing.JavaPoetExtKt.addOriginatingElement; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.compat.XConverters; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -34,10 +39,7 @@ import dagger.internal.codegen.javapoet.AnnotationSpecs; import dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression; import dagger.internal.codegen.langmodel.DaggerElements; import java.util.Optional; -import javax.annotation.processing.Filer; -import javax.annotation.processing.Messager; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; /** * A template class that provides a framework for properly handling IO while generating source files @@ -49,11 +51,11 @@ import javax.lang.model.element.Element; public abstract class SourceFileGenerator<T> { private static final String GENERATED_COMMENTS = "https://dagger.dev"; - private final Filer filer; + private final XFiler filer; private final DaggerElements elements; private final SourceVersion sourceVersion; - public SourceFileGenerator(Filer filer, DaggerElements elements, SourceVersion sourceVersion) { + public SourceFileGenerator(XFiler filer, DaggerElements elements, SourceVersion sourceVersion) { this.filer = checkNotNull(filer); this.elements = checkNotNull(elements); this.sourceVersion = checkNotNull(sourceVersion); @@ -67,7 +69,7 @@ public abstract class SourceFileGenerator<T> { * Generates a source file to be compiled for {@code T}. Writes any generation exception to {@code * messager} and does not throw. */ - public void generate(T input, Messager messager) { + public void generate(T input, XMessager messager) { try { generate(input); } catch (SourceFileGenerationException e) { @@ -79,7 +81,7 @@ public abstract class SourceFileGenerator<T> { public void generate(T input) throws SourceFileGenerationException { for (TypeSpec.Builder type : topLevelTypes(input)) { try { - buildJavaFile(input, type).writeTo(filer); + buildJavaFile(input, type).writeTo(XConverters.toJavac(filer)); } catch (Exception e) { // if the code above threw a SFGE, use that Throwables.propagateIfPossible(e, SourceFileGenerationException.class); @@ -90,7 +92,7 @@ public abstract class SourceFileGenerator<T> { } private JavaFile buildJavaFile(T input, TypeSpec.Builder typeSpecBuilder) { - typeSpecBuilder.addOriginatingElement(originatingElement(input)); + addOriginatingElement(typeSpecBuilder, originatingElement(input)); typeSpecBuilder.addAnnotation(DaggerGenerated.class); Optional<AnnotationSpec> generatedAnnotation = generatedAnnotation(elements, sourceVersion) @@ -112,7 +114,10 @@ public abstract class SourceFileGenerator<T> { JavaFile.Builder javaFileBuilder = JavaFile.builder( - elements.getPackageOf(originatingElement(input)).getQualifiedName().toString(), + elements + .getPackageOf(toJavac(originatingElement(input))) + .getQualifiedName() + .toString(), typeSpecBuilder.build()) .skipJavaLangImports(true); if (!generatedAnnotation.isPresent()) { @@ -122,7 +127,7 @@ public abstract class SourceFileGenerator<T> { } /** Returns the originating element of the generating type. */ - public abstract Element originatingElement(T input); + public abstract XElement originatingElement(T input); /** * Returns {@link TypeSpec.Builder types} be generated for {@code T}, or an empty list if no types diff --git a/java/dagger/internal/codegen/base/TarjanSCCs.java b/java/dagger/internal/codegen/base/TarjanSCCs.java new file mode 100644 index 000000000..ab9a0fdae --- /dev/null +++ b/java/dagger/internal/codegen/base/TarjanSCCs.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2021 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.google.common.base.Preconditions.checkState; +import static java.lang.Math.min; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.graph.SuccessorsFunction; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * An implementation of Tarjan's algorithm for finding the SCC of a graph. This is based on the + * psuedo code algorithm here: + * http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm + */ +public final class TarjanSCCs { + + /** Returns the set of strongly connected components in reverse topological order. */ + public static <NodeT> ImmutableSet<ImmutableSet<NodeT>> compute( + ImmutableCollection<NodeT> nodes, SuccessorsFunction<NodeT> successorsFunction) { + return new TarjanSCC<>(nodes, successorsFunction).compute(); + } + + private static class TarjanSCC<NodeT> { + private final ImmutableCollection<NodeT> nodes; + private final SuccessorsFunction<NodeT> successorsFunction; + private final Deque<NodeT> stack; + private final Set<NodeT> onStack; + private final Map<NodeT, Integer> indexes; + private final Map<NodeT, Integer> lowLinks; + private final List<ImmutableSet<NodeT>> stronglyConnectedComponents = new ArrayList<>(); + + TarjanSCC(ImmutableCollection<NodeT> nodes, SuccessorsFunction<NodeT> successorsFunction) { + this.nodes = nodes; + this.successorsFunction = successorsFunction; + this.stack = new ArrayDeque<>(nodes.size()); + this.onStack = Sets.newHashSetWithExpectedSize(nodes.size()); + this.indexes = Maps.newHashMapWithExpectedSize(nodes.size()); + this.lowLinks = Maps.newHashMapWithExpectedSize(nodes.size()); + } + + private ImmutableSet<ImmutableSet<NodeT>> compute() { + checkState(indexes.isEmpty(), "TarjanSCC#compute() can only be called once per instance!"); + for (NodeT node : nodes) { + if (!indexes.containsKey(node)) { + stronglyConnect(node); + } + } + return ImmutableSet.copyOf(stronglyConnectedComponents); + } + + private void stronglyConnect(NodeT node) { + // Set the index and lowLink for node to the smallest unused index and add it to the stack + lowLinks.put(node, indexes.size()); + indexes.put(node, indexes.size()); + stack.push(node); + onStack.add(node); + + for (NodeT successor : successorsFunction.successors(node)) { + if (!indexes.containsKey(successor)) { + // Successor has not been processed. + stronglyConnect(successor); + lowLinks.put(node, min(lowLinks.get(node), lowLinks.get(successor))); + } else if (onStack.contains(successor)) { + // Successor is on the stack and hence in the current SCC. + lowLinks.put(node, min(lowLinks.get(node), indexes.get(successor))); + } else { + // Successor is not on the stack and hence in an already processed SCC, so ignore. + } + } + + // If node is the root of the SCC, pop the stack until reaching the root to get all SCC nodes. + if (lowLinks.get(node).equals(indexes.get(node))) { + ImmutableSet.Builder<NodeT> scc = ImmutableSet.builder(); + NodeT currNode; + do { + currNode = stack.pop(); + onStack.remove(currNode); + scc.add(currNode); + } while (!node.equals(currNode)); + stronglyConnectedComponents.add(scc.build()); + } + } + } + + private TarjanSCCs() {} +} diff --git a/java/dagger/internal/codegen/binding/AnnotationExpression.java b/java/dagger/internal/codegen/binding/AnnotationExpression.java index de0aea586..82f282d9b 100644 --- a/java/dagger/internal/codegen/binding/AnnotationExpression.java +++ b/java/dagger/internal/codegen/binding/AnnotationExpression.java @@ -17,8 +17,10 @@ package dagger.internal.codegen.binding; import static com.google.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults; +import static com.google.auto.common.MoreTypes.asArray; import static dagger.internal.codegen.binding.SourceFiles.classFileName; import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; +import static dagger.internal.codegen.javapoet.TypeNames.rawTypeName; import static java.util.stream.Collectors.toList; import com.google.auto.common.MoreElements; @@ -26,17 +28,14 @@ import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.TypeName; import java.util.List; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleAnnotationValueVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.SimpleAnnotationValueVisitor8; /** * Returns an expression creating an instance of the visited annotation type. Its parameter must be @@ -50,7 +49,7 @@ import javax.lang.model.util.SimpleTypeVisitor6; * but in code it would have to be {@code new int[] {1, 2, 3}}. */ public class AnnotationExpression - extends SimpleAnnotationValueVisitor6<CodeBlock, AnnotationValue> { + extends SimpleAnnotationValueVisitor8<CodeBlock, AnnotationValue> { private final AnnotationMirror annotation; private final ClassName creatorClass; @@ -104,7 +103,10 @@ public class AnnotationExpression * annotation}. */ CodeBlock getValueExpression(TypeMirror valueType, AnnotationValue value) { - return ARRAY_LITERAL_PREFIX.visit(valueType, this.visit(value, value)); + CodeBlock codeBlock = visit(value, value); + return valueType.getKind() == TypeKind.ARRAY + ? CodeBlock.of("new $T[] $L", rawTypeName(asArray(valueType).getComponentType()), codeBlock) + : codeBlock; } @Override @@ -170,39 +172,4 @@ public class AnnotationExpression } return CodeBlock.of("{$L}", makeParametersCodeBlock(codeBlocks.build())); } - - /** - * If the visited type is an array, prefixes the parameter code block with {@code new T[]}, where - * {@code T} is the raw array component type. - */ - private static final SimpleTypeVisitor6<CodeBlock, CodeBlock> ARRAY_LITERAL_PREFIX = - new SimpleTypeVisitor6<CodeBlock, CodeBlock>() { - - @Override - public CodeBlock visitArray(ArrayType t, CodeBlock p) { - return CodeBlock.of("new $T[] $L", RAW_TYPE_NAME.visit(t.getComponentType()), p); - } - - @Override - protected CodeBlock defaultAction(TypeMirror e, CodeBlock p) { - return p; - } - }; - - /** - * If the visited type is an array, returns the name of its raw component type; otherwise returns - * the name of the type itself. - */ - private static final SimpleTypeVisitor6<TypeName, Void> RAW_TYPE_NAME = - new SimpleTypeVisitor6<TypeName, Void>() { - @Override - public TypeName visitDeclared(DeclaredType t, Void p) { - return ClassName.get(MoreTypes.asTypeElement(t)); - } - - @Override - protected TypeName defaultAction(TypeMirror e, Void p) { - return TypeName.get(e); - } - }; } diff --git a/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java b/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java index f9929aef6..49ecd88e8 100644 --- a/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java +++ b/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java @@ -16,25 +16,28 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.MoreElements.asExecutable; -import static com.google.auto.common.MoreElements.isAnnotationPresent; -import static com.google.auto.common.MoreTypes.asDeclared; -import static com.google.auto.common.MoreTypes.asExecutable; -import static com.google.auto.common.MoreTypes.asTypeElement; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.internal.codegen.base.MoreAnnotationValues.getStringValue; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror; -import static javax.lang.model.element.Modifier.ABSTRACT; -import static javax.lang.model.util.ElementFilter.constructorsIn; - +import static dagger.internal.codegen.langmodel.DaggerElements.isAnnotationPresent; +import static dagger.internal.codegen.xprocessing.XElements.asConstructor; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static kotlin.streams.jdk8.StreamsKt.asStream; + +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XConstructorType; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XHasModifiers; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.XVariableElement; import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; -import com.google.common.base.Equivalence; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -42,46 +45,36 @@ import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; -import dagger.assisted.AssistedInject; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.BindingKind; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.BindingKind; import java.util.List; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; +import java.util.Optional; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.TypeMirror; /** Assisted injection utility methods. */ public final class AssistedInjectionAnnotations { - /** Returns the factory method for the given factory {@link TypeElement}. */ - public static ExecutableElement assistedFactoryMethod( - TypeElement factory, DaggerElements elements) { - return getOnlyElement(assistedFactoryMethods(factory, elements)); + /** Returns the factory method for the given factory {@link XTypeElement}. */ + public static XMethodElement assistedFactoryMethod(XTypeElement factory) { + return getOnlyElement(assistedFactoryMethods(factory)); } - /** Returns the list of abstract factory methods for the given factory {@link TypeElement}. */ - public static ImmutableSet<ExecutableElement> assistedFactoryMethods( - TypeElement factory, DaggerElements elements) { - return elements.getLocalAndInheritedMethods(factory).stream() - .filter(method -> method.getModifiers().contains(ABSTRACT)) - .filter(method -> !method.isDefault()) + /** Returns the list of abstract factory methods for the given factory {@link XTypeElement}. */ + public static ImmutableSet<XMethodElement> assistedFactoryMethods(XTypeElement factory) { + return asStream(factory.getAllNonPrivateInstanceMethods()) + .filter(XHasModifiers::isAbstract) + .filter(method -> !method.isJavaDefault()) .collect(toImmutableSet()); } /** Returns {@code true} if the element uses assisted injection. */ - public static boolean isAssistedInjectionType(TypeElement typeElement) { - ImmutableSet<ExecutableElement> injectConstructors = assistedInjectedConstructors(typeElement); - return !injectConstructors.isEmpty() - && isAnnotationPresent(getOnlyElement(injectConstructors), AssistedInject.class); + public static boolean isAssistedInjectionType(XTypeElement typeElement) { + return assistedInjectedConstructors(typeElement).stream() + .anyMatch(constructor -> constructor.hasAnnotation(TypeNames.ASSISTED_INJECT)); } /** Returns {@code true} if this binding is an assisted factory. */ - public static boolean isAssistedFactoryType(Element element) { - return isAnnotationPresent(element, AssistedFactory.class); + public static boolean isAssistedFactoryType(XElement element) { + return element.hasAnnotation(TypeNames.ASSISTED_FACTORY); } /** @@ -91,25 +84,22 @@ public final class AssistedInjectionAnnotations { * of each parameter will be the name given in the {@link * dagger.assisted.AssistedInject}-annotated constructor. */ - public static ImmutableList<ParameterSpec> assistedParameterSpecs( - Binding binding, DaggerTypes types) { + public static ImmutableList<ParameterSpec> assistedParameterSpecs(Binding binding) { checkArgument(binding.kind() == BindingKind.ASSISTED_INJECTION); - ExecutableElement constructor = asExecutable(binding.bindingElement().get()); - ExecutableType constructorType = - asExecutable(types.asMemberOf(asDeclared(binding.key().type()), constructor)); + XConstructorElement constructor = asConstructor(binding.bindingElement().get()); + XConstructorType constructorType = constructor.asMemberOf(binding.key().type().xprocessing()); return assistedParameterSpecs(constructor.getParameters(), constructorType.getParameterTypes()); } private static ImmutableList<ParameterSpec> assistedParameterSpecs( - List<? extends VariableElement> paramElements, List<? extends TypeMirror> paramTypes) { + List<? extends XVariableElement> paramElements, List<XType> paramTypes) { ImmutableList.Builder<ParameterSpec> assistedParameterSpecs = ImmutableList.builder(); for (int i = 0; i < paramElements.size(); i++) { - VariableElement paramElement = paramElements.get(i); - TypeMirror paramType = paramTypes.get(i); + XVariableElement paramElement = paramElements.get(i); + XType paramType = paramTypes.get(i); if (isAssistedParameter(paramElement)) { assistedParameterSpecs.add( - ParameterSpec.builder(TypeName.get(paramType), paramElement.getSimpleName().toString()) - .build()); + ParameterSpec.builder(paramType.getTypeName(), getSimpleName(paramElement)).build()); } } return assistedParameterSpecs.build(); @@ -122,14 +112,13 @@ public final class AssistedInjectionAnnotations { * of each parameter will be the name given in the {@link * dagger.assisted.AssistedInject}-annotated constructor. */ - public static ImmutableList<ParameterSpec> assistedFactoryParameterSpecs( - Binding binding, DaggerElements elements, DaggerTypes types) { + public static ImmutableList<ParameterSpec> assistedFactoryParameterSpecs(Binding binding) { checkArgument(binding.kind() == BindingKind.ASSISTED_FACTORY); - AssistedFactoryMetadata metadata = - AssistedFactoryMetadata.create(binding.bindingElement().get().asType(), elements, types); - ExecutableType factoryMethodType = - asExecutable(types.asMemberOf(asDeclared(binding.key().type()), metadata.factoryMethod())); + XTypeElement factory = asTypeElement(binding.bindingElement().get()); + AssistedFactoryMetadata metadata = AssistedFactoryMetadata.create(factory.getType()); + XMethodType factoryMethodType = + metadata.factoryMethod().asMemberOf(binding.key().type().xprocessing()); return assistedParameterSpecs( // Use the order of the parameters from the @AssistedFactory method but use the parameter // names of the @AssistedInject constructor. @@ -140,81 +129,82 @@ public final class AssistedInjectionAnnotations { } /** Returns the constructors in {@code type} that are annotated with {@link AssistedInject}. */ - public static ImmutableSet<ExecutableElement> assistedInjectedConstructors(TypeElement type) { - return constructorsIn(type.getEnclosedElements()).stream() - .filter(constructor -> isAnnotationPresent(constructor, AssistedInject.class)) + public static ImmutableSet<XConstructorElement> assistedInjectedConstructors(XTypeElement type) { + return type.getConstructors().stream() + .filter(constructor -> constructor.hasAnnotation(TypeNames.ASSISTED_INJECT)) .collect(toImmutableSet()); } - public static ImmutableList<VariableElement> assistedParameters(Binding binding) { + public static ImmutableList<XVariableElement> assistedParameters(Binding binding) { return binding.kind() == BindingKind.ASSISTED_INJECTION - ? assistedParameters(asExecutable(binding.bindingElement().get())) + ? asConstructor(binding.bindingElement().get()).getParameters().stream() + .filter(AssistedInjectionAnnotations::isAssistedParameter) + .collect(toImmutableList()) : ImmutableList.of(); } - private static ImmutableList<VariableElement> assistedParameters(ExecutableElement constructor) { - return constructor.getParameters().stream() - .filter(AssistedInjectionAnnotations::isAssistedParameter) - .collect(toImmutableList()); + /** Returns {@code true} if this binding is uses assisted injection. */ + public static boolean isAssistedParameter(XVariableElement param) { + return param.hasAnnotation(TypeNames.ASSISTED); } /** Returns {@code true} if this binding is uses assisted injection. */ public static boolean isAssistedParameter(VariableElement param) { - return isAnnotationPresent(MoreElements.asVariable(param), Assisted.class); + return isAnnotationPresent(MoreElements.asVariable(param), TypeNames.ASSISTED); } /** Metadata about an {@link dagger.assisted.AssistedFactory} annotated type. */ @AutoValue public abstract static class AssistedFactoryMetadata { - public static AssistedFactoryMetadata create( - TypeMirror factory, DaggerElements elements, DaggerTypes types) { - DeclaredType factoryType = asDeclared(factory); - TypeElement factoryElement = asTypeElement(factoryType); - ExecutableElement factoryMethod = assistedFactoryMethod(factoryElement, elements); - ExecutableType factoryMethodType = asExecutable(types.asMemberOf(factoryType, factoryMethod)); - DeclaredType assistedInjectType = asDeclared(factoryMethodType.getReturnType()); + public static AssistedFactoryMetadata create(XType factoryType) { + XTypeElement factoryElement = factoryType.getTypeElement(); + XMethodElement factoryMethod = assistedFactoryMethod(factoryElement); + XMethodType factoryMethodType = factoryMethod.asMemberOf(factoryType); + XType assistedInjectType = factoryMethodType.getReturnType(); + XTypeElement assistedInjectElement = assistedInjectType.getTypeElement(); return new AutoValue_AssistedInjectionAnnotations_AssistedFactoryMetadata( factoryElement, factoryType, factoryMethod, factoryMethodType, - asTypeElement(assistedInjectType), + assistedInjectElement, assistedInjectType, - AssistedInjectionAnnotations.assistedInjectAssistedParameters(assistedInjectType, types), + AssistedInjectionAnnotations.assistedInjectAssistedParameters(assistedInjectType), AssistedInjectionAnnotations.assistedFactoryAssistedParameters( factoryMethod, factoryMethodType)); } - public abstract TypeElement factory(); + public abstract XTypeElement factory(); - public abstract DeclaredType factoryType(); + public abstract XType factoryType(); - public abstract ExecutableElement factoryMethod(); + public abstract XMethodElement factoryMethod(); - public abstract ExecutableType factoryMethodType(); + public abstract XMethodType factoryMethodType(); - public abstract TypeElement assistedInjectElement(); + public abstract XTypeElement assistedInjectElement(); - public abstract DeclaredType assistedInjectType(); + public abstract XType assistedInjectType(); public abstract ImmutableList<AssistedParameter> assistedInjectAssistedParameters(); public abstract ImmutableList<AssistedParameter> assistedFactoryAssistedParameters(); @Memoized - public ImmutableMap<AssistedParameter, VariableElement> assistedInjectAssistedParametersMap() { - ImmutableMap.Builder<AssistedParameter, VariableElement> builder = ImmutableMap.builder(); + public ImmutableMap<AssistedParameter, XVariableElement> assistedInjectAssistedParametersMap() { + ImmutableMap.Builder<AssistedParameter, XVariableElement> builder = ImmutableMap.builder(); for (AssistedParameter assistedParameter : assistedInjectAssistedParameters()) { - builder.put(assistedParameter, assistedParameter.variableElement); + builder.put(assistedParameter, assistedParameter.element()); } return builder.build(); } @Memoized - public ImmutableMap<AssistedParameter, VariableElement> assistedFactoryAssistedParametersMap() { - ImmutableMap.Builder<AssistedParameter, VariableElement> builder = ImmutableMap.builder(); + public ImmutableMap<AssistedParameter, XVariableElement> + assistedFactoryAssistedParametersMap() { + ImmutableMap.Builder<AssistedParameter, XVariableElement> builder = ImmutableMap.builder(); for (AssistedParameter assistedParameter : assistedFactoryAssistedParameters()) { - builder.put(assistedParameter, assistedParameter.variableElement); + builder.put(assistedParameter, assistedParameter.element()); } return builder.build(); } @@ -228,32 +218,34 @@ public final class AssistedInjectionAnnotations { */ @AutoValue public abstract static class AssistedParameter { - public static AssistedParameter create(VariableElement parameter, TypeMirror parameterType) { + public static AssistedParameter create(XVariableElement parameter, XType parameterType) { AssistedParameter assistedParameter = new AutoValue_AssistedInjectionAnnotations_AssistedParameter( - getAnnotationMirror(parameter, Assisted.class) - .map(assisted -> getStringValue(assisted, "value")) + Optional.ofNullable(parameter.getAnnotation(TypeNames.ASSISTED)) + .map(assisted -> assisted.getAsString("value")) .orElse(""), - MoreTypes.equivalence().wrap(parameterType)); - assistedParameter.variableElement = parameter; + parameterType.getTypeName()); + assistedParameter.parameterElement = parameter; + assistedParameter.parameterType = parameterType; return assistedParameter; } - private VariableElement variableElement; + private XVariableElement parameterElement; + private XType parameterType; /** Returns the string qualifier from the {@link Assisted#value()}. */ public abstract String qualifier(); - /** Returns the wrapper for the type annotated with {@link Assisted}. */ - public abstract Equivalence.Wrapper<TypeMirror> wrappedType(); + /** Returns the type annotated with {@link Assisted}. */ + abstract TypeName typeName(); /** Returns the type annotated with {@link Assisted}. */ - public final TypeMirror type() { - return wrappedType().get(); + public final XType type() { + return parameterType; } - public final VariableElement variableElement() { - return variableElement; + public final XVariableElement element() { + return parameterElement; } @Override @@ -265,31 +257,31 @@ public final class AssistedInjectionAnnotations { } public static ImmutableList<AssistedParameter> assistedInjectAssistedParameters( - DeclaredType assistedInjectType, DaggerTypes types) { + XType assistedInjectType) { // We keep track of the constructor both as an ExecutableElement to access @Assisted // parameters and as an ExecutableType to access the resolved parameter types. - ExecutableElement assistedInjectConstructor = - getOnlyElement(assistedInjectedConstructors(asTypeElement(assistedInjectType))); - ExecutableType assistedInjectConstructorType = - asExecutable(types.asMemberOf(assistedInjectType, assistedInjectConstructor)); + XConstructorElement assistedInjectConstructor = + getOnlyElement(assistedInjectedConstructors(assistedInjectType.getTypeElement())); + XConstructorType assistedInjectConstructorType = + assistedInjectConstructor.asMemberOf(assistedInjectType); ImmutableList.Builder<AssistedParameter> builder = ImmutableList.builder(); for (int i = 0; i < assistedInjectConstructor.getParameters().size(); i++) { - VariableElement parameter = assistedInjectConstructor.getParameters().get(i); - TypeMirror parameterType = assistedInjectConstructorType.getParameterTypes().get(i); - if (isAnnotationPresent(parameter, Assisted.class)) { + XVariableElement parameter = assistedInjectConstructor.getParameters().get(i); + XType parameterType = assistedInjectConstructorType.getParameterTypes().get(i); + if (parameter.hasAnnotation(TypeNames.ASSISTED)) { builder.add(AssistedParameter.create(parameter, parameterType)); } } return builder.build(); } - public static ImmutableList<AssistedParameter> assistedFactoryAssistedParameters( - ExecutableElement factoryMethod, ExecutableType factoryMethodType) { + private static ImmutableList<AssistedParameter> assistedFactoryAssistedParameters( + XMethodElement factoryMethod, XMethodType factoryMethodType) { ImmutableList.Builder<AssistedParameter> builder = ImmutableList.builder(); for (int i = 0; i < factoryMethod.getParameters().size(); i++) { - VariableElement parameter = factoryMethod.getParameters().get(i); - TypeMirror parameterType = factoryMethodType.getParameterTypes().get(i); + XVariableElement parameter = factoryMethod.getParameters().get(i); + XType parameterType = factoryMethodType.getParameterTypes().get(i); builder.add(AssistedParameter.create(parameter, parameterType)); } return builder.build(); diff --git a/java/dagger/internal/codegen/binding/BUILD b/java/dagger/internal/codegen/binding/BUILD index ff8db8f60..54211d858 100644 --- a/java/dagger/internal/codegen/binding/BUILD +++ b/java/dagger/internal/codegen/binding/BUILD @@ -32,16 +32,17 @@ java_library( "//java/dagger/internal/codegen/javapoet", "//java/dagger/internal/codegen/kotlin", "//java/dagger/internal/codegen/langmodel", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", - "//java/dagger/internal/guava:graph", - "//java/dagger/model:internal-proxies", + "//java/dagger/internal/codegen/xprocessing", "//java/dagger/producers", "//java/dagger/spi", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/error_prone:annotations", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/error_prone:annotations", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/graph", + "//third_party/java/guava/util/concurrent", + "//third_party/java/javapoet", + "@maven//:org_jetbrains_kotlin_kotlin_stdlib_jdk8", ], ) diff --git a/java/dagger/internal/codegen/binding/Binding.java b/java/dagger/internal/codegen/binding/Binding.java index 0d4eef65e..47d5d40de 100644 --- a/java/dagger/internal/codegen/binding/Binding.java +++ b/java/dagger/internal/codegen/binding/Binding.java @@ -16,34 +16,25 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Suppliers.memoize; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.STATIC; import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.BindingKind; -import dagger.model.DependencyRequest; -import dagger.model.Scope; -import java.util.List; +import dagger.spi.model.BindingKind; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Scope; import java.util.Optional; import java.util.Set; -import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleTypeVisitor6; /** - * An abstract type for classes representing a Dagger binding. Particularly, contains the {@link - * Element} that generated the binding and the {@link DependencyRequest} instances that are required - * to satisfy the binding, but leaves the specifics of the <i>mechanism</i> of the binding to the + * An abstract type for classes representing a Dagger binding. Particularly, contains the element + * that generated the binding and the {@link DependencyRequest} instances that are required to + * satisfy the binding, but leaves the specifics of the <i>mechanism</i> of the binding to the * subtypes. */ public abstract class Binding extends BindingDeclaration { @@ -56,7 +47,7 @@ public abstract class Binding extends BindingDeclaration { if (!bindingElement().isPresent() || !contributingModule().isPresent()) { return false; } - Set<Modifier> modifiers = bindingElement().get().getModifiers(); + Set<Modifier> modifiers = toJavac(bindingElement().get()).getModifiers(); return !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC); } @@ -121,45 +112,4 @@ public abstract class Binding extends BindingDeclaration { public Optional<Scope> scope() { return Optional.empty(); } - - // TODO(sameb): Remove the TypeElement parameter and pull it from the TypeMirror. - static boolean hasNonDefaultTypeParameters( - TypeElement element, TypeMirror type, DaggerTypes types) { - // If the element has no type parameters, nothing can be wrong. - if (element.getTypeParameters().isEmpty()) { - return false; - } - - List<TypeMirror> defaultTypes = Lists.newArrayList(); - for (TypeParameterElement parameter : element.getTypeParameters()) { - defaultTypes.add(parameter.asType()); - } - - List<TypeMirror> actualTypes = - type.accept( - new SimpleTypeVisitor6<List<TypeMirror>, Void>() { - @Override - protected List<TypeMirror> defaultAction(TypeMirror e, Void p) { - return ImmutableList.of(); - } - - @Override - public List<TypeMirror> visitDeclared(DeclaredType t, Void p) { - return ImmutableList.<TypeMirror>copyOf(t.getTypeArguments()); - } - }, - null); - - // The actual type parameter size can be different if the user is using a raw type. - if (defaultTypes.size() != actualTypes.size()) { - return true; - } - - for (int i = 0; i < defaultTypes.size(); i++) { - if (!types.isSameType(defaultTypes.get(i), actualTypes.get(i))) { - return true; - } - } - return false; - } } diff --git a/java/dagger/internal/codegen/binding/BindingDeclaration.java b/java/dagger/internal/codegen/binding/BindingDeclaration.java index 712260fb7..353921ace 100644 --- a/java/dagger/internal/codegen/binding/BindingDeclaration.java +++ b/java/dagger/internal/codegen/binding/BindingDeclaration.java @@ -16,16 +16,17 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static dagger.internal.codegen.extension.Optionals.emptiesLast; import static java.util.Comparator.comparing; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.model.BindingKind; -import dagger.model.Key; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XTypeElement; +import dagger.internal.codegen.xprocessing.XElements; +import dagger.spi.model.BindingKind; +import dagger.spi.model.Key; import java.util.Comparator; import java.util.Optional; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; /** An object that declares or specifies a binding. */ public abstract class BindingDeclaration { @@ -48,18 +49,18 @@ public abstract class BindingDeclaration { declaration.contributingModule().isPresent() ? declaration.contributingModule() : declaration.bindingTypeElement(), - emptiesLast(comparing((TypeElement type) -> type.getQualifiedName().toString()))) + emptiesLast(comparing(XTypeElement::getQualifiedName))) .thenComparing( (BindingDeclaration declaration) -> declaration.bindingElement(), emptiesLast( - comparing((Element element) -> element.getSimpleName().toString()) - .thenComparing((Element element) -> element.asType().toString()))); + comparing((XElement element) -> toJavac(element).getSimpleName().toString()) + .thenComparing((XElement element) -> toJavac(element).asType().toString()))); /** The {@link Key} of this declaration. */ public abstract Key key(); /** - * The {@link Element} that declares this binding. Absent for {@linkplain BindingKind binding + * The {@link XElement} that declares this binding. Absent for {@linkplain BindingKind binding * kinds} that are not always declared by exactly one element. * * <p>For example, consider {@link BindingKind#MULTIBOUND_SET}. A component with many @@ -68,14 +69,14 @@ public abstract class BindingDeclaration { * contribute a synthetic binding, but since multiple {@code @Multibinds} methods can coexist in * the same component (and contribute to one single binding), it has no binding element. */ - public abstract Optional<Element> bindingElement(); + public abstract Optional<XElement> bindingElement(); /** * The type enclosing the {@link #bindingElement()}, or {@link Optional#empty()} if {@link * #bindingElement()} is empty. */ - public final Optional<TypeElement> bindingTypeElement() { - return bindingElement().map(DaggerElements::closestEnclosingTypeElement); + public final Optional<XTypeElement> bindingTypeElement() { + return bindingElement().map(XElements::closestEnclosingTypeElement); } /** @@ -83,5 +84,5 @@ public abstract class BindingDeclaration { * the class that contains {@link #bindingElement()}. Absent if {@link #bindingElement()} is * empty. */ - public abstract Optional<TypeElement> contributingModule(); + public abstract Optional<XTypeElement> contributingModule(); } diff --git a/java/dagger/internal/codegen/binding/BindingDeclarationFormatter.java b/java/dagger/internal/codegen/binding/BindingDeclarationFormatter.java index 84764973b..a69ddaa6b 100644 --- a/java/dagger/internal/codegen/binding/BindingDeclarationFormatter.java +++ b/java/dagger/internal/codegen/binding/BindingDeclarationFormatter.java @@ -16,30 +16,24 @@ package dagger.internal.codegen.binding; -import static com.google.common.collect.Sets.immutableEnumSet; +import static androidx.room.compiler.processing.XElementKt.isMethodParameter; +import static androidx.room.compiler.processing.XElementKt.isTypeElement; import static dagger.internal.codegen.base.DiagnosticFormatting.stripCommonTypePrefixes; import static dagger.internal.codegen.base.ElementFormatter.elementToString; -import static javax.lang.model.element.ElementKind.PARAMETER; -import static javax.lang.model.type.TypeKind.DECLARED; -import static javax.lang.model.type.TypeKind.EXECUTABLE; +import static dagger.internal.codegen.xprocessing.XElements.asExecutable; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.isExecutable; -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import dagger.internal.codegen.base.Formatter; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; /** * Formats a {@link BindingDeclaration} into a {@link String} suitable for use in error messages. */ public final class BindingDeclarationFormatter extends Formatter<BindingDeclaration> { - private static final ImmutableSet<TypeKind> FORMATTABLE_ELEMENT_TYPE_KINDS = - immutableEnumSet(EXECUTABLE, DECLARED); - private final MethodSignatureFormatter methodSignatureFormatter; @Inject @@ -57,9 +51,10 @@ public final class BindingDeclarationFormatter extends Formatter<BindingDeclarat return true; } if (bindingDeclaration.bindingElement().isPresent()) { - Element bindingElement = bindingDeclaration.bindingElement().get(); - return bindingElement.getKind().equals(PARAMETER) - || FORMATTABLE_ELEMENT_TYPE_KINDS.contains(bindingElement.asType().getKind()); + XElement bindingElement = bindingDeclaration.bindingElement().get(); + return isMethodParameter(bindingElement) + || isTypeElement(bindingElement) + || isExecutable(bindingElement); } // TODO(dpb): validate whether what this is doing is correct return false; @@ -72,26 +67,17 @@ public final class BindingDeclarationFormatter extends Formatter<BindingDeclarat } if (bindingDeclaration.bindingElement().isPresent()) { - Element bindingElement = bindingDeclaration.bindingElement().get(); - if (bindingElement.getKind().equals(PARAMETER)) { + XElement bindingElement = bindingDeclaration.bindingElement().get(); + if (isMethodParameter(bindingElement)) { return elementToString(bindingElement); + } else if (isTypeElement(bindingElement)) { + return stripCommonTypePrefixes(asTypeElement(bindingElement).getType().toString()); + } else if (isExecutable(bindingElement)) { + return methodSignatureFormatter.format( + asExecutable(bindingElement), + bindingDeclaration.contributingModule().map(XTypeElement::getType)); } - - switch (bindingElement.asType().getKind()) { - case EXECUTABLE: - return methodSignatureFormatter.format( - MoreElements.asExecutable(bindingElement), - bindingDeclaration - .contributingModule() - .map(module -> MoreTypes.asDeclared(module.asType()))); - - case DECLARED: - return stripCommonTypePrefixes(bindingElement.asType().toString()); - - default: - throw new IllegalArgumentException( - "Formatting unsupported for element: " + bindingElement); - } + throw new IllegalArgumentException("Formatting unsupported for element: " + bindingElement); } return String.format( @@ -100,7 +86,7 @@ public final class BindingDeclarationFormatter extends Formatter<BindingDeclarat } private String formatSubcomponentDeclaration(SubcomponentDeclaration subcomponentDeclaration) { - ImmutableList<TypeElement> moduleSubcomponents = + ImmutableList<XTypeElement> moduleSubcomponents = subcomponentDeclaration.moduleAnnotation().subcomponents(); int index = moduleSubcomponents.indexOf(subcomponentDeclaration.subcomponentType()); StringBuilder annotationValue = new StringBuilder(); @@ -118,7 +104,7 @@ public final class BindingDeclarationFormatter extends Formatter<BindingDeclarat return String.format( "@%s(subcomponents = %s) for %s", - subcomponentDeclaration.moduleAnnotation().annotationName(), + subcomponentDeclaration.moduleAnnotation().simpleName(), annotationValue, subcomponentDeclaration.contributingModule().get()); } diff --git a/java/dagger/internal/codegen/binding/BindingFactory.java b/java/dagger/internal/codegen/binding/BindingFactory.java index eb7c1322a..5146c35e1 100644 --- a/java/dagger/internal/codegen/binding/BindingFactory.java +++ b/java/dagger/internal/codegen/binding/BindingFactory.java @@ -16,69 +16,68 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.MoreElements.isAnnotationPresent; -import static com.google.auto.common.MoreTypes.asDeclared; +import static androidx.room.compiler.processing.XElementKt.isMethod; +import static androidx.room.compiler.processing.XElementKt.isTypeElement; +import static androidx.room.compiler.processing.XElementKt.isVariableElement; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.MoreAnnotationMirrors.wrapOptionalInEquivalence; -import static dagger.internal.codegen.base.Scopes.uniqueScopeOf; -import static dagger.internal.codegen.binding.Binding.hasNonDefaultTypeParameters; import static dagger.internal.codegen.binding.ComponentDescriptor.isComponentProductionMethod; import static dagger.internal.codegen.binding.ConfigurationAnnotations.getNullableType; -import static dagger.internal.codegen.binding.ContributionBinding.bindingKindForMultibindingKey; import static dagger.internal.codegen.binding.MapKeys.getMapKey; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import static dagger.model.BindingKind.ASSISTED_FACTORY; -import static dagger.model.BindingKind.ASSISTED_INJECTION; -import static dagger.model.BindingKind.BOUND_INSTANCE; -import static dagger.model.BindingKind.COMPONENT; -import static dagger.model.BindingKind.COMPONENT_DEPENDENCY; -import static dagger.model.BindingKind.COMPONENT_PRODUCTION; -import static dagger.model.BindingKind.COMPONENT_PROVISION; -import static dagger.model.BindingKind.DELEGATE; -import static dagger.model.BindingKind.INJECTION; -import static dagger.model.BindingKind.MEMBERS_INJECTOR; -import static dagger.model.BindingKind.OPTIONAL; -import static dagger.model.BindingKind.PRODUCTION; -import static dagger.model.BindingKind.PROVISION; -import static dagger.model.BindingKind.SUBCOMPONENT_CREATOR; -import static javax.lang.model.element.ElementKind.CONSTRUCTOR; -import static javax.lang.model.element.ElementKind.METHOD; - -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.asVariable; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.spi.model.BindingKind.ASSISTED_FACTORY; +import static dagger.spi.model.BindingKind.ASSISTED_INJECTION; +import static dagger.spi.model.BindingKind.BOUND_INSTANCE; +import static dagger.spi.model.BindingKind.COMPONENT; +import static dagger.spi.model.BindingKind.COMPONENT_DEPENDENCY; +import static dagger.spi.model.BindingKind.COMPONENT_PRODUCTION; +import static dagger.spi.model.BindingKind.COMPONENT_PROVISION; +import static dagger.spi.model.BindingKind.DELEGATE; +import static dagger.spi.model.BindingKind.INJECTION; +import static dagger.spi.model.BindingKind.MEMBERS_INJECTOR; +import static dagger.spi.model.BindingKind.OPTIONAL; +import static dagger.spi.model.BindingKind.PRODUCTION; +import static dagger.spi.model.BindingKind.PROVISION; +import static dagger.spi.model.BindingKind.SUBCOMPONENT_CREATOR; + +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XConstructorType; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.XVariableElement; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; +import com.squareup.javapoet.ClassName; import dagger.Module; -import dagger.assisted.AssistedInject; import dagger.internal.codegen.base.ContributionType; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.SetType; import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; import dagger.internal.codegen.binding.ProductionBinding.ProductionKind; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.DependencyRequest; -import dagger.model.Key; -import dagger.model.RequestKind; -import dagger.producers.Produced; -import dagger.producers.Producer; +import dagger.spi.model.BindingKind; +import dagger.spi.model.DaggerType; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; +import dagger.spi.model.RequestKind; import java.util.Optional; import java.util.function.BiFunction; import javax.inject.Inject; -import javax.inject.Provider; -import javax.lang.model.element.Element; -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.ExecutableType; -import javax.lang.model.type.TypeMirror; /** A factory for {@link Binding} objects. */ public final class BindingFactory { @@ -86,30 +85,24 @@ public final class BindingFactory { private final KeyFactory keyFactory; private final DependencyRequestFactory dependencyRequestFactory; private final InjectionSiteFactory injectionSiteFactory; - private final DaggerElements elements; private final InjectionAnnotations injectionAnnotations; - private final KotlinMetadataUtil metadataUtil; @Inject BindingFactory( DaggerTypes types, - DaggerElements elements, KeyFactory keyFactory, DependencyRequestFactory dependencyRequestFactory, InjectionSiteFactory injectionSiteFactory, - InjectionAnnotations injectionAnnotations, - KotlinMetadataUtil metadataUtil) { + InjectionAnnotations injectionAnnotations) { this.types = types; - this.elements = elements; this.keyFactory = keyFactory; this.dependencyRequestFactory = dependencyRequestFactory; this.injectionSiteFactory = injectionSiteFactory; this.injectionAnnotations = injectionAnnotations; - this.metadataUtil = metadataUtil; } /** - * Returns an {@link dagger.model.BindingKind#INJECTION} binding. + * Returns an {@link dagger.spi.model.BindingKind#INJECTION} binding. * * @param constructorElement the {@code @Inject}-annotated constructor * @param resolvedType the parameterized type if the constructor is for a generic class and the @@ -117,91 +110,69 @@ public final class BindingFactory { */ // TODO(dpb): See if we can just pass the parameterized type and not also the constructor. public ProvisionBinding injectionBinding( - ExecutableElement constructorElement, Optional<TypeMirror> resolvedType) { - checkArgument(constructorElement.getKind().equals(CONSTRUCTOR)); - checkArgument( - isAnnotationPresent(constructorElement, Inject.class) - || isAnnotationPresent(constructorElement, AssistedInject.class)); - checkArgument(!injectionAnnotations.getQualifier(constructorElement).isPresent()); - - ExecutableType constructorType = MoreTypes.asExecutable(constructorElement.asType()); - DeclaredType constructedType = - MoreTypes.asDeclared(constructorElement.getEnclosingElement().asType()); + XConstructorElement constructorElement, Optional<XType> resolvedEnclosingType) { + checkArgument(InjectionAnnotations.hasInjectOrAssistedInjectAnnotation(constructorElement)); + + XConstructorType constructorType = constructorElement.getExecutableType(); + XType enclosingType = constructorElement.getEnclosingElement().getType(); // If the class this is constructing has some type arguments, resolve everything. - if (!constructedType.getTypeArguments().isEmpty() && resolvedType.isPresent()) { - DeclaredType resolved = MoreTypes.asDeclared(resolvedType.get()); - // Validate that we're resolving from the correct type. - checkState( - types.isSameType(types.erasure(resolved), types.erasure(constructedType)), - "erased expected type: %s, erased actual type: %s", - types.erasure(resolved), - types.erasure(constructedType)); - constructorType = MoreTypes.asExecutable(types.asMemberOf(resolved, constructorElement)); - constructedType = resolved; + if (!enclosingType.getTypeArguments().isEmpty() && resolvedEnclosingType.isPresent()) { + checkIsSameErasedType(resolvedEnclosingType.get(), enclosingType); + enclosingType = resolvedEnclosingType.get(); + constructorType = constructorElement.asMemberOf(enclosingType); } // Collect all dependency requests within the provision method. // Note: we filter out @Assisted parameters since these aren't considered dependency requests. ImmutableSet.Builder<DependencyRequest> provisionDependencies = ImmutableSet.builder(); for (int i = 0; i < constructorElement.getParameters().size(); i++) { - VariableElement parameter = constructorElement.getParameters().get(i); - TypeMirror parameterType = constructorType.getParameterTypes().get(i); + XExecutableParameterElement parameter = constructorElement.getParameters().get(i); + XType parameterType = constructorType.getParameterTypes().get(i); if (!AssistedInjectionAnnotations.isAssistedParameter(parameter)) { provisionDependencies.add( dependencyRequestFactory.forRequiredResolvedVariable(parameter, parameterType)); } } - Key key = keyFactory.forInjectConstructorWithResolvedType(constructedType); ProvisionBinding.Builder builder = ProvisionBinding.builder() .contributionType(ContributionType.UNIQUE) .bindingElement(constructorElement) - .key(key) + .key(keyFactory.forInjectConstructorWithResolvedType(enclosingType)) .provisionDependencies(provisionDependencies.build()) - .injectionSites(injectionSiteFactory.getInjectionSites(constructedType)) + .injectionSites(injectionSiteFactory.getInjectionSites(enclosingType)) .kind( - isAnnotationPresent(constructorElement, AssistedInject.class) + constructorElement.hasAnnotation(TypeNames.ASSISTED_INJECT) ? ASSISTED_INJECTION : INJECTION) - .scope(uniqueScopeOf(constructorElement.getEnclosingElement())); + .scope(injectionAnnotations.getScope(constructorElement.getEnclosingElement())); - TypeElement bindingTypeElement = MoreElements.asType(constructorElement.getEnclosingElement()); - if (hasNonDefaultTypeParameters(bindingTypeElement, key.type(), types)) { + if (hasNonDefaultTypeParameters(enclosingType)) { builder.unresolved(injectionBinding(constructorElement, Optional.empty())); } return builder.build(); } public ProvisionBinding assistedFactoryBinding( - TypeElement factory, Optional<TypeMirror> resolvedType) { + XTypeElement factory, Optional<XType> resolvedFactoryType) { // If the class this is constructing has some type arguments, resolve everything. - DeclaredType factoryType = MoreTypes.asDeclared(factory.asType()); - if (!factoryType.getTypeArguments().isEmpty() && resolvedType.isPresent()) { - DeclaredType resolved = MoreTypes.asDeclared(resolvedType.get()); - // Validate that we're resolving from the correct type by checking that the erasure of the - // resolvedType is the same as the erasure of the factoryType. - checkState( - types.isSameType(types.erasure(resolved), types.erasure(factoryType)), - "erased expected type: %s, erased actual type: %s", - types.erasure(resolved), - types.erasure(factoryType)); - factoryType = resolved; + XType factoryType = factory.getType(); + if (!factoryType.getTypeArguments().isEmpty() && resolvedFactoryType.isPresent()) { + checkIsSameErasedType(resolvedFactoryType.get(), factoryType); + factoryType = resolvedFactoryType.get(); } - ExecutableElement factoryMethod = - AssistedInjectionAnnotations.assistedFactoryMethod(factory, elements); - ExecutableType factoryMethodType = - MoreTypes.asExecutable(types.asMemberOf(factoryType, factoryMethod)); + XMethodElement factoryMethod = AssistedInjectionAnnotations.assistedFactoryMethod(factory); + XMethodType factoryMethodType = factoryMethod.asMemberOf(factoryType); return ProvisionBinding.builder() .contributionType(ContributionType.UNIQUE) - .key(Key.builder(factoryType).build()) + .key(Key.builder(DaggerType.from(factoryType)).build()) .bindingElement(factory) .provisionDependencies( ImmutableSet.of( DependencyRequest.builder() - .key(Key.builder(factoryMethodType.getReturnType()).build()) + .key(Key.builder(DaggerType.from(factoryMethodType.getReturnType())).build()) .kind(RequestKind.PROVIDER) .build())) .kind(ASSISTED_FACTORY) @@ -209,13 +180,13 @@ public final class BindingFactory { } /** - * Returns a {@link dagger.model.BindingKind#PROVISION} binding for a {@code @Provides}-annotated - * method. + * Returns a {@link dagger.spi.model.BindingKind#PROVISION} binding for a + * {@code @Provides}-annotated method. * * @param contributedBy the installed module that declares or inherits the method */ public ProvisionBinding providesMethodBinding( - ExecutableElement providesMethod, TypeElement contributedBy) { + XMethodElement providesMethod, XTypeElement contributedBy) { return setMethodBindingProperties( ProvisionBinding.builder(), providesMethod, @@ -223,19 +194,19 @@ public final class BindingFactory { keyFactory.forProvidesMethod(providesMethod, contributedBy), this::providesMethodBinding) .kind(PROVISION) - .scope(uniqueScopeOf(providesMethod)) + .scope(injectionAnnotations.getScope(providesMethod)) .nullableType(getNullableType(providesMethod)) .build(); } /** - * Returns a {@link dagger.model.BindingKind#PRODUCTION} binding for a {@code @Produces}-annotated - * method. + * Returns a {@link dagger.spi.model.BindingKind#PRODUCTION} binding for a + * {@code @Produces}-annotated method. * * @param contributedBy the installed module that declares or inherits the method */ public ProductionBinding producesMethodBinding( - ExecutableElement producesMethod, TypeElement contributedBy) { + XMethodElement producesMethod, XTypeElement contributedBy) { // TODO(beder): Add nullability checking with Java 8. ProductionBinding.Builder builder = setMethodBindingProperties( @@ -255,35 +226,29 @@ public final class BindingFactory { private <C extends ContributionBinding, B extends ContributionBinding.Builder<C, B>> B setMethodBindingProperties( B builder, - ExecutableElement method, - TypeElement contributedBy, + XMethodElement method, + XTypeElement contributedBy, Key key, - BiFunction<ExecutableElement, TypeElement, C> create) { - checkArgument(method.getKind().equals(METHOD)); - ExecutableType methodType = - MoreTypes.asExecutable( - types.asMemberOf(MoreTypes.asDeclared(contributedBy.asType()), method)); - if (!types.isSameType(methodType, method.asType())) { - builder.unresolved(create.apply(method, MoreElements.asType(method.getEnclosingElement()))); + BiFunction<XMethodElement, XTypeElement, C> create) { + XMethodType methodType = method.asMemberOf(contributedBy.getType()); + if (!types.isSameType(toJavac(methodType), toJavac(method.getExecutableType()))) { + checkState(isTypeElement(method.getEnclosingElement())); + builder.unresolved(create.apply(method, asTypeElement(method.getEnclosingElement()))); } - boolean isKotlinObject = - metadataUtil.isObjectClass(contributedBy) - || metadataUtil.isCompanionObjectClass(contributedBy); return builder .contributionType(ContributionType.fromBindingElement(method)) .bindingElement(method) .contributingModule(contributedBy) - .isContributingModuleKotlinObject(isKotlinObject) .key(key) .dependencies( - dependencyRequestFactory.forRequiredResolvedVariables( + dependencyRequestFactory.forRequiredResolvedXVariables( method.getParameters(), methodType.getParameterTypes())) .wrappedMapKeyAnnotation(wrapOptionalInEquivalence(getMapKey(method))); } /** - * Returns a {@link dagger.model.BindingKind#MULTIBOUND_MAP} or {@link - * dagger.model.BindingKind#MULTIBOUND_SET} binding given a set of multibinding contribution + * Returns a {@link dagger.spi.model.BindingKind#MULTIBOUND_MAP} or {@link + * dagger.spi.model.BindingKind#MULTIBOUND_SET} binding given a set of multibinding contribution * bindings. * * @param key a key that may be satisfied by a multibinding @@ -303,33 +268,44 @@ public final class BindingFactory { .build(); } + private static BindingKind bindingKindForMultibindingKey(Key key) { + if (SetType.isSet(key)) { + return BindingKind.MULTIBOUND_SET; + } else if (MapType.isMap(key)) { + return BindingKind.MULTIBOUND_MAP; + } else { + throw new IllegalArgumentException(String.format("key is not for a set or map: %s", key)); + } + } + private boolean multibindingRequiresProduction( Key key, Iterable<ContributionBinding> multibindingContributions) { if (MapType.isMap(key)) { MapType mapType = MapType.from(key); - if (mapType.valuesAreTypeOf(Producer.class) || mapType.valuesAreTypeOf(Produced.class)) { + if (mapType.valuesAreTypeOf(TypeNames.PRODUCER) + || mapType.valuesAreTypeOf(TypeNames.PRODUCED)) { return true; } - } else if (SetType.isSet(key) && SetType.from(key).elementsAreTypeOf(Produced.class)) { + } else if (SetType.isSet(key) && SetType.from(key).elementsAreTypeOf(TypeNames.PRODUCED)) { return true; } return Iterables.any( multibindingContributions, binding -> binding.bindingType().equals(BindingType.PRODUCTION)); } - /** Returns a {@link dagger.model.BindingKind#COMPONENT} binding for the component. */ - public ProvisionBinding componentBinding(TypeElement componentDefinitionType) { + /** Returns a {@link dagger.spi.model.BindingKind#COMPONENT} binding for the component. */ + public ProvisionBinding componentBinding(XTypeElement componentDefinitionType) { checkNotNull(componentDefinitionType); return ProvisionBinding.builder() .contributionType(ContributionType.UNIQUE) .bindingElement(componentDefinitionType) - .key(keyFactory.forType(componentDefinitionType.asType())) + .key(keyFactory.forType(componentDefinitionType.getType())) .kind(COMPONENT) .build(); } /** - * Returns a {@link dagger.model.BindingKind#COMPONENT_DEPENDENCY} binding for a component's + * Returns a {@link dagger.spi.model.BindingKind#COMPONENT_DEPENDENCY} binding for a component's * dependency. */ public ProvisionBinding componentDependencyBinding(ComponentRequirement dependency) { @@ -343,20 +319,18 @@ public final class BindingFactory { } /** - * Returns a {@link dagger.model.BindingKind#COMPONENT_PROVISION} or {@link - * dagger.model.BindingKind#COMPONENT_PRODUCTION} binding for a method on a component's + * Returns a {@link dagger.spi.model.BindingKind#COMPONENT_PROVISION} or {@link + * dagger.spi.model.BindingKind#COMPONENT_PRODUCTION} binding for a method on a component's * dependency. * * @param componentDescriptor the component with the dependency, not the dependency that has the * method */ public ContributionBinding componentDependencyMethodBinding( - ComponentDescriptor componentDescriptor, ExecutableElement dependencyMethod) { - checkArgument(dependencyMethod.getKind().equals(METHOD)); + ComponentDescriptor componentDescriptor, XMethodElement dependencyMethod) { checkArgument(dependencyMethod.getParameters().isEmpty()); ContributionBinding.Builder<?, ?> builder; - if (componentDescriptor.isProduction() - && isComponentProductionMethod(elements, dependencyMethod)) { + if (componentDescriptor.isProduction() && isComponentProductionMethod(dependencyMethod)) { builder = ProductionBinding.builder() .key(keyFactory.forProductionComponentMethod(dependencyMethod)) @@ -368,7 +342,7 @@ public final class BindingFactory { .key(keyFactory.forComponentMethod(dependencyMethod)) .nullableType(getNullableType(dependencyMethod)) .kind(COMPONENT_PROVISION) - .scope(uniqueScopeOf(dependencyMethod)); + .scope(injectionAnnotations.getScope(dependencyMethod)); } return builder .contributionType(ContributionType.UNIQUE) @@ -377,15 +351,15 @@ public final class BindingFactory { } /** - * Returns a {@link dagger.model.BindingKind#BOUND_INSTANCE} binding for a + * Returns a {@link dagger.spi.model.BindingKind#BOUND_INSTANCE} binding for a * {@code @BindsInstance}-annotated builder setter method or factory method parameter. */ - ProvisionBinding boundInstanceBinding(ComponentRequirement requirement, Element element) { - checkArgument(element instanceof VariableElement || element instanceof ExecutableElement); - VariableElement parameterElement = - element instanceof VariableElement - ? MoreElements.asVariable(element) - : getOnlyElement(MoreElements.asExecutable(element).getParameters()); + ProvisionBinding boundInstanceBinding(ComponentRequirement requirement, XElement element) { + checkArgument(isVariableElement(element) || isMethod(element)); + XVariableElement parameterElement = + isVariableElement(element) + ? asVariable(element) + : getOnlyElement(asMethod(element).getParameters()); return ProvisionBinding.builder() .contributionType(ContributionType.UNIQUE) .bindingElement(element) @@ -396,20 +370,18 @@ public final class BindingFactory { } /** - * Returns a {@link dagger.model.BindingKind#SUBCOMPONENT_CREATOR} binding declared by a component - * method that returns a subcomponent builder. Use {{@link + * Returns a {@link dagger.spi.model.BindingKind#SUBCOMPONENT_CREATOR} binding declared by a + * component method that returns a subcomponent builder. Use {{@link * #subcomponentCreatorBinding(ImmutableSet)}} for bindings declared using {@link * Module#subcomponents()}. * * @param component the component that declares or inherits the method */ ProvisionBinding subcomponentCreatorBinding( - ExecutableElement subcomponentCreatorMethod, TypeElement component) { - checkArgument(subcomponentCreatorMethod.getKind().equals(METHOD)); + XMethodElement subcomponentCreatorMethod, XTypeElement component) { checkArgument(subcomponentCreatorMethod.getParameters().isEmpty()); Key key = - keyFactory.forSubcomponentCreatorMethod( - subcomponentCreatorMethod, asDeclared(component.asType())); + keyFactory.forSubcomponentCreatorMethod(subcomponentCreatorMethod, component.getType()); return ProvisionBinding.builder() .contributionType(ContributionType.UNIQUE) .bindingElement(subcomponentCreatorMethod) @@ -419,8 +391,8 @@ public final class BindingFactory { } /** - * Returns a {@link dagger.model.BindingKind#SUBCOMPONENT_CREATOR} binding declared using {@link - * Module#subcomponents()}. + * Returns a {@link dagger.spi.model.BindingKind#SUBCOMPONENT_CREATOR} binding declared using + * {@link Module#subcomponents()}. */ ProvisionBinding subcomponentCreatorBinding( ImmutableSet<SubcomponentDeclaration> subcomponentDeclarations) { @@ -433,7 +405,7 @@ public final class BindingFactory { } /** - * Returns a {@link dagger.model.BindingKind#DELEGATE} binding. + * Returns a {@link dagger.spi.model.BindingKind#DELEGATE} binding. * * @param delegateDeclaration the {@code @Binds}-annotated declaration * @param actualBinding the binding that satisfies the {@code @Binds} declaration @@ -445,15 +417,15 @@ public final class BindingFactory { return buildDelegateBinding( ProductionBinding.builder().nullableType(actualBinding.nullableType()), delegateDeclaration, - Producer.class); + TypeNames.PRODUCER); case PROVISION: return buildDelegateBinding( ProvisionBinding.builder() - .scope(uniqueScopeOf(delegateDeclaration.bindingElement().get())) + .scope(injectionAnnotations.getScope(delegateDeclaration.bindingElement().get())) .nullableType(actualBinding.nullableType()), delegateDeclaration, - Provider.class); + TypeNames.PROVIDER); case MEMBERS_INJECTION: // fall-through to throw } @@ -461,28 +433,25 @@ public final class BindingFactory { } /** - * Returns a {@link dagger.model.BindingKind#DELEGATE} binding used when there is no binding that - * satisfies the {@code @Binds} declaration. + * Returns a {@link dagger.spi.model.BindingKind#DELEGATE} binding used when there is no binding + * that satisfies the {@code @Binds} declaration. */ public ContributionBinding unresolvedDelegateBinding(DelegateDeclaration delegateDeclaration) { return buildDelegateBinding( - ProvisionBinding.builder().scope(uniqueScopeOf(delegateDeclaration.bindingElement().get())), + ProvisionBinding.builder() + .scope(injectionAnnotations.getScope(delegateDeclaration.bindingElement().get())), delegateDeclaration, - Provider.class); + TypeNames.PROVIDER); } private ContributionBinding buildDelegateBinding( ContributionBinding.Builder<?, ?> builder, DelegateDeclaration delegateDeclaration, - Class<?> frameworkType) { - boolean isKotlinObject = - metadataUtil.isObjectClass(delegateDeclaration.contributingModule().get()) - || metadataUtil.isCompanionObjectClass(delegateDeclaration.contributingModule().get()); + ClassName frameworkType) { return builder .contributionType(delegateDeclaration.contributionType()) .bindingElement(delegateDeclaration.bindingElement().get()) .contributingModule(delegateDeclaration.contributingModule().get()) - .isContributingModuleKotlinObject(isKotlinObject) .key(keyFactory.forDelegateBinding(delegateDeclaration, frameworkType)) .dependencies(delegateDeclaration.delegateRequest()) .wrappedMapKeyAnnotation(delegateDeclaration.wrappedMapKey()) @@ -491,7 +460,7 @@ public final class BindingFactory { } /** - * Returns an {@link dagger.model.BindingKind#OPTIONAL} binding for {@code key}. + * Returns an {@link dagger.spi.model.BindingKind#OPTIONAL} binding for {@code key}. * * @param requestKind the kind of request for the optional binding * @param underlyingKeyBindings the possibly empty set of bindings that exist in the component for @@ -523,56 +492,78 @@ public final class BindingFactory { .build(); } - /** Returns a {@link dagger.model.BindingKind#MEMBERS_INJECTOR} binding. */ + /** Returns a {@link dagger.spi.model.BindingKind#MEMBERS_INJECTOR} binding. */ public ProvisionBinding membersInjectorBinding( Key key, MembersInjectionBinding membersInjectionBinding) { return ProvisionBinding.builder() .key(key) .contributionType(ContributionType.UNIQUE) .kind(MEMBERS_INJECTOR) - .bindingElement(MoreTypes.asTypeElement(membersInjectionBinding.key().type())) + .bindingElement(membersInjectionBinding.key().type().xprocessing().getTypeElement()) .provisionDependencies(membersInjectionBinding.dependencies()) .injectionSites(membersInjectionBinding.injectionSites()) .build(); } /** - * Returns a {@link dagger.model.BindingKind#MEMBERS_INJECTION} binding. + * Returns a {@link dagger.spi.model.BindingKind#MEMBERS_INJECTION} binding. * * @param resolvedType if {@code declaredType} is a generic class and {@code resolvedType} is a * parameterization of that type, the returned binding will be for the resolved type */ // TODO(dpb): See if we can just pass one nongeneric/parameterized type. - public MembersInjectionBinding membersInjectionBinding( - DeclaredType declaredType, Optional<TypeMirror> resolvedType) { + public MembersInjectionBinding membersInjectionBinding(XType type, Optional<XType> resolvedType) { // If the class this is injecting has some type arguments, resolve everything. - if (!declaredType.getTypeArguments().isEmpty() && resolvedType.isPresent()) { - DeclaredType resolved = asDeclared(resolvedType.get()); - // Validate that we're resolving from the correct type. - checkState( - types.isSameType(types.erasure(resolved), types.erasure(declaredType)), - "erased expected type: %s, erased actual type: %s", - types.erasure(resolved), - types.erasure(declaredType)); - declaredType = resolved; + if (!type.getTypeArguments().isEmpty() && resolvedType.isPresent()) { + checkIsSameErasedType(resolvedType.get(), type); + type = resolvedType.get(); } - ImmutableSortedSet<InjectionSite> injectionSites = - injectionSiteFactory.getInjectionSites(declaredType); + ImmutableSortedSet<InjectionSite> injectionSites = injectionSiteFactory.getInjectionSites(type); ImmutableSet<DependencyRequest> dependencies = injectionSites.stream() .flatMap(injectionSite -> injectionSite.dependencies().stream()) .collect(toImmutableSet()); - Key key = keyFactory.forMembersInjectedType(declaredType); - TypeElement typeElement = MoreElements.asType(declaredType.asElement()); - return new AutoValue_MembersInjectionBinding( - key, + return MembersInjectionBinding.create( + keyFactory.forMembersInjectedType(type), dependencies, - typeElement, - hasNonDefaultTypeParameters(typeElement, key.type(), types) + hasNonDefaultTypeParameters(type) ? Optional.of( - membersInjectionBinding(asDeclared(typeElement.asType()), Optional.empty())) + membersInjectionBinding(type.getTypeElement().getType(), Optional.empty())) : Optional.empty(), injectionSites); } + + private void checkIsSameErasedType(XType type1, XType type2) { + checkState( + types.isSameType(types.erasure(toJavac(type1)), types.erasure(toJavac(type2))), + "erased expected type: %s, erased actual type: %s", + types.erasure(toJavac(type1)), + types.erasure(toJavac(type2))); + } + + private static boolean hasNonDefaultTypeParameters(XType type) { + // If the type is not declared, then it can't have type parameters. + if (!isDeclared(type)) { + return false; + } + + // If the element has no type parameters, none can be non-default. + XType defaultType = type.getTypeElement().getType(); + if (defaultType.getTypeArguments().isEmpty()) { + return false; + } + + // The actual type parameter size can be different if the user is using a raw type. + if (defaultType.getTypeArguments().size() != type.getTypeArguments().size()) { + return true; + } + + for (int i = 0; i < defaultType.getTypeArguments().size(); i++) { + if (!defaultType.getTypeArguments().get(i).isSameType(type.getTypeArguments().get(i))) { + return true; + } + } + return false; + } } diff --git a/java/dagger/internal/codegen/binding/BindingGraph.java b/java/dagger/internal/codegen/binding/BindingGraph.java index 533d113f1..ee5a54b94 100644 --- a/java/dagger/internal/codegen/binding/BindingGraph.java +++ b/java/dagger/internal/codegen/binding/BindingGraph.java @@ -18,12 +18,16 @@ package dagger.internal.codegen.binding; import static com.google.common.collect.Iterables.transform; import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; +import static dagger.internal.codegen.extension.DaggerStreams.instancesOf; import static dagger.internal.codegen.extension.DaggerStreams.presentValues; import static dagger.internal.codegen.extension.DaggerStreams.stream; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableList; @@ -31,24 +35,28 @@ import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Maps; import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; import com.google.common.graph.ImmutableNetwork; import com.google.common.graph.Traverser; -import dagger.model.BindingGraph.ChildFactoryMethodEdge; -import dagger.model.BindingGraph.ComponentNode; -import dagger.model.BindingGraph.Edge; -import dagger.model.BindingGraph.Node; -import dagger.model.ComponentPath; -import dagger.model.Key; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import dagger.internal.codegen.base.TarjanSCCs; +import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge; +import dagger.spi.model.BindingGraph.ComponentNode; +import dagger.spi.model.BindingGraph.DependencyEdge; +import dagger.spi.model.BindingGraph.Edge; +import dagger.spi.model.BindingGraph.Node; +import dagger.spi.model.ComponentPath; +import dagger.spi.model.DaggerTypeElement; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; +import java.util.Set; +import java.util.stream.Stream; /** * A graph that represents a single component or subcomponent within a fully validated top-level @@ -57,8 +65,12 @@ import javax.lang.model.element.VariableElement; @AutoValue public abstract class BindingGraph { + /** + * A graph that represents the entire network of nodes from all components, subcomponents and + * their bindings. + */ @AutoValue - abstract static class TopLevelBindingGraph extends dagger.model.BindingGraph { + public abstract static class TopLevelBindingGraph extends dagger.spi.model.BindingGraph { static TopLevelBindingGraph create( ImmutableNetwork<Node, Edge> network, boolean isFullBindingGraph) { TopLevelBindingGraph topLevelBindingGraph = @@ -82,15 +94,18 @@ public abstract class BindingGraph { // AutoValue to prevent exposing this data outside of the class. topLevelBindingGraph.componentNodes = componentNodes; topLevelBindingGraph.subcomponentNodes = subcomponentNodesBuilder.build(); + topLevelBindingGraph.frameworkTypeBindings = + frameworkRequestBindingSet(network, topLevelBindingGraph.bindings()); return topLevelBindingGraph; } private ImmutableMap<ComponentPath, ComponentNode> componentNodes; private ImmutableSetMultimap<ComponentNode, ComponentNode> subcomponentNodes; + private ImmutableSet<Binding> frameworkTypeBindings; TopLevelBindingGraph() {} - // This overrides dagger.model.BindingGraph with a more efficient implementation. + // This overrides dagger.spi.model.BindingGraph with a more efficient implementation. @Override public Optional<ComponentNode> componentNode(ComponentPath componentPath) { return componentNodes.containsKey(componentPath) @@ -117,6 +132,61 @@ public abstract class BindingGraph { ImmutableListMultimap<ComponentPath, BindingNode> bindingsByComponent() { return Multimaps.index(transform(bindings(), BindingNode.class::cast), Node::componentPath); } + + /** Returns a {@link Comparator} in the same order as {@link Network#nodes()}. */ + @Memoized + Comparator<Node> nodeOrder() { + Map<Node, Integer> nodeOrderMap = Maps.newHashMapWithExpectedSize(network().nodes().size()); + int i = 0; + for (Node node : network().nodes()) { + nodeOrderMap.put(node, i++); + } + return (n1, n2) -> nodeOrderMap.get(n1).compareTo(nodeOrderMap.get(n2)); + } + + /** Returns the set of strongly connected nodes in this graph in reverse topological order. */ + @Memoized + public ImmutableSet<ImmutableSet<Node>> stronglyConnectedNodes() { + return TarjanSCCs.<Node>compute( + ImmutableSet.copyOf(network().nodes()), + // NetworkBuilder does not have a stable successor order, so we have to roll our own + // based on the node order, which is stable. + // TODO(bcorso): Fix once https://github.com/google/guava/issues/2650 is fixed. + node -> + network().successors(node).stream().sorted(nodeOrder()).collect(toImmutableList())); + } + + public boolean hasFrameworkRequest(Binding binding) { + return frameworkTypeBindings.contains(binding); + } + + private static ImmutableSet<Binding> frameworkRequestBindingSet( + ImmutableNetwork<Node, Edge> network, ImmutableSet<dagger.spi.model.Binding> bindings) { + Set<Binding> frameworkRequestBindings = new HashSet<>(); + for (dagger.spi.model.Binding binding : bindings) { + ImmutableList<DependencyEdge> edges = + network.inEdges(binding).stream() + .flatMap(instancesOf(DependencyEdge.class)) + .collect(toImmutableList()); + for (DependencyEdge edge : edges) { + DependencyRequest request = edge.dependencyRequest(); + switch (request.kind()) { + case INSTANCE: + case FUTURE: + continue; + case PRODUCED: + case PRODUCER: + case MEMBERS_INJECTION: + case PROVIDER_OF_LAZY: + case LAZY: + case PROVIDER: + frameworkRequestBindings.add(((BindingNode) binding).delegate()); + break; + } + } + } + return ImmutableSet.copyOf(frameworkRequestBindings); + } } static BindingGraph create( @@ -128,42 +198,34 @@ public abstract class BindingGraph { Optional<BindingGraph> parent, ComponentNode componentNode, TopLevelBindingGraph topLevelBindingGraph) { - List<BindingNode> reachableBindingNodes = new ArrayList<>(); - for (ComponentPath path = componentNode.componentPath(); - !path.components().isEmpty(); - path = ComponentPath.create(path.components().subList(0, path.components().size() - 1))) { - reachableBindingNodes.addAll(topLevelBindingGraph.bindingsByComponent().get(path)); - } - - // Construct the maps of the ContributionBindings and MembersInjectionBindings. - Map<Key, BindingNode> contributionBindings = new HashMap<>(); - Map<Key, BindingNode> membersInjectionBindings = new HashMap<>(); - for (BindingNode bindingNode : reachableBindingNodes) { - Map<Key, BindingNode> bindingsMap; - if (bindingNode.delegate() instanceof ContributionBinding) { - bindingsMap = contributionBindings; - } else if (bindingNode.delegate() instanceof MembersInjectionBinding) { - bindingsMap = membersInjectionBindings; - } else { - throw new AssertionError("Unexpected binding node type: " + bindingNode.delegate()); - } - - // TODO(bcorso): Mapping binding nodes by key is flawed since bindings that depend on local - // multibindings can have multiple nodes (one in each component). In this case, we choose the - // node in the child-most component since this is likely the node that users of this - // BindingGraph will want (and to remain consisted with LegacyBindingGraph). However, ideally - // we would avoid this ambiguity by getting dependencies directly from the top-level network. - // In particular, rather than using a Binding's list of DependencyRequests (which only - // contains the key) we would use the top-level network to find the DependencyEdges for a - // particular BindingNode. - Key key = bindingNode.key(); - if (!bindingsMap.containsKey(key) - // Always choose the child-most binding node. - || bindingNode.componentPath().components().size() - > bindingsMap.get(key).componentPath().components().size()) { - bindingsMap.put(key, bindingNode); - } - } + // TODO(bcorso): Mapping binding nodes by key is flawed since bindings that depend on local + // multibindings can have multiple nodes (one in each component). In this case, we choose the + // node in the child-most component since this is likely the node that users of this + // BindingGraph will want (and to remain consistent with LegacyBindingGraph). However, ideally + // we would avoid this ambiguity by getting dependencies directly from the top-level network. + // In particular, rather than using a Binding's list of DependencyRequests (which only + // contains the key) we would use the top-level network to find the DependencyEdges for a + // particular BindingNode. + Map<Key, BindingNode> contributionBindings = new LinkedHashMap<>(); + Map<Key, BindingNode> membersInjectionBindings = new LinkedHashMap<>(); + + // Construct the maps of the ContributionBindings and MembersInjectionBindings by iterating + // bindings from this component and then from each successive parent. If a binding exists in + // multple components, this order ensures that the child-most binding is always chosen first. + Stream.iterate(componentNode.componentPath(), ComponentPath::parent) + // Stream.iterate is inifinte stream so we need limit it to the known size of the path. + .limit(componentNode.componentPath().components().size()) + .flatMap(path -> topLevelBindingGraph.bindingsByComponent().get(path).stream()) + .forEach( + bindingNode -> { + if (bindingNode.delegate() instanceof ContributionBinding) { + contributionBindings.putIfAbsent(bindingNode.key(), bindingNode); + } else if (bindingNode.delegate() instanceof MembersInjectionBinding) { + membersInjectionBindings.putIfAbsent(bindingNode.key(), bindingNode); + } else { + throw new AssertionError("Unexpected binding node type: " + bindingNode.delegate()); + } + }); BindingGraph bindingGraph = new AutoValue_BindingGraph(componentNode, topLevelBindingGraph); @@ -185,6 +247,7 @@ public abstract class BindingGraph { contributionBindings.values().stream() .map(BindingNode::contributingModule) .flatMap(presentValues()) + .map(DaggerTypeElement::xprocessing) .collect(toImmutableSet()); return bindingGraph; @@ -194,7 +257,7 @@ public abstract class BindingGraph { private ImmutableMap<Key, BindingNode> membersInjectionBindings; private ImmutableSet<ModuleDescriptor> inheritedModules; private ImmutableSet<ModuleDescriptor> ownedModules; - private ImmutableSet<TypeElement> bindingModules; + private ImmutableSet<XTypeElement> bindingModules; BindingGraph() {} @@ -214,6 +277,30 @@ public abstract class BindingGraph { return ((ComponentNodeImpl) componentNode()).componentDescriptor(); } + /** + * Returns the {@link ContributionBinding} for the given {@link Key} in this component or {@link + * Optional#empty()} if one doesn't exist. + */ + public final Optional<Binding> localContributionBinding(Key key) { + return contributionBindings.containsKey(key) + ? Optional.of(contributionBindings.get(key)) + .filter(bindingNode -> bindingNode.componentPath().equals(componentPath())) + .map(BindingNode::delegate) + : Optional.empty(); + } + + /** + * Returns the {@link MembersInjectionBinding} for the given {@link Key} in this component or + * {@link Optional#empty()} if one doesn't exist. + */ + public final Optional<Binding> localMembersInjectionBinding(Key key) { + return membersInjectionBindings.containsKey(key) + ? Optional.of(membersInjectionBindings.get(key)) + .filter(bindingNode -> bindingNode.componentPath().equals(componentPath())) + .map(BindingNode::delegate) + : Optional.empty(); + } + /** Returns the {@link ContributionBinding} for the given {@link Key}. */ public final ContributionBinding contributionBinding(Key key) { return (ContributionBinding) contributionBindings.get(key).delegate(); @@ -229,9 +316,9 @@ public abstract class BindingGraph { : Optional.empty(); } - /** Returns the {@link TypeElement} for the component this graph represents. */ - public final TypeElement componentTypeElement() { - return componentPath().currentComponent(); + /** Returns the {@link XTypeElement} for the component this graph represents. */ + public final XTypeElement componentTypeElement() { + return componentPath().currentComponent().xprocessing(); } /** @@ -242,8 +329,10 @@ public abstract class BindingGraph { * subcomponents}, this set will be the transitive modules that are not owned by any of their * ancestors. */ - public final ImmutableSet<TypeElement> ownedModuleTypes() { - return ownedModules.stream().map(ModuleDescriptor::moduleElement).collect(toImmutableSet()); + public final ImmutableSet<XTypeElement> ownedModuleTypes() { + return ownedModules.stream() + .map(ModuleDescriptor::moduleElement) + .collect(toImmutableSet()); } /** @@ -252,7 +341,7 @@ public abstract class BindingGraph { * <p>This factory method is the one defined in the parent component's interface. * * <p>In the example below, the {@link BindingGraph#factoryMethod} for {@code ChildComponent} - * would return the {@link ExecutableElement}: {@code childComponent(ChildModule1)} . + * would return the {@link XExecutableElement}: {@code childComponent(ChildModule1)} . * * <pre><code> * {@literal @Component} @@ -262,24 +351,25 @@ public abstract class BindingGraph { * </code></pre> */ // TODO(b/73294201): Consider returning the resolved ExecutableType for the factory method. - public final Optional<ExecutableElement> factoryMethod() { + public final Optional<XExecutableElement> factoryMethod() { return topLevelBindingGraph().network().inEdges(componentNode()).stream() .filter(edge -> edge instanceof ChildFactoryMethodEdge) - .map(edge -> ((ChildFactoryMethodEdge) edge).factoryMethod()) + .map(edge -> ((ChildFactoryMethodEdge) edge).factoryMethod().xprocessing()) .collect(toOptional()); } /** * Returns a map between the {@linkplain ComponentRequirement component requirement} and the - * corresponding {@link VariableElement} for each module parameter in the {@linkplain + * corresponding {@link XExecutableParameterElement} for each module parameter in the {@linkplain * BindingGraph#factoryMethod factory method}. */ // TODO(dpb): Consider disallowing modules if none of their bindings are used. - public final ImmutableMap<ComponentRequirement, VariableElement> factoryMethodParameters() { + public final ImmutableMap<ComponentRequirement, XExecutableParameterElement> + factoryMethodParameters() { return factoryMethod().get().getParameters().stream() .collect( toImmutableMap( - parameter -> ComponentRequirement.forModule(parameter.asType()), + parameter -> ComponentRequirement.forModule(parameter.getType()), parameter -> parameter)); } @@ -294,7 +384,7 @@ public abstract class BindingGraph { */ @Memoized public ImmutableSet<ComponentRequirement> componentRequirements() { - ImmutableSet<TypeElement> requiredModules = + ImmutableSet<XTypeElement> requiredModules = stream(Traverser.forTree(BindingGraph::subgraphs).depthFirstPostOrder(this)) .flatMap(graph -> graph.bindingModules.stream()) .filter(ownedModuleTypes()::contains) @@ -312,11 +402,16 @@ public abstract class BindingGraph { return requirements.build(); } - /** Returns all {@link ComponentDescriptor}s in the {@link TopLevelBindingGraph}. */ - public final ImmutableSet<ComponentDescriptor> componentDescriptors() { + /** + * Returns all {@link ComponentDescriptor}s in the {@link TopLevelBindingGraph} mapped by the + * component path. + */ + @Memoized + public ImmutableMap<ComponentPath, ComponentDescriptor> componentDescriptorsByPath() { return topLevelBindingGraph().componentNodes().stream() - .map(componentNode -> ((ComponentNodeImpl) componentNode).componentDescriptor()) - .collect(toImmutableSet()); + .map(ComponentNodeImpl.class::cast) + .collect( + toImmutableMap(ComponentNode::componentPath, ComponentNodeImpl::componentDescriptor)); } @Memoized @@ -326,15 +421,9 @@ public abstract class BindingGraph { .collect(toImmutableList()); } - public final ImmutableSet<BindingNode> bindingNodes(Key key) { - ImmutableSet.Builder<BindingNode> builder = ImmutableSet.builder(); - if (contributionBindings.containsKey(key)) { - builder.add(contributionBindings.get(key)); - } - if (membersInjectionBindings.containsKey(key)) { - builder.add(membersInjectionBindings.get(key)); - } - return builder.build(); + /** Returns the list of all {@link BindingNode}s local to this component. */ + public ImmutableList<BindingNode> localBindingNodes() { + return topLevelBindingGraph().bindingsByComponent().get(componentPath()); } @Memoized diff --git a/java/dagger/internal/codegen/binding/BindingGraphConverter.java b/java/dagger/internal/codegen/binding/BindingGraphConverter.java index b882b37a9..7d24bca89 100644 --- a/java/dagger/internal/codegen/binding/BindingGraphConverter.java +++ b/java/dagger/internal/codegen/binding/BindingGraphConverter.java @@ -16,13 +16,18 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; import static com.google.auto.common.MoreTypes.asTypeElement; import static com.google.common.base.Verify.verify; import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import static dagger.internal.codegen.extension.DaggerGraphs.unreachableNodes; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; -import static dagger.model.BindingKind.SUBCOMPONENT_CREATOR; +import static dagger.spi.model.BindingKind.SUBCOMPONENT_CREATOR; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableList; @@ -35,14 +40,16 @@ import com.google.common.graph.Network; import com.google.common.graph.NetworkBuilder; import dagger.internal.codegen.binding.BindingGraph.TopLevelBindingGraph; import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; -import dagger.model.BindingGraph.ComponentNode; -import dagger.model.BindingGraph.DependencyEdge; -import dagger.model.BindingGraph.Edge; -import dagger.model.BindingGraph.MissingBinding; -import dagger.model.BindingGraph.Node; -import dagger.model.ComponentPath; -import dagger.model.DependencyRequest; -import dagger.model.Key; +import dagger.spi.model.BindingGraph.ComponentNode; +import dagger.spi.model.BindingGraph.DependencyEdge; +import dagger.spi.model.BindingGraph.Edge; +import dagger.spi.model.BindingGraph.MissingBinding; +import dagger.spi.model.BindingGraph.Node; +import dagger.spi.model.ComponentPath; +import dagger.spi.model.DaggerExecutableElement; +import dagger.spi.model.DaggerTypeElement; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; @@ -54,18 +61,21 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -/** Converts {@link BindingGraph}s to {@link dagger.model.BindingGraph}s. */ +/** Converts {@link BindingGraph}s to {@link dagger.spi.model.BindingGraph}s. */ final class BindingGraphConverter { + private final XProcessingEnv processingEnv; private final BindingDeclarationFormatter bindingDeclarationFormatter; @Inject - BindingGraphConverter(BindingDeclarationFormatter bindingDeclarationFormatter) { + BindingGraphConverter( + XProcessingEnv processingEnv, BindingDeclarationFormatter bindingDeclarationFormatter) { + this.processingEnv = processingEnv; this.bindingDeclarationFormatter = bindingDeclarationFormatter; } /** - * Creates the external {@link dagger.model.BindingGraph} representing the given internal {@link - * BindingGraph}. + * Creates the external {@link dagger.spi.model.BindingGraph} representing the given internal + * {@link BindingGraph}. */ BindingGraph convert(LegacyBindingGraph legacyBindingGraph, boolean isFullBindingGraph) { MutableNetwork<Node, Edge> network = asNetwork(legacyBindingGraph); @@ -86,7 +96,7 @@ final class BindingGraphConverter { } private MutableNetwork<Node, Edge> asNetwork(LegacyBindingGraph graph) { - Converter converter = new Converter(bindingDeclarationFormatter); + Converter converter = new Converter(); converter.visitRootComponent(graph); return converter.network; } @@ -116,14 +126,13 @@ final class BindingGraphConverter { } } - private static final class Converter { + private final class Converter { /** The path from the root graph to the currently visited graph. */ private final Deque<LegacyBindingGraph> bindingGraphPath = new ArrayDeque<>(); /** The {@link ComponentPath} for each component in {@link #bindingGraphPath}. */ private final Deque<ComponentPath> componentPaths = new ArrayDeque<>(); - private final BindingDeclarationFormatter bindingDeclarationFormatter; private final MutableNetwork<Node, Edge> network = NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build(); private final Set<BindingNode> bindings = new HashSet<>(); @@ -131,11 +140,6 @@ final class BindingGraphConverter { private final Map<ResolvedBindingsWithPath, ImmutableSet<BindingNode>> resolvedBindingsMap = new HashMap<>(); - /** Constructs a converter for a root (component, not subcomponent) binding graph. */ - private Converter(BindingDeclarationFormatter bindingDeclarationFormatter) { - this.bindingDeclarationFormatter = bindingDeclarationFormatter; - } - private void visitRootComponent(LegacyBindingGraph graph) { visitComponent(graph, null); } @@ -164,6 +168,7 @@ final class BindingGraphConverter { bindingGraphPath.stream() .map(LegacyBindingGraph::componentDescriptor) .map(ComponentDescriptor::typeElement) + .map(DaggerTypeElement::from) .collect(toImmutableList())); componentPaths.addLast(graphPath); ComponentNode currentComponent = @@ -188,7 +193,7 @@ final class BindingGraphConverter { && binding.componentPath().equals(currentComponent.componentPath())) { network.addEdge( binding, - subcomponentNode(binding.key().type(), graph), + subcomponentNode(binding.key().type().java(), graph), new SubcomponentCreatorBindingEdgeImpl( resolvedBindings.subcomponentDeclarations())); } @@ -235,11 +240,11 @@ final class BindingGraphConverter { private void visitSubcomponentFactoryMethod( ComponentNode parentComponent, ComponentNode currentComponent, - ExecutableElement factoryMethod) { + XMethodElement factoryMethod) { network.addEdge( parentComponent, currentComponent, - new ChildFactoryMethodEdgeImpl(factoryMethod)); + new ChildFactoryMethodEdgeImpl(DaggerExecutableElement.from(factoryMethod))); } /** @@ -256,7 +261,7 @@ final class BindingGraphConverter { */ private ComponentPath pathFromRootToAncestor(TypeElement ancestor) { for (ComponentPath componentPath : componentPaths) { - if (componentPath.currentComponent().equals(ancestor)) { + if (componentPath.currentComponent().java().equals(ancestor)) { return componentPath; } } @@ -271,7 +276,7 @@ final class BindingGraphConverter { */ private LegacyBindingGraph graphForAncestor(TypeElement ancestor) { for (LegacyBindingGraph graph : bindingGraphPath) { - if (graph.componentDescriptor().typeElement().equals(ancestor)) { + if (toJavac(graph.componentDescriptor().typeElement()).equals(ancestor)) { return graph; } } @@ -281,8 +286,8 @@ final class BindingGraphConverter { } /** - * Adds a {@link dagger.model.BindingGraph.DependencyEdge} from a node to the binding(s) that - * satisfy a dependency request. + * Adds a {@link dagger.spi.model.BindingGraph.DependencyEdge} from a node to the binding(s) + * that satisfy a dependency request. */ private void addDependencyEdges(Node source, DependencyRequest dependencyRequest) { ResolvedBindings dependencies = resolvedDependencies(source, dependencyRequest); @@ -325,7 +330,7 @@ final class BindingGraphConverter { private ResolvedBindings resolvedDependencies( Node source, DependencyRequest dependencyRequest) { - return graphForAncestor(source.componentPath().currentComponent()) + return graphForAncestor(source.componentPath().currentComponent().java()) .resolvedBindings(bindingRequest(dependencyRequest)); } @@ -373,11 +378,13 @@ final class BindingGraphConverter { private ComponentNode subcomponentNode( TypeMirror subcomponentBuilderType, LegacyBindingGraph graph) { - TypeElement subcomponentBuilderElement = asTypeElement(subcomponentBuilderType); + XTypeElement subcomponentBuilderElement = + toXProcessing(asTypeElement(subcomponentBuilderType), processingEnv); ComponentDescriptor subcomponent = graph.componentDescriptor().getChildComponentWithBuilderType(subcomponentBuilderElement); return ComponentNodeImpl.create( - componentPath().childPath(subcomponent.typeElement()), subcomponent); + componentPath().childPath(DaggerTypeElement.from(subcomponent.typeElement())), + subcomponent); } } diff --git a/java/dagger/internal/codegen/binding/BindingGraphFactory.java b/java/dagger/internal/codegen/binding/BindingGraphFactory.java index a94f6b01e..ab0e90792 100644 --- a/java/dagger/internal/codegen/binding/BindingGraphFactory.java +++ b/java/dagger/internal/codegen/binding/BindingGraphFactory.java @@ -16,7 +16,8 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.MoreTypes.asTypeElement; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; import static com.google.auto.common.MoreTypes.isType; import static com.google.auto.common.MoreTypes.isTypeOf; import static com.google.common.base.Preconditions.checkArgument; @@ -26,16 +27,19 @@ import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType; import static dagger.internal.codegen.binding.ComponentDescriptor.isComponentContributionMethod; import static dagger.internal.codegen.binding.SourceFiles.generatedMonitoringModuleName; -import static dagger.model.BindingKind.ASSISTED_INJECTION; -import static dagger.model.BindingKind.DELEGATE; -import static dagger.model.BindingKind.INJECTION; -import static dagger.model.BindingKind.OPTIONAL; -import static dagger.model.BindingKind.SUBCOMPONENT_CREATOR; -import static dagger.model.RequestKind.MEMBERS_INJECTION; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.spi.model.BindingKind.ASSISTED_INJECTION; +import static dagger.spi.model.BindingKind.DELEGATE; +import static dagger.spi.model.BindingKind.INJECTION; +import static dagger.spi.model.BindingKind.OPTIONAL; +import static dagger.spi.model.BindingKind.SUBCOMPONENT_CREATOR; +import static dagger.spi.model.RequestKind.MEMBERS_INJECTION; import static java.util.function.Predicate.isEqual; import static javax.lang.model.util.ElementFilter.methodsIn; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -47,17 +51,17 @@ import dagger.MembersInjector; import dagger.Reusable; import dagger.internal.codegen.base.ClearableCache; import dagger.internal.codegen.base.ContributionType; +import dagger.internal.codegen.base.DaggerSuperficialValidation; import dagger.internal.codegen.base.Keys; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.OptionalType; import dagger.internal.codegen.compileroption.CompilerOptions; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.model.DependencyRequest; -import dagger.model.Key; -import dagger.model.Scope; -import dagger.producers.Produced; -import dagger.producers.Producer; import dagger.producers.internal.ProductionExecutorModule; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; +import dagger.spi.model.Scope; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; @@ -70,16 +74,15 @@ import java.util.Optional; import java.util.Queue; import java.util.Set; import javax.inject.Inject; -import javax.inject.Provider; import javax.inject.Singleton; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; /** A factory for {@link BindingGraph} objects. */ @Singleton public final class BindingGraphFactory implements ClearableCache { + private final XProcessingEnv processingEnv; private final DaggerElements elements; private final InjectBindingRegistry injectBindingRegistry; private final KeyFactory keyFactory; @@ -91,6 +94,7 @@ public final class BindingGraphFactory implements ClearableCache { @Inject BindingGraphFactory( + XProcessingEnv processingEnv, DaggerElements elements, InjectBindingRegistry injectBindingRegistry, KeyFactory keyFactory, @@ -98,6 +102,7 @@ public final class BindingGraphFactory implements ClearableCache { ModuleDescriptor.Factory moduleDescriptorFactory, BindingGraphConverter bindingGraphConverter, CompilerOptions compilerOptions) { + this.processingEnv = processingEnv; this.elements = elements; this.injectBindingRegistry = injectBindingRegistry; this.keyFactory = keyFactory; @@ -138,7 +143,7 @@ public final class BindingGraphFactory implements ClearableCache { for (ComponentRequirement dependency : componentDescriptor.dependencies()) { explicitBindingsBuilder.add(bindingFactory.componentDependencyBinding(dependency)); List<ExecutableElement> dependencyMethods = - methodsIn(elements.getAllMembers(dependency.typeElement())); + methodsIn(elements.getAllMembers(toJavac(dependency.typeElement()))); // Within a component dependency, we want to allow the same method to appear multiple // times assuming it is the exact same method. We do this by tracking a set of bindings @@ -147,9 +152,10 @@ public final class BindingGraphFactory implements ClearableCache { HashMultimap<String, ContributionBinding> dedupeBindings = HashMultimap.create(); for (ExecutableElement method : dependencyMethods) { // MembersInjection methods aren't "provided" explicitly, so ignore them. - if (isComponentContributionMethod(elements, method)) { - ContributionBinding binding = bindingFactory.componentDependencyMethodBinding( - componentDescriptor, method); + if (isComponentContributionMethod(method)) { + ContributionBinding binding = + bindingFactory.componentDependencyMethodBinding( + componentDescriptor, asMethod(toXProcessing(method, processingEnv))); if (dedupeBindings.put( method.getSimpleName().toString(), // Remove the binding element since we know that will be different, but everything @@ -284,15 +290,16 @@ public final class BindingGraphFactory implements ClearableCache { * @throws TypeNotPresentException if the module has not been generated yet. This will cause the * processor to retry in a later processing round. */ - private ModuleDescriptor descriptorForMonitoringModule(TypeElement componentDefinitionType) { + private ModuleDescriptor descriptorForMonitoringModule(XTypeElement componentDefinitionType) { return moduleDescriptorFactory.create( - elements.checkTypePresent( - generatedMonitoringModuleName(componentDefinitionType).toString())); + DaggerSuperficialValidation.requireTypeElement( + processingEnv, generatedMonitoringModuleName(componentDefinitionType))); } /** Returns a descriptor {@link ProductionExecutorModule}. */ private ModuleDescriptor descriptorForProductionExecutorModule() { - return moduleDescriptorFactory.create(elements.getTypeElement(ProductionExecutorModule.class)); + return moduleDescriptorFactory.create( + processingEnv.findTypeElement(TypeNames.PRODUCTION_EXECTUTOR_MODULE)); } /** Indexes {@code bindingDeclarations} by {@link BindingDeclaration#key()}. */ @@ -411,19 +418,21 @@ public final class BindingGraphFactory implements ClearableCache { } // Add members injector binding - if (isType(requestKey.type()) && isTypeOf(MembersInjector.class, requestKey.type())) { + if (isType(requestKey.type().java()) + && isTypeOf(MembersInjector.class, requestKey.type().java())) { injectBindingRegistry .getOrFindMembersInjectorProvisionBinding(requestKey) .ifPresent(bindings::add); } // Add Assisted Factory binding - if (isType(requestKey.type()) - && requestKey.type().getKind() == TypeKind.DECLARED - && isAssistedFactoryType(asTypeElement(requestKey.type()))) { + if (isType(requestKey.type().java()) + && isDeclared(requestKey.type().xprocessing()) + && isAssistedFactoryType(requestKey.type().xprocessing().getTypeElement())) { bindings.add( bindingFactory.assistedFactoryBinding( - asTypeElement(requestKey.type()), Optional.of(requestKey.type()))); + requestKey.type().xprocessing().getTypeElement(), + Optional.of(requestKey.type().xprocessing()))); } // If there are no bindings, add the implicit @Inject-constructed binding if there is one. @@ -486,7 +495,8 @@ public final class BindingGraphFactory implements ClearableCache { checkArgument(subcomponentCreatorBinding.kind().equals(SUBCOMPONENT_CREATOR)); Resolver owningResolver = getOwningResolver(subcomponentCreatorBinding).get(); - TypeElement builderType = MoreTypes.asTypeElement(subcomponentCreatorBinding.key().type()); + XTypeElement builderType = + subcomponentCreatorBinding.key().type().xprocessing().getTypeElement(); owningResolver.subcomponentsToResolve.add( owningResolver.componentDescriptor.getChildComponentWithBuilderType(builderType)); } @@ -515,9 +525,13 @@ public final class BindingGraphFactory implements ClearableCache { private ImmutableSet<Key> keysMatchingRequestUncached(Key requestKey) { ImmutableSet.Builder<Key> keys = ImmutableSet.builder(); keys.add(requestKey); - keyFactory.unwrapSetKey(requestKey, Produced.class).ifPresent(keys::add); - keyFactory.rewrapMapKey(requestKey, Producer.class, Provider.class).ifPresent(keys::add); - keyFactory.rewrapMapKey(requestKey, Provider.class, Producer.class).ifPresent(keys::add); + keyFactory.unwrapSetKey(requestKey, TypeNames.PRODUCED).ifPresent(keys::add); + keyFactory + .rewrapMapKey(requestKey, TypeNames.PRODUCER, TypeNames.PROVIDER) + .ifPresent(keys::add); + keyFactory + .rewrapMapKey(requestKey, TypeNames.PROVIDER, TypeNames.PRODUCER) + .ifPresent(keys::add); keys.addAll(keyFactory.implicitFrameworkMapKeys(requestKey)); return keys.build(); } @@ -585,7 +599,7 @@ public final class BindingGraphFactory implements ClearableCache { parentResolver.get().resolvedContributionBindings.get(requestKey); return parentResolvedBindings.owningComponent(binding); } else { - return componentDescriptor.typeElement(); + return toJavac(componentDescriptor.typeElement()); } } diff --git a/java/dagger/internal/codegen/binding/BindingNode.java b/java/dagger/internal/codegen/binding/BindingNode.java index 78d440da6..aa0f6cb8e 100644 --- a/java/dagger/internal/codegen/binding/BindingNode.java +++ b/java/dagger/internal/codegen/binding/BindingNode.java @@ -24,25 +24,25 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import dagger.BindsOptionalOf; import dagger.Module; -import dagger.model.BindingKind; -import dagger.model.ComponentPath; -import dagger.model.DependencyRequest; -import dagger.model.Key; -import dagger.model.Scope; import dagger.multibindings.Multibinds; +import dagger.spi.model.BindingKind; +import dagger.spi.model.ComponentPath; +import dagger.spi.model.DaggerElement; +import dagger.spi.model.DaggerTypeElement; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; +import dagger.spi.model.Scope; import java.util.Optional; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; /** - * An implementation of {@link dagger.model.Binding} that also exposes {@link BindingDeclaration}s - * associated with the binding. + * An implementation of {@link dagger.spi.model.Binding} that also exposes {@link + * BindingDeclaration}s associated with the binding. */ -// TODO(dpb): Consider a supertype of dagger.model.Binding that +// TODO(dpb): Consider a supertype of dagger.spi.model.Binding that // dagger.internal.codegen.binding.Binding // could also implement. @AutoValue -public abstract class BindingNode implements dagger.model.Binding { +public abstract class BindingNode implements dagger.spi.model.Binding { public static BindingNode create( ComponentPath component, Binding delegate, @@ -72,8 +72,8 @@ public abstract class BindingNode implements dagger.model.Binding { public abstract ImmutableSet<SubcomponentDeclaration> subcomponentDeclarations(); /** - * The {@link Element}s (other than the binding's {@link #bindingElement()}) that are associated - * with the binding. + * The elements (other than the binding's {@link #bindingElement()}) that are associated with the + * binding. * * <ul> * <li>{@linkplain BindsOptionalOf optional binding} declarations @@ -97,13 +97,13 @@ public abstract class BindingNode implements dagger.model.Binding { } @Override - public Optional<Element> bindingElement() { - return delegate().bindingElement(); + public Optional<DaggerElement> bindingElement() { + return delegate().bindingElement().map(DaggerElement::from); } @Override - public Optional<TypeElement> contributingModule() { - return delegate().contributingModule(); + public Optional<DaggerTypeElement> contributingModule() { + return delegate().contributingModule().map(DaggerTypeElement::from); } @Override diff --git a/java/dagger/internal/codegen/binding/BindingRequest.java b/java/dagger/internal/codegen/binding/BindingRequest.java index d61d9cfba..6bb572dc6 100644 --- a/java/dagger/internal/codegen/binding/BindingRequest.java +++ b/java/dagger/internal/codegen/binding/BindingRequest.java @@ -16,13 +16,15 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static dagger.internal.codegen.base.RequestKinds.requestType; +import androidx.room.compiler.processing.XType; import com.google.auto.value.AutoValue; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.DependencyRequest; -import dagger.model.Key; -import dagger.model.RequestKind; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; +import dagger.spi.model.RequestKind; import java.util.Optional; import javax.lang.model.type.TypeMirror; @@ -47,12 +49,12 @@ public abstract class BindingRequest { // associated with that FrameworkType as well, because we want to ensure that if a request // comes in for that as a dependency first and as a framework instance later, they resolve to // the same binding expression. - // TODO(cgdecker): Instead of doing this, make ComponentBindingExpressions create a - // BindingExpression for the RequestKind that simply delegates to the BindingExpression for the - // FrameworkType. Then there are separate BindingExpressions, but we don't end up doing weird - // things like creating two fields when there should only be one. + // TODO(cgdecker): Instead of doing this, make ComponentRequestRepresentations create a + // RequestRepresentation for the RequestKind that simply delegates to the RequestRepresentation + // for the FrameworkType. Then there are separate RequestRepresentations, but we don't end up + // doing weird things like creating two fields when there should only be one. return new AutoValue_BindingRequest( - key, Optional.of(requestKind), FrameworkType.forRequestKind(requestKind)); + key, requestKind, FrameworkType.forRequestKind(requestKind)); } /** @@ -67,30 +69,27 @@ public abstract class BindingRequest { /** Returns the {@link Key} for the requested binding. */ public abstract Key key(); - /** Returns the request kind associated with this request, if any. */ - public abstract Optional<RequestKind> requestKind(); + /** Returns the request kind associated with this request. */ + public abstract RequestKind requestKind(); /** Returns the framework type associated with this request, if any. */ public abstract Optional<FrameworkType> frameworkType(); /** Returns whether this request is of the given kind. */ public final boolean isRequestKind(RequestKind requestKind) { - return requestKind.equals(requestKind().orElse(null)); + return requestKind.equals(requestKind()); + } + + public final TypeMirror requestedType(XType contributedType, DaggerTypes types) { + return requestedType(toJavac(contributedType), types); } public final TypeMirror requestedType(TypeMirror contributedType, DaggerTypes types) { - if (requestKind().isPresent()) { - return requestType(requestKind().get(), contributedType, types); - } - return types.wrapType(contributedType, frameworkType().get().frameworkClass()); + return requestType(requestKind(), contributedType, types); } /** Returns a name that can be used for the kind of request this is. */ public final String kindName() { - Object requestKindObject = - requestKind().isPresent() - ? requestKind().get() - : frameworkType().get().frameworkClass().getSimpleName(); - return requestKindObject.toString(); + return requestKind().toString(); } } diff --git a/java/dagger/internal/codegen/binding/BindsTypeChecker.java b/java/dagger/internal/codegen/binding/BindsTypeChecker.java index d850fd373..2010597b2 100644 --- a/java/dagger/internal/codegen/binding/BindsTypeChecker.java +++ b/java/dagger/internal/codegen/binding/BindsTypeChecker.java @@ -16,15 +16,16 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.collect.Iterables.getOnlyElement; +import androidx.room.compiler.processing.XType; import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableList; import dagger.internal.codegen.base.ContributionType; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import java.util.Map; -import java.util.Set; import javax.inject.Inject; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; @@ -35,14 +36,14 @@ import javax.lang.model.type.TypeMirror; * Checks the assignability of one type to another, given a {@link ContributionType} context. This * is used by {@link dagger.internal.codegen.validation.BindsMethodValidator} to validate that the * right-hand- side of a {@link dagger.Binds} method is valid, as well as in {@link - * dagger.internal.codegen.writing.DelegateBindingExpression} when the right-hand-side in generated - * code might be an erased type due to accessibility. + * dagger.internal.codegen.writing.DelegateRequestRepresentation} when the right-hand-side in + * generated code might be an erased type due to accessibility. */ public final class BindsTypeChecker { private final DaggerTypes types; private final DaggerElements elements; - // TODO(bcorso): Make this pkg-private. Used by DelegateBindingExpression. + // TODO(bcorso): Make this pkg-private. Used by DelegateRequestRepresentation. @Inject public BindsTypeChecker(DaggerTypes types, DaggerElements elements) { this.types = types; @@ -54,6 +55,15 @@ public final class BindsTypeChecker { * ContributionType} context. */ public boolean isAssignable( + XType rightHandSide, XType leftHandSide, ContributionType contributionType) { + return isAssignable(toJavac(rightHandSide), toJavac(leftHandSide), contributionType); + } + + /** + * Checks the assignability of {@code rightHandSide} to {@code leftHandSide} given a {@link + * ContributionType} context. + */ + public boolean isAssignable( TypeMirror rightHandSide, TypeMirror leftHandSide, ContributionType contributionType) { return types.isAssignable(rightHandSide, desiredAssignableType(leftHandSide, contributionType)); } @@ -67,6 +77,7 @@ public final class BindsTypeChecker { DeclaredType parameterizedSetType = types.getDeclaredType(setElement(), leftHandSide); return methodParameterType(parameterizedSetType, "add"); case SET_VALUES: + // TODO(b/211774331): The left hand side type should be limited to Set types. return methodParameterType(MoreTypes.asDeclared(leftHandSide), "addAll"); case MAP: DeclaredType parameterizedMapType = @@ -98,11 +109,11 @@ public final class BindsTypeChecker { } private TypeElement setElement() { - return elements.getTypeElement(Set.class); + return elements.getTypeElement(TypeNames.SET); } private TypeElement mapElement() { - return elements.getTypeElement(Map.class); + return elements.getTypeElement(TypeNames.MAP); } private TypeMirror unboundedWildcard() { diff --git a/java/dagger/internal/codegen/binding/ChildFactoryMethodEdgeImpl.java b/java/dagger/internal/codegen/binding/ChildFactoryMethodEdgeImpl.java index c0565881a..465d17e4c 100644 --- a/java/dagger/internal/codegen/binding/ChildFactoryMethodEdgeImpl.java +++ b/java/dagger/internal/codegen/binding/ChildFactoryMethodEdgeImpl.java @@ -18,25 +18,25 @@ package dagger.internal.codegen.binding; import static dagger.internal.codegen.base.ElementFormatter.elementToString; -import dagger.model.BindingGraph.ChildFactoryMethodEdge; -import javax.lang.model.element.ExecutableElement; +import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge; +import dagger.spi.model.DaggerExecutableElement; /** An implementation of {@link ChildFactoryMethodEdge}. */ public final class ChildFactoryMethodEdgeImpl implements ChildFactoryMethodEdge { - private final ExecutableElement factoryMethod; + private final DaggerExecutableElement factoryMethod; - ChildFactoryMethodEdgeImpl(ExecutableElement factoryMethod) { + ChildFactoryMethodEdgeImpl(DaggerExecutableElement factoryMethod) { this.factoryMethod = factoryMethod; } @Override - public ExecutableElement factoryMethod() { + public DaggerExecutableElement factoryMethod() { return factoryMethod; } @Override public String toString() { - return elementToString(factoryMethod); + return elementToString(factoryMethod.java()); } } diff --git a/java/dagger/internal/codegen/binding/ComponentCreatorDescriptor.java b/java/dagger/internal/codegen/binding/ComponentCreatorDescriptor.java index 5ea30ed42..29d5b0821 100644 --- a/java/dagger/internal/codegen/binding/ComponentCreatorDescriptor.java +++ b/java/dagger/internal/codegen/binding/ComponentCreatorDescriptor.java @@ -16,15 +16,19 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.MoreElements.isAnnotationPresent; -import static com.google.auto.common.MoreTypes.asTypeElement; import static com.google.common.base.Verify.verify; import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.getCreatorAnnotations; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.getCreatorAnnotations; +import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotations; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; - -import com.google.auto.common.MoreTypes; +import static dagger.internal.codegen.xprocessing.XTypeElements.getAllUnimplementedMethods; + +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableMap; @@ -32,18 +36,12 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; -import dagger.BindsInstance; -import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.base.ComponentCreatorAnnotation; +import dagger.internal.codegen.base.ComponentCreatorKind; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.DependencyRequest; +import dagger.spi.model.DependencyRequest; import java.util.List; -import javax.lang.model.element.Element; -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.ExecutableType; -import javax.lang.model.type.TypeMirror; /** * A descriptor for a component <i>creator</i> type: that is, a type annotated with @@ -61,10 +59,10 @@ public abstract class ComponentCreatorDescriptor { } /** The annotated creator type. */ - public abstract TypeElement typeElement(); + public abstract XTypeElement typeElement(); /** The method that creates and returns a component instance. */ - public abstract ExecutableElement factoryMethod(); + public abstract XMethodElement factoryMethod(); /** * Multimap of component requirements to setter methods that set that requirement. @@ -72,7 +70,7 @@ public abstract class ComponentCreatorDescriptor { * <p>In a valid creator, there will be exactly one element per component requirement, so this * method should only be called when validating the descriptor. */ - abstract ImmutableSetMultimap<ComponentRequirement, ExecutableElement> unvalidatedSetterMethods(); + abstract ImmutableSetMultimap<ComponentRequirement, XMethodElement> unvalidatedSetterMethods(); /** * Multimap of component requirements to factory method parameters that set that requirement. @@ -80,7 +78,7 @@ public abstract class ComponentCreatorDescriptor { * <p>In a valid creator, there will be exactly one element per component requirement, so this * method should only be called when validating the descriptor. */ - abstract ImmutableSetMultimap<ComponentRequirement, VariableElement> + abstract ImmutableSetMultimap<ComponentRequirement, XExecutableParameterElement> unvalidatedFactoryParameters(); /** @@ -90,7 +88,7 @@ public abstract class ComponentCreatorDescriptor { * <p>In a valid creator, there will be exactly one element per component requirement, so this * method should only be called when validating the descriptor. */ - public final ImmutableSetMultimap<ComponentRequirement, Element> + public final ImmutableSetMultimap<ComponentRequirement, XElement> unvalidatedRequirementElements() { // ComponentCreatorValidator ensures that there are either setter methods or factory method // parameters, but not both, so we can cheat a little here since we know that only one of @@ -106,19 +104,19 @@ public abstract class ComponentCreatorDescriptor { * set them. */ @Memoized - ImmutableMap<ComponentRequirement, Element> requirementElements() { + ImmutableMap<ComponentRequirement, XElement> requirementElements() { return flatten(unvalidatedRequirementElements()); } /** Map of component requirements to setter methods for those requirements. */ @Memoized - public ImmutableMap<ComponentRequirement, ExecutableElement> setterMethods() { + public ImmutableMap<ComponentRequirement, XMethodElement> setterMethods() { return flatten(unvalidatedSetterMethods()); } /** Map of component requirements to factory method parameters for those requirements. */ @Memoized - public ImmutableMap<ComponentRequirement, VariableElement> factoryParameters() { + public ImmutableMap<ComponentRequirement, XExecutableParameterElement> factoryParameters() { return flatten(unvalidatedFactoryParameters()); } @@ -148,31 +146,26 @@ public abstract class ComponentCreatorDescriptor { } /** Returns the element in this creator that sets the given {@code requirement}. */ - final Element elementForRequirement(ComponentRequirement requirement) { + final XElement elementForRequirement(ComponentRequirement requirement) { return requirementElements().get(requirement); } /** Creates a new {@link ComponentCreatorDescriptor} for the given creator {@code type}. */ public static ComponentCreatorDescriptor create( - DeclaredType type, - DaggerElements elements, - DaggerTypes types, - DependencyRequestFactory dependencyRequestFactory) { - TypeElement typeElement = asTypeElement(type); - TypeMirror componentType = typeElement.getEnclosingElement().asType(); - - ImmutableSetMultimap.Builder<ComponentRequirement, ExecutableElement> setterMethods = - ImmutableSetMultimap.builder(); - - ExecutableElement factoryMethod = null; - for (ExecutableElement method : elements.getUnimplementedMethods(typeElement)) { - ExecutableType resolvedMethodType = MoreTypes.asExecutable(types.asMemberOf(type, method)); + XTypeElement creator, DaggerTypes types, DependencyRequestFactory dependencyRequestFactory) { + XType componentType = creator.getEnclosingTypeElement().getType(); + ImmutableSetMultimap.Builder<ComponentRequirement, XMethodElement> setterMethods = + ImmutableSetMultimap.builder(); + XMethodElement factoryMethod = null; + for (XMethodElement method : getAllUnimplementedMethods(creator)) { + XMethodType resolvedMethodType = method.asMemberOf(creator.getType()); if (types.isSubtype(componentType, resolvedMethodType.getReturnType())) { + verify(factoryMethod == null); // validation should have ensured there's only 1. factoryMethod = method; } else { - VariableElement parameter = getOnlyElement(method.getParameters()); - TypeMirror parameterType = getOnlyElement(resolvedMethodType.getParameterTypes()); + XExecutableParameterElement parameter = getOnlyElement(method.getParameters()); + XType parameterType = getOnlyElement(resolvedMethodType.getParameterTypes()); setterMethods.put( requirement(method, parameter, parameterType, dependencyRequestFactory, method), method); @@ -180,44 +173,46 @@ public abstract class ComponentCreatorDescriptor { } verify(factoryMethod != null); // validation should have ensured this. - ImmutableSetMultimap.Builder<ComponentRequirement, VariableElement> factoryParameters = - ImmutableSetMultimap.builder(); + ImmutableSetMultimap.Builder<ComponentRequirement, XExecutableParameterElement> + factoryParameters = ImmutableSetMultimap.builder(); - ExecutableType resolvedFactoryMethodType = - MoreTypes.asExecutable(types.asMemberOf(type, factoryMethod)); - List<? extends VariableElement> parameters = factoryMethod.getParameters(); - List<? extends TypeMirror> parameterTypes = resolvedFactoryMethodType.getParameterTypes(); + XMethodType resolvedFactoryMethodType = factoryMethod.asMemberOf(creator.getType()); + List<XExecutableParameterElement> parameters = factoryMethod.getParameters(); + List<XType> parameterTypes = resolvedFactoryMethodType.getParameterTypes(); for (int i = 0; i < parameters.size(); i++) { - VariableElement parameter = parameters.get(i); - TypeMirror parameterType = parameterTypes.get(i); + XExecutableParameterElement parameter = parameters.get(i); + XType parameterType = parameterTypes.get(i); factoryParameters.put( - requirement(factoryMethod, parameter, parameterType, dependencyRequestFactory, parameter), + requirement( + factoryMethod, + parameter, + parameterType, + dependencyRequestFactory, + parameter), parameter); } - // Validation should have ensured exactly one creator annotation is present on the type. - ComponentCreatorAnnotation annotation = getOnlyElement(getCreatorAnnotations(typeElement)); + ComponentCreatorAnnotation annotation = getOnlyElement(getCreatorAnnotations(creator)); return new AutoValue_ComponentCreatorDescriptor( - annotation, typeElement, factoryMethod, setterMethods.build(), factoryParameters.build()); + annotation, creator, factoryMethod, setterMethods.build(), factoryParameters.build()); } private static ComponentRequirement requirement( - ExecutableElement method, - VariableElement parameter, - TypeMirror type, + XMethodElement method, + XExecutableParameterElement parameter, + XType parameterType, DependencyRequestFactory dependencyRequestFactory, - Element elementForVariableName) { - if (isAnnotationPresent(method, BindsInstance.class) - || isAnnotationPresent(parameter, BindsInstance.class)) { + XElement elementForVariableName) { + if (method.hasAnnotation(TypeNames.BINDS_INSTANCE) + || parameter.hasAnnotation(TypeNames.BINDS_INSTANCE)) { DependencyRequest request = - dependencyRequestFactory.forRequiredResolvedVariable(parameter, type); - String variableName = elementForVariableName.getSimpleName().toString(); + dependencyRequestFactory.forRequiredResolvedVariable(parameter, parameterType); return ComponentRequirement.forBoundInstance( - request.key(), request.isNullable(), variableName); + request.key(), request.isNullable(), elementForVariableName); } - return moduleAnnotation(asTypeElement(type)).isPresent() - ? ComponentRequirement.forModule(type) - : ComponentRequirement.forDependency(type); + return parameterType.getTypeElement().hasAnyAnnotation(moduleAnnotations()) + ? ComponentRequirement.forModule(parameterType) + : ComponentRequirement.forDependency(parameterType); } } diff --git a/java/dagger/internal/codegen/binding/ComponentDescriptor.java b/java/dagger/internal/codegen/binding/ComponentDescriptor.java index f6ea62c77..83a83556f 100644 --- a/java/dagger/internal/codegen/binding/ComponentDescriptor.java +++ b/java/dagger/internal/codegen/binding/ComponentDescriptor.java @@ -16,15 +16,23 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.XElementKt.isMethod; +import static androidx.room.compiler.processing.XTypeKt.isVoid; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.langmodel.DaggerTypes.isFutureType; -import static javax.lang.model.element.Modifier.ABSTRACT; +import static dagger.internal.codegen.langmodel.DaggerTypes.isTypeOf; +import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; import static javax.lang.model.type.TypeKind.VOID; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.base.Supplier; @@ -35,24 +43,21 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; +import com.squareup.javapoet.TypeName; import dagger.Component; import dagger.Module; import dagger.Subcomponent; import dagger.internal.codegen.base.ComponentAnnotation; -import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.DependencyRequest; -import dagger.model.Scope; import dagger.producers.CancellationPolicy; -import dagger.producers.ProductionComponent; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Scope; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; -import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; /** @@ -67,6 +72,35 @@ import javax.lang.model.type.TypeMirror; */ @AutoValue public abstract class ComponentDescriptor { + /** Creates a {@link ComponentDescriptor}. */ + static ComponentDescriptor create( + ComponentAnnotation componentAnnotation, + XTypeElement component, + ImmutableSet<ComponentRequirement> componentDependencies, + ImmutableSet<ModuleDescriptor> transitiveModules, + ImmutableMap<XMethodElement, ComponentRequirement> dependenciesByDependencyMethod, + ImmutableSet<Scope> scopes, + ImmutableSet<ComponentDescriptor> subcomponentsFromModules, + ImmutableBiMap<ComponentMethodDescriptor, ComponentDescriptor> subcomponentsByFactoryMethod, + ImmutableBiMap<ComponentMethodDescriptor, ComponentDescriptor> subcomponentsByBuilderMethod, + ImmutableSet<ComponentMethodDescriptor> componentMethods, + Optional<ComponentCreatorDescriptor> creator) { + ComponentDescriptor descriptor = + new AutoValue_ComponentDescriptor( + componentAnnotation, + component, + componentDependencies, + transitiveModules, + dependenciesByDependencyMethod, + scopes, + subcomponentsFromModules, + subcomponentsByFactoryMethod, + subcomponentsByBuilderMethod, + componentMethods, + creator); + return descriptor; + } + /** The annotation that specifies that {@link #typeElement()} is a component. */ public abstract ComponentAnnotation annotation(); @@ -95,11 +129,11 @@ public abstract class ComponentDescriptor { * The element that defines the component. This is the element to which the {@link #annotation()} * was applied. */ - public abstract TypeElement typeElement(); + public abstract XTypeElement typeElement(); /** * The set of component dependencies listed in {@link Component#dependencies} or {@link - * ProductionComponent#dependencies()}. + * dagger.producers.ProductionComponent#dependencies()}. */ public abstract ImmutableSet<ComponentRequirement> dependencies(); @@ -107,8 +141,8 @@ public abstract class ComponentDescriptor { public final ImmutableSet<ComponentRequirement> dependenciesAndConcreteModules() { return Stream.concat( moduleTypes().stream() - .filter(dep -> !dep.getModifiers().contains(ABSTRACT)) - .map(module -> ComponentRequirement.forModule(module.asType())), + .filter(dep -> !dep.isAbstract()) + .map(module -> ComponentRequirement.forModule(module.getType())), dependencies().stream()) .collect(toImmutableSet()); } @@ -120,7 +154,7 @@ public abstract class ComponentDescriptor { public abstract ImmutableSet<ModuleDescriptor> modules(); /** The types of the {@link #modules()}. */ - public final ImmutableSet<TypeElement> moduleTypes() { + public final ImmutableSet<XTypeElement> moduleTypes() { return modules().stream().map(ModuleDescriptor::moduleElement).collect(toImmutableSet()); } @@ -142,7 +176,7 @@ public abstract class ComponentDescriptor { .filter( module -> module.bindings().stream().anyMatch(ContributionBinding::requiresModuleInstance)) - .map(module -> ComponentRequirement.forModule(module.moduleElement().asType())) + .map(module -> ComponentRequirement.forModule(module.moduleElement().getType())) .forEach(requirements::add); requirements.addAll(dependencies()); requirements.addAll( @@ -158,15 +192,17 @@ public abstract class ComponentDescriptor { * the enclosing type of the method; a method may be declared by a supertype of the actual * dependency. */ - public abstract ImmutableMap<ExecutableElement, ComponentRequirement> + public abstract ImmutableMap<XMethodElement, ComponentRequirement> dependenciesByDependencyMethod(); /** The {@linkplain #dependencies() component dependency} that defines a method. */ - public final ComponentRequirement getDependencyThatDefinesMethod(Element method) { - checkArgument( - method instanceof ExecutableElement, "method must be an executable element: %s", method); - return checkNotNull( - dependenciesByDependencyMethod().get(method), "no dependency implements %s", method); + public final ComponentRequirement getDependencyThatDefinesMethod(XElement method) { + checkArgument(isMethod(method), "method must be an executable element: %s", method); + checkState( + dependenciesByDependencyMethod().containsKey(method), + "no dependency implements %s", + method); + return dependenciesByDependencyMethod().get(method); } /** The scopes of the component. */ @@ -201,7 +237,7 @@ public abstract class ComponentDescriptor { /** Returns a map of {@link #childComponents()} indexed by {@link #typeElement()}. */ @Memoized - public ImmutableMap<TypeElement, ComponentDescriptor> childComponentsByElement() { + public ImmutableMap<XTypeElement, ComponentDescriptor> childComponentsByElement() { return Maps.uniqueIndex(childComponents(), ComponentDescriptor::typeElement); } @@ -219,7 +255,7 @@ public abstract class ComponentDescriptor { abstract ImmutableBiMap<ComponentMethodDescriptor, ComponentDescriptor> childComponentsDeclaredByBuilderEntryPoints(); - private final Supplier<ImmutableMap<TypeElement, ComponentDescriptor>> + private final Supplier<ImmutableMap<XTypeElement, ComponentDescriptor>> childComponentsByBuilderType = Suppliers.memoize( () -> @@ -231,7 +267,7 @@ public abstract class ComponentDescriptor { child -> child))); /** Returns the child component with the given builder type. */ - final ComponentDescriptor getChildComponentWithBuilderType(TypeElement builderType) { + final ComponentDescriptor getChildComponentWithBuilderType(XTypeElement builderType) { return checkNotNull( childComponentsByBuilderType.get().get(builderType), "no child component found for builder type %s", @@ -283,7 +319,8 @@ public abstract class ComponentDescriptor { */ public final Optional<CancellationPolicy> cancellationPolicy() { return isProduction() - ? Optional.ofNullable(typeElement().getAnnotation(CancellationPolicy.class)) + // TODO(bcorso): Get values from XAnnotation instead of using CancellationPolicy directly. + ? Optional.ofNullable(toJavac(typeElement()).getAnnotation(CancellationPolicy.class)) : Optional.empty(); } @@ -302,7 +339,7 @@ public abstract class ComponentDescriptor { @AutoValue public abstract static class ComponentMethodDescriptor { /** The method itself. Note that this may be declared on a supertype of the component. */ - public abstract ExecutableElement methodElement(); + public abstract XMethodElement methodElement(); /** * The dependency request for production, provision, and subcomponent creator methods. Absent @@ -321,16 +358,16 @@ public abstract class ComponentDescriptor { public TypeMirror resolvedReturnType(DaggerTypes types) { checkState(dependencyRequest().isPresent()); - TypeMirror returnType = methodElement().getReturnType(); - if (returnType.getKind().isPrimitive() || returnType.getKind().equals(VOID)) { - return returnType; + XType returnType = methodElement().getReturnType(); + if (isPrimitive(returnType) || isVoid(returnType)) { + return toJavac(returnType); } return BindingRequest.bindingRequest(dependencyRequest().get()) - .requestedType(dependencyRequest().get().key().type(), types); + .requestedType(dependencyRequest().get().key().type().java(), types); } /** A {@link ComponentMethodDescriptor}builder for a method. */ - public static Builder builder(ExecutableElement method) { + public static Builder builder(XMethodElement method) { return new AutoValue_ComponentDescriptor_ComponentMethodDescriptor.Builder() .methodElement(method); } @@ -340,7 +377,7 @@ public abstract class ComponentDescriptor { @CanIgnoreReturnValue public interface Builder { /** @see ComponentMethodDescriptor#methodElement() */ - Builder methodElement(ExecutableElement methodElement); + Builder methodElement(XMethodElement methodElement); /** @see ComponentMethodDescriptor#dependencyRequest() */ Builder dependencyRequest(DependencyRequest dependencyRequest); @@ -362,15 +399,23 @@ public abstract class ComponentDescriptor { * Returns {@code true} if a method could be a component entry point but not a members-injection * method. */ - static boolean isComponentContributionMethod(DaggerElements elements, ExecutableElement method) { + static boolean isComponentContributionMethod(XMethodElement method) { + return isComponentContributionMethod(toJavac(method)); + } + + /** + * Returns {@code true} if a method could be a component entry point but not a members-injection + * method. + */ + static boolean isComponentContributionMethod(ExecutableElement method) { return method.getParameters().isEmpty() && !method.getReturnType().getKind().equals(VOID) - && !elements.getTypeElement(Object.class).equals(method.getEnclosingElement()) + && !isTypeOf(TypeName.OBJECT, method.getEnclosingElement().asType()) && !NON_CONTRIBUTING_OBJECT_METHOD_NAMES.contains(method.getSimpleName().toString()); } /** Returns {@code true} if a method could be a component production entry point. */ - static boolean isComponentProductionMethod(DaggerElements elements, ExecutableElement method) { - return isComponentContributionMethod(elements, method) && isFutureType(method.getReturnType()); + static boolean isComponentProductionMethod(XMethodElement method) { + return isComponentContributionMethod(method) && isFutureType(method.getReturnType()); } } diff --git a/java/dagger/internal/codegen/binding/ComponentDescriptorFactory.java b/java/dagger/internal/codegen/binding/ComponentDescriptorFactory.java index f13aa50c0..94d32cd4f 100644 --- a/java/dagger/internal/codegen/binding/ComponentDescriptorFactory.java +++ b/java/dagger/internal/codegen/binding/ComponentDescriptorFactory.java @@ -16,126 +16,120 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.MoreElements.asType; -import static com.google.auto.common.MoreTypes.asTypeElement; +import static androidx.room.compiler.processing.XTypeKt.isVoid; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.internal.codegen.base.ComponentAnnotation.rootComponentAnnotation; import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation; +import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotations; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.creatorAnnotationsFor; +import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation; import static dagger.internal.codegen.base.Scopes.productionScope; -import static dagger.internal.codegen.base.Scopes.scopesOf; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.creatorAnnotationsFor; import static dagger.internal.codegen.binding.ComponentDescriptor.isComponentContributionMethod; import static dagger.internal.codegen.binding.ConfigurationAnnotations.enclosedAnnotatedTypes; import static dagger.internal.codegen.binding.ConfigurationAnnotations.isSubcomponentCreator; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import static javax.lang.model.type.TypeKind.DECLARED; -import static javax.lang.model.type.TypeKind.VOID; +import static dagger.internal.codegen.xprocessing.XTypeElements.getAllUnimplementedMethods; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import static javax.lang.model.util.ElementFilter.methodsIn; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import dagger.internal.codegen.base.ComponentAnnotation; +import dagger.internal.codegen.base.DaggerSuperficialValidation; import dagger.internal.codegen.base.ModuleAnnotation; import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Scope; +import dagger.spi.model.Scope; import java.util.Optional; -import java.util.function.Function; import javax.inject.Inject; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.TypeMirror; /** A factory for {@link ComponentDescriptor}s. */ public final class ComponentDescriptorFactory { + private final XProcessingEnv processingEnv; private final DaggerElements elements; private final DaggerTypes types; private final DependencyRequestFactory dependencyRequestFactory; private final ModuleDescriptor.Factory moduleDescriptorFactory; private final InjectionAnnotations injectionAnnotations; + private final DaggerSuperficialValidation superficialValidation; @Inject ComponentDescriptorFactory( + XProcessingEnv processingEnv, DaggerElements elements, DaggerTypes types, DependencyRequestFactory dependencyRequestFactory, ModuleDescriptor.Factory moduleDescriptorFactory, - InjectionAnnotations injectionAnnotations) { + InjectionAnnotations injectionAnnotations, + DaggerSuperficialValidation superficialValidation) { + this.processingEnv = processingEnv; this.elements = elements; this.types = types; this.dependencyRequestFactory = dependencyRequestFactory; this.moduleDescriptorFactory = moduleDescriptorFactory; this.injectionAnnotations = injectionAnnotations; + this.superficialValidation = superficialValidation; } /** Returns a descriptor for a root component type. */ - public ComponentDescriptor rootComponentDescriptor(TypeElement typeElement) { - return create( - typeElement, - checkAnnotation( - typeElement, - ComponentAnnotation::rootComponentAnnotation, - "must have a component annotation")); + public ComponentDescriptor rootComponentDescriptor(XTypeElement typeElement) { + Optional<ComponentAnnotation> annotation = + rootComponentAnnotation(typeElement, superficialValidation); + checkArgument(annotation.isPresent(), "%s must have a component annotation", typeElement); + return create(typeElement, annotation.get()); } /** Returns a descriptor for a subcomponent type. */ - public ComponentDescriptor subcomponentDescriptor(TypeElement typeElement) { - return create( - typeElement, - checkAnnotation( - typeElement, - ComponentAnnotation::subcomponentAnnotation, - "must have a subcomponent annotation")); + public ComponentDescriptor subcomponentDescriptor(XTypeElement typeElement) { + Optional<ComponentAnnotation> annotation = + subcomponentAnnotation(typeElement, superficialValidation); + checkArgument(annotation.isPresent(), "%s must have a subcomponent annotation", typeElement); + return create(typeElement, annotation.get()); } /** * Returns a descriptor for a fictional component based on a module type in order to validate its * bindings. */ - public ComponentDescriptor moduleComponentDescriptor(TypeElement typeElement) { - return create( - typeElement, - ComponentAnnotation.fromModuleAnnotation( - checkAnnotation( - typeElement, ModuleAnnotation::moduleAnnotation, "must have a module annotation"))); - } - - private static <A> A checkAnnotation( - TypeElement typeElement, - Function<TypeElement, Optional<A>> annotationFunction, - String message) { - return annotationFunction - .apply(typeElement) - .orElseThrow(() -> new IllegalArgumentException(typeElement + " " + message)); + public ComponentDescriptor moduleComponentDescriptor(XTypeElement typeElement) { + Optional<ModuleAnnotation> annotation = moduleAnnotation(typeElement, superficialValidation); + checkArgument(annotation.isPresent(), "%s must have a module annotation", typeElement); + return create(typeElement, ComponentAnnotation.fromModuleAnnotation(annotation.get())); } private ComponentDescriptor create( - TypeElement typeElement, ComponentAnnotation componentAnnotation) { + XTypeElement typeElement, ComponentAnnotation componentAnnotation) { ImmutableSet<ComponentRequirement> componentDependencies = componentAnnotation.dependencyTypes().stream() .map(ComponentRequirement::forDependency) .collect(toImmutableSet()); - ImmutableMap.Builder<ExecutableElement, ComponentRequirement> dependenciesByDependencyMethod = + ImmutableMap.Builder<XMethodElement, ComponentRequirement> dependenciesByDependencyMethod = ImmutableMap.builder(); - for (ComponentRequirement componentDependency : componentDependencies) { for (ExecutableElement dependencyMethod : - methodsIn(elements.getAllMembers(componentDependency.typeElement()))) { - if (isComponentContributionMethod(elements, dependencyMethod)) { - dependenciesByDependencyMethod.put(dependencyMethod, componentDependency); + methodsIn(elements.getAllMembers(toJavac(componentDependency.typeElement())))) { + if (isComponentContributionMethod(dependencyMethod)) { + dependenciesByDependencyMethod.put( + (XMethodElement) toXProcessing(dependencyMethod, processingEnv), componentDependency); } } } // Start with the component's modules. For fictional components built from a module, start with // that module. - ImmutableSet<TypeElement> modules = + ImmutableSet<XTypeElement> modules = componentAnnotation.isRealComponent() ? componentAnnotation.modules() : ImmutableSet.of(typeElement); @@ -143,13 +137,12 @@ public final class ComponentDescriptorFactory { ImmutableSet<ModuleDescriptor> transitiveModules = moduleDescriptorFactory.transitiveModules(modules); - ImmutableSet.Builder<ComponentDescriptor> subcomponentsFromModules = ImmutableSet.builder(); - for (ModuleDescriptor module : transitiveModules) { - for (SubcomponentDeclaration subcomponentDeclaration : module.subcomponentDeclarations()) { - TypeElement subcomponent = subcomponentDeclaration.subcomponentType(); - subcomponentsFromModules.add(subcomponentDescriptor(subcomponent)); - } - } + ImmutableSet<ComponentDescriptor> subcomponentsFromModules = + transitiveModules.stream() + .flatMap(transitiveModule -> transitiveModule.subcomponentDeclarations().stream()) + .map(SubcomponentDeclaration::subcomponentType) + .map(this::subcomponentDescriptor) + .collect(toImmutableSet()); ImmutableSet.Builder<ComponentMethodDescriptor> componentMethodsBuilder = ImmutableSet.builder(); @@ -158,11 +151,9 @@ public final class ComponentDescriptorFactory { ImmutableBiMap.Builder<ComponentMethodDescriptor, ComponentDescriptor> subcomponentsByBuilderMethod = ImmutableBiMap.builder(); if (componentAnnotation.isRealComponent()) { - ImmutableSet<ExecutableElement> unimplementedMethods = - elements.getUnimplementedMethods(typeElement); - for (ExecutableElement componentMethod : unimplementedMethods) { + for (XMethodElement componentMethod : getAllUnimplementedMethods(typeElement)) { ComponentMethodDescriptor componentMethodDescriptor = - getDescriptorForComponentMethod(typeElement, componentAnnotation, componentMethod); + getDescriptorForComponentMethod(componentAnnotation, typeElement, componentMethod); componentMethodsBuilder.add(componentMethodDescriptor); componentMethodDescriptor .subcomponent() @@ -180,32 +171,29 @@ public final class ComponentDescriptorFactory { } // Validation should have ensured that this set will have at most one element. - ImmutableSet<DeclaredType> enclosedCreators = - creatorAnnotationsFor(componentAnnotation).stream() - .flatMap( - creatorAnnotation -> - enclosedAnnotatedTypes(typeElement, creatorAnnotation).stream()) - .collect(toImmutableSet()); + ImmutableSet<XTypeElement> enclosedCreators = + enclosedAnnotatedTypes(typeElement, creatorAnnotationsFor(componentAnnotation)); Optional<ComponentCreatorDescriptor> creatorDescriptor = enclosedCreators.isEmpty() ? Optional.empty() : Optional.of( ComponentCreatorDescriptor.create( - getOnlyElement(enclosedCreators), elements, types, dependencyRequestFactory)); + getOnlyElement(enclosedCreators), types, dependencyRequestFactory)); - ImmutableSet<Scope> scopes = scopesOf(typeElement); + ImmutableSet<Scope> scopes = injectionAnnotations.getScopes(typeElement); if (componentAnnotation.isProduction()) { - scopes = ImmutableSet.<Scope>builder().addAll(scopes).add(productionScope(elements)).build(); + scopes = + ImmutableSet.<Scope>builder().addAll(scopes).add(productionScope(processingEnv)).build(); } - return new AutoValue_ComponentDescriptor( + return ComponentDescriptor.create( componentAnnotation, typeElement, componentDependencies, transitiveModules, dependenciesByDependencyMethod.build(), scopes, - subcomponentsFromModules.build(), + subcomponentsFromModules, subcomponentsByFactoryMethod.build(), subcomponentsByBuilderMethod.build(), componentMethodsBuilder.build(), @@ -213,36 +201,30 @@ public final class ComponentDescriptorFactory { } private ComponentMethodDescriptor getDescriptorForComponentMethod( - TypeElement componentElement, ComponentAnnotation componentAnnotation, - ExecutableElement componentMethod) { + XTypeElement componentElement, + XMethodElement componentMethod) { ComponentMethodDescriptor.Builder descriptor = ComponentMethodDescriptor.builder(componentMethod); - ExecutableType resolvedComponentMethod = - MoreTypes.asExecutable( - types.asMemberOf(MoreTypes.asDeclared(componentElement.asType()), componentMethod)); - TypeMirror returnType = resolvedComponentMethod.getReturnType(); - if (returnType.getKind().equals(DECLARED) - && !injectionAnnotations.getQualifier(componentMethod).isPresent()) { - TypeElement returnTypeElement = asTypeElement(returnType); - if (subcomponentAnnotation(returnTypeElement).isPresent()) { + XMethodType resolvedComponentMethod = componentMethod.asMemberOf(componentElement.getType()); + XType returnType = resolvedComponentMethod.getReturnType(); + if (isDeclared(returnType) && !injectionAnnotations.getQualifier(componentMethod).isPresent()) { + XTypeElement returnTypeElement = returnType.getTypeElement(); + if (returnTypeElement.hasAnyAnnotation(subcomponentAnnotations())) { // It's a subcomponent factory method. There is no dependency request, and there could be // any number of parameters. Just return the descriptor. return descriptor.subcomponent(subcomponentDescriptor(returnTypeElement)).build(); } if (isSubcomponentCreator(returnTypeElement)) { descriptor.subcomponent( - subcomponentDescriptor(asType(returnTypeElement.getEnclosingElement()))); + subcomponentDescriptor(returnTypeElement.getEnclosingTypeElement())); } } switch (componentMethod.getParameters().size()) { case 0: - checkArgument( - !returnType.getKind().equals(VOID), - "component method cannot be void: %s", - componentMethod); + checkArgument(!isVoid(returnType), "component method cannot be void: %s", componentMethod); descriptor.dependencyRequest( componentAnnotation.isProduction() ? dependencyRequestFactory.forComponentProductionMethod( @@ -253,9 +235,11 @@ public final class ComponentDescriptorFactory { case 1: checkArgument( - returnType.getKind().equals(VOID) - || MoreTypes.equivalence() - .equivalent(returnType, resolvedComponentMethod.getParameterTypes().get(0)), + isVoid(returnType) + // TODO(bcorso): Replace this with isSameType()? + || returnType + .getTypeName() + .equals(resolvedComponentMethod.getParameterTypes().get(0).getTypeName()), "members injection method must return void or parameter type: %s", componentMethod); descriptor.dependencyRequest( diff --git a/java/dagger/internal/codegen/binding/ComponentKind.java b/java/dagger/internal/codegen/binding/ComponentKind.java deleted file mode 100644 index 1cb3d7c1c..000000000 --- a/java/dagger/internal/codegen/binding/ComponentKind.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2014 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.binding; - -import static com.google.auto.common.MoreElements.isAnnotationPresent; -import static com.google.common.collect.Sets.immutableEnumSet; -import static dagger.internal.codegen.extension.DaggerStreams.stream; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import static dagger.internal.codegen.extension.DaggerStreams.valuesOf; -import static java.util.EnumSet.allOf; - -import com.google.common.collect.ImmutableSet; -import dagger.Component; -import dagger.Module; -import dagger.Subcomponent; -import dagger.producers.ProducerModule; -import dagger.producers.ProductionComponent; -import dagger.producers.ProductionSubcomponent; -import java.lang.annotation.Annotation; -import java.util.Optional; -import javax.lang.model.element.TypeElement; - -/** Enumeration of the different kinds of components. */ -public enum ComponentKind { - /** {@code @Component} */ - COMPONENT(Component.class, true, false), - - /** {@code @Subcomponent} */ - SUBCOMPONENT(Subcomponent.class, false, false), - - /** {@code @ProductionComponent} */ - PRODUCTION_COMPONENT(ProductionComponent.class, true, true), - - /** {@code @ProductionSubcomponent} */ - PRODUCTION_SUBCOMPONENT(ProductionSubcomponent.class, false, true), - - /** - * Kind for a descriptor that was generated from a {@link Module} instead of a component type in - * order to validate the module's bindings. - */ - MODULE(Module.class, true, false), - - /** - * Kind for a descriptor was generated from a {@link ProducerModule} instead of a component type - * in order to validate the module's bindings. - */ - PRODUCER_MODULE(ProducerModule.class, true, true), - ; - - private static final ImmutableSet<ComponentKind> ROOT_COMPONENT_KINDS = - valuesOf(ComponentKind.class) - .filter(kind -> !kind.isForModuleValidation()) - .filter(kind -> kind.isRoot()) - .collect(toImmutableSet()); - - private static final ImmutableSet<ComponentKind> SUBCOMPONENT_KINDS = - valuesOf(ComponentKind.class) - .filter(kind -> !kind.isForModuleValidation()) - .filter(kind -> !kind.isRoot()) - .collect(toImmutableSet()); - - /** Returns the set of kinds for root components. */ - public static ImmutableSet<ComponentKind> rootComponentKinds() { - return ROOT_COMPONENT_KINDS; - } - - /** Returns the set of kinds for subcomponents. */ - public static ImmutableSet<ComponentKind> subcomponentKinds() { - return SUBCOMPONENT_KINDS; - } - - /** Returns the annotations for components of the given kinds. */ - public static ImmutableSet<Class<? extends Annotation>> annotationsFor( - Iterable<ComponentKind> kinds) { - return stream(kinds).map(ComponentKind::annotation).collect(toImmutableSet()); - } - - /** Returns the set of component kinds the given {@code element} has annotations for. */ - public static ImmutableSet<ComponentKind> getComponentKinds(TypeElement element) { - return valuesOf(ComponentKind.class) - .filter(kind -> isAnnotationPresent(element, kind.annotation())) - .collect(toImmutableSet()); - } - - /** - * Returns the kind of an annotated element if it is annotated with one of the {@linkplain - * #annotation() annotations}. - * - * @throws IllegalArgumentException if the element is annotated with more than one of the - * annotations - */ - public static Optional<ComponentKind> forAnnotatedElement(TypeElement element) { - ImmutableSet<ComponentKind> kinds = getComponentKinds(element); - if (kinds.size() > 1) { - throw new IllegalArgumentException( - element + " cannot be annotated with more than one of " + annotationsFor(kinds)); - } - return kinds.stream().findAny(); - } - - private final Class<? extends Annotation> annotation; - private final boolean isRoot; - private final boolean production; - - ComponentKind( - Class<? extends Annotation> annotation, - boolean isRoot, - boolean production) { - this.annotation = annotation; - this.isRoot = isRoot; - this.production = production; - } - - /** Returns the annotation that marks a component of this kind. */ - public Class<? extends Annotation> annotation() { - return annotation; - } - - /** Returns the kinds of modules that can be used with a component of this kind. */ - public ImmutableSet<ModuleKind> legalModuleKinds() { - return isProducer() - ? immutableEnumSet(allOf(ModuleKind.class)) - : immutableEnumSet(ModuleKind.MODULE); - } - - /** Returns the kinds of subcomponents a component of this kind can have. */ - public ImmutableSet<ComponentKind> legalSubcomponentKinds() { - return isProducer() - ? immutableEnumSet(PRODUCTION_SUBCOMPONENT) - : immutableEnumSet(SUBCOMPONENT, PRODUCTION_SUBCOMPONENT); - } - - /** - * Returns {@code true} if the descriptor is for a root component (not a subcomponent) or is for - * {@linkplain #isForModuleValidation() module-validation}. - */ - public boolean isRoot() { - return isRoot; - } - - /** Returns true if this is a production component. */ - public boolean isProducer() { - return production; - } - - /** Returns {@code true} if the descriptor is for a module in order to validate its bindings. */ - public boolean isForModuleValidation() { - switch (this) { - case MODULE: - case PRODUCER_MODULE: - return true; - default: - // fall through - } - return false; - } -} diff --git a/java/dagger/internal/codegen/binding/ComponentNodeImpl.java b/java/dagger/internal/codegen/binding/ComponentNodeImpl.java index 0947c252c..5dbf63371 100644 --- a/java/dagger/internal/codegen/binding/ComponentNodeImpl.java +++ b/java/dagger/internal/codegen/binding/ComponentNodeImpl.java @@ -20,10 +20,10 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableSet; -import dagger.model.BindingGraph.ComponentNode; -import dagger.model.ComponentPath; -import dagger.model.DependencyRequest; -import dagger.model.Scope; +import dagger.spi.model.BindingGraph.ComponentNode; +import dagger.spi.model.ComponentPath; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Scope; /** An implementation of {@link ComponentNode} that also exposes the {@link ComponentDescriptor}. */ @AutoValue diff --git a/java/dagger/internal/codegen/binding/ComponentRequirement.java b/java/dagger/internal/codegen/binding/ComponentRequirement.java index 9d54f4dc3..d021eb6da 100644 --- a/java/dagger/internal/codegen/binding/ComponentRequirement.java +++ b/java/dagger/internal/codegen/binding/ComponentRequirement.java @@ -16,36 +16,28 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.XElementKt.isConstructor; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.binding.SourceFiles.simpleVariableName; -import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent; -import static javax.lang.model.element.ElementKind.CONSTRUCTOR; -import static javax.lang.model.element.Modifier.ABSTRACT; -import static javax.lang.model.element.Modifier.PRIVATE; -import static javax.lang.model.element.Modifier.STATIC; - -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; +import static dagger.internal.codegen.xprocessing.XElements.asConstructor; +import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation; +import static dagger.internal.codegen.xprocessing.XTypeElements.isNested; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static kotlin.streams.jdk8.StreamsKt.asStream; + +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; -import com.google.common.base.Equivalence; -import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; -import dagger.Binds; -import dagger.BindsOptionalOf; -import dagger.Provides; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.model.BindingKind; -import dagger.model.Key; -import dagger.multibindings.Multibinds; -import dagger.producers.Produces; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.BindingKind; +import dagger.spi.model.Key; import java.util.Optional; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; /** A type that a component needs an instance of. */ @AutoValue @@ -73,6 +65,8 @@ public abstract class ComponentRequirement { } } + private XType type; + /** The kind of requirement. */ public abstract Kind kind(); @@ -82,20 +76,17 @@ public abstract class ComponentRequirement { return kind().isBoundInstance(); } - /** - * The type of the instance the component must have, wrapped so that requirements can be used as - * value types. - */ - public abstract Equivalence.Wrapper<TypeMirror> wrappedType(); + /** The type of the instance the component must have. */ + abstract TypeName typeName(); /** The type of the instance the component must have. */ - public TypeMirror type() { - return wrappedType().get(); + public XType type() { + return type; } /** The element associated with the type of this requirement. */ - public TypeElement typeElement() { - return MoreTypes.asTypeElement(type()); + public XTypeElement typeElement() { + return type.getTypeElement(); } /** The action a component builder should take if it {@code null} is passed. */ @@ -118,15 +109,15 @@ public abstract class ComponentRequirement { abstract Optional<NullPolicy> overrideNullPolicy(); /** The requirement's null policy. */ - public NullPolicy nullPolicy(DaggerElements elements, KotlinMetadataUtil metadataUtil) { + public NullPolicy nullPolicy() { if (overrideNullPolicy().isPresent()) { return overrideNullPolicy().get(); } switch (kind()) { case MODULE: - return componentCanMakeNewInstances(typeElement(), metadataUtil) + return componentCanMakeNewInstances(typeElement()) ? NullPolicy.NEW - : requiresAPassedInstance(elements, metadataUtil) ? NullPolicy.THROW : NullPolicy.ALLOW; + : requiresAPassedInstance() ? NullPolicy.THROW : NullPolicy.ALLOW; case DEPENDENCY: case BOUND_INSTANCE: return NullPolicy.THROW; @@ -138,13 +129,12 @@ public abstract class ComponentRequirement { * Returns true if the passed {@link ComponentRequirement} requires a passed instance in order to * be used within a component. */ - public boolean requiresAPassedInstance(DaggerElements elements, KotlinMetadataUtil metadataUtil) { + public boolean requiresAPassedInstance() { if (!kind().isModule()) { // Bound instances and dependencies always require the user to provide an instance. return true; } - return requiresModuleInstance(elements, metadataUtil) - && !componentCanMakeNewInstances(typeElement(), metadataUtil); + return requiresModuleInstance() && !componentCanMakeNewInstances(typeElement()); } /** @@ -157,33 +147,27 @@ public abstract class ComponentRequirement { * <p>Alternatively, if the module is a Kotlin Object then the binding methods are considered * {@code static}, requiring no module instance. */ - private boolean requiresModuleInstance(DaggerElements elements, KotlinMetadataUtil metadataUtil) { - boolean isKotlinObject = - metadataUtil.isObjectClass(typeElement()) - || metadataUtil.isCompanionObjectClass(typeElement()); - if (isKotlinObject) { + private boolean requiresModuleInstance() { + if (typeElement().isKotlinObject() || typeElement().isCompanionObject()) { return false; } - - ImmutableSet<ExecutableElement> methods = elements.getLocalAndInheritedMethods(typeElement()); - return methods.stream() + return asStream(typeElement().getAllNonPrivateInstanceMethods()) .filter(this::isBindingMethod) - .map(ExecutableElement::getModifiers) - .anyMatch(modifiers -> !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC)); + .anyMatch(method -> !method.isAbstract() && !method.isStatic()); } - private boolean isBindingMethod(ExecutableElement method) { + private boolean isBindingMethod(XMethodElement method) { // TODO(cgdecker): At the very least, we should have utility methods to consolidate this stuff // in one place; listing individual annotations all over the place is brittle. - return isAnyAnnotationPresent( + return hasAnyAnnotation( method, - Provides.class, - Produces.class, + TypeNames.PROVIDES, + TypeNames.PRODUCES, // TODO(ronshapiro): it would be cool to have internal meta-annotations that could describe // these, like @AbstractBindingMethod - Binds.class, - Multibinds.class, - BindsOptionalOf.class); + TypeNames.BINDS, + TypeNames.MULTIBINDS, + TypeNames.BINDS_OPTIONAL_OF); } /** The key for this requirement, if one is available. */ @@ -194,42 +178,57 @@ public abstract class ComponentRequirement { /** Returns a parameter spec for this requirement. */ public ParameterSpec toParameterSpec() { - return ParameterSpec.builder(TypeName.get(type()), variableName()).build(); + return ParameterSpec.builder(type().getTypeName(), variableName()).build(); } - public static ComponentRequirement forDependency(TypeMirror type) { - return new AutoValue_ComponentRequirement( - Kind.DEPENDENCY, - MoreTypes.equivalence().wrap(checkNotNull(type)), - Optional.empty(), - Optional.empty(), - simpleVariableName(MoreTypes.asTypeElement(type))); + public static ComponentRequirement forDependency(XType type) { + checkArgument(isDeclared(checkNotNull(type))); + ComponentRequirement requirement = + new AutoValue_ComponentRequirement( + Kind.DEPENDENCY, + type.getTypeName(), + Optional.empty(), + Optional.empty(), + simpleVariableName(type.getTypeElement().getClassName())); + requirement.type = type; + return requirement; } - public static ComponentRequirement forModule(TypeMirror type) { - return new AutoValue_ComponentRequirement( - Kind.MODULE, - MoreTypes.equivalence().wrap(checkNotNull(type)), - Optional.empty(), - Optional.empty(), - simpleVariableName(MoreTypes.asTypeElement(type))); + public static ComponentRequirement forModule(XType type) { + checkArgument(isDeclared(checkNotNull(type))); + ComponentRequirement requirement = + new AutoValue_ComponentRequirement( + Kind.MODULE, + type.getTypeName(), + Optional.empty(), + Optional.empty(), + simpleVariableName(type.getTypeElement().getClassName())); + requirement.type = type; + return requirement; } - static ComponentRequirement forBoundInstance(Key key, boolean nullable, String variableName) { - return new AutoValue_ComponentRequirement( - Kind.BOUND_INSTANCE, - MoreTypes.equivalence().wrap(key.type()), - nullable ? Optional.of(NullPolicy.ALLOW) : Optional.empty(), - Optional.of(key), - variableName); + static ComponentRequirement forBoundInstance( + Key key, boolean nullable, XElement elementForVariableName) { + ComponentRequirement requirement = + new AutoValue_ComponentRequirement( + Kind.BOUND_INSTANCE, + key.type().xprocessing().getTypeName(), + nullable ? Optional.of(NullPolicy.ALLOW) : Optional.empty(), + Optional.of(key), + toJavac(elementForVariableName).getSimpleName().toString()); + requirement.type = key.type().xprocessing(); + return requirement; } public static ComponentRequirement forBoundInstance(ContributionBinding binding) { checkArgument(binding.kind().equals(BindingKind.BOUND_INSTANCE)); - return forBoundInstance( - binding.key(), - binding.nullableType().isPresent(), - binding.bindingElement().get().getSimpleName().toString()); + ComponentRequirement requirement = + forBoundInstance( + binding.key(), + binding.nullableType().isPresent(), + binding.bindingElement().get()); + requirement.type = binding.key().type().xprocessing(); + return requirement; } /** @@ -237,9 +236,11 @@ public abstract class ComponentRequirement { * rather than requiring that they be passed. */ // TODO(bcorso): Should this method throw if its called knowing that an instance is not needed? - public static boolean componentCanMakeNewInstances( - TypeElement typeElement, KotlinMetadataUtil metadataUtil) { - switch (typeElement.getKind()) { + public static boolean componentCanMakeNewInstances(XTypeElement typeElement) { + // TODO(bcorso): Investigate how we should replace this in XProcessing. It's not clear what the + // complete set of kinds are in XProcessing and if they're mutually exclusive. For example, + // does XTypeElement#isClass() cover XTypeElement#isDataClass(), etc? + switch (toJavac(typeElement).getKind()) { case CLASS: break; case ENUM: @@ -247,10 +248,10 @@ public abstract class ComponentRequirement { case INTERFACE: return false; default: - throw new AssertionError("TypeElement cannot have kind: " + typeElement.getKind()); + throw new AssertionError("TypeElement cannot have kind: " + toJavac(typeElement).getKind()); } - if (typeElement.getModifiers().contains(ABSTRACT)) { + if (typeElement.isAbstract()) { return false; } @@ -258,15 +259,10 @@ public abstract class ComponentRequirement { return false; } - if (metadataUtil.isObjectClass(typeElement) - || metadataUtil.isCompanionObjectClass(typeElement)) { - return false; - } - - for (Element enclosed : typeElement.getEnclosedElements()) { - if (enclosed.getKind().equals(CONSTRUCTOR) - && MoreElements.asExecutable(enclosed).getParameters().isEmpty() - && !enclosed.getModifiers().contains(PRIVATE)) { + for (XElement enclosed : typeElement.getEnclosedElements()) { + if (isConstructor(enclosed) + && asConstructor(enclosed).getParameters().isEmpty() + && !asConstructor(enclosed).isPrivate()) { return true; } } @@ -276,17 +272,7 @@ public abstract class ComponentRequirement { return false; } - private static boolean requiresEnclosingInstance(TypeElement typeElement) { - switch (typeElement.getNestingKind()) { - case TOP_LEVEL: - return false; - case MEMBER: - return !typeElement.getModifiers().contains(STATIC); - case ANONYMOUS: - case LOCAL: - return true; - } - throw new AssertionError( - "TypeElement cannot have nesting kind: " + typeElement.getNestingKind()); + private static boolean requiresEnclosingInstance(XTypeElement typeElement) { + return isNested(typeElement) && !typeElement.isStatic(); } } diff --git a/java/dagger/internal/codegen/binding/ConfigurationAnnotations.java b/java/dagger/internal/codegen/binding/ConfigurationAnnotations.java index 539a66ac6..75ae97e68 100644 --- a/java/dagger/internal/codegen/binding/ConfigurationAnnotations.java +++ b/java/dagger/internal/codegen/binding/ConfigurationAnnotations.java @@ -17,37 +17,25 @@ package dagger.internal.codegen.binding; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.Iterables.consumingIterable; -import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation; -import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation; -import static dagger.internal.codegen.base.MoreAnnotationMirrors.getTypeListValue; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.subcomponentCreatorAnnotations; -import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent; -import static javax.lang.model.util.ElementFilter.typesIn; +import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotations; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.subcomponentCreatorAnnotations; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static dagger.internal.codegen.xprocessing.XAnnotations.getClassName; +import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation; -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; -import com.google.common.collect.ImmutableList; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.collect.Sets; +import com.squareup.javapoet.ClassName; import dagger.Component; import dagger.Module; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import java.lang.annotation.Annotation; -import java.util.ArrayDeque; import java.util.List; import java.util.Optional; -import java.util.Queue; -import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; /** * Utility methods related to dagger configuration annotations (e.g.: {@link Component} and {@link @@ -55,24 +43,27 @@ import javax.lang.model.type.TypeMirror; */ public final class ConfigurationAnnotations { - public static Optional<TypeElement> getSubcomponentCreator(TypeElement subcomponent) { - checkArgument(subcomponentAnnotation(subcomponent).isPresent()); - for (TypeElement nestedType : typesIn(subcomponent.getEnclosedElements())) { - if (isSubcomponentCreator(nestedType)) { - return Optional.of(nestedType); - } - } - return Optional.empty(); + public static Optional<XTypeElement> getSubcomponentCreator(XTypeElement subcomponent) { + checkArgument(subcomponent.hasAnyAnnotation(subcomponentAnnotations())); + return subcomponent.getEnclosedTypeElements().stream() + .filter(ConfigurationAnnotations::isSubcomponentCreator) + // TODO(bcorso): Consider doing toOptional() instead since there should be at most 1. + .findFirst(); } - static boolean isSubcomponentCreator(Element element) { - return isAnyAnnotationPresent(element, subcomponentCreatorAnnotations()); + static boolean isSubcomponentCreator(XElement element) { + return hasAnyAnnotation(element, subcomponentCreatorAnnotations()); } - // Dagger 1 support. - public static ImmutableList<TypeMirror> getModuleInjects(AnnotationMirror moduleAnnotation) { - checkNotNull(moduleAnnotation); - return getTypeListValue(moduleAnnotation, "injects"); + /** Returns the first type that specifies this' nullability, or empty if none. */ + public static Optional<XAnnotation> getNullableAnnotation(XElement element) { + return element.getAllAnnotations().stream() + .filter(annotation -> getClassName(annotation).simpleName().contentEquals("Nullable")) + .findFirst(); + } + + public static Optional<XType> getNullableType(XElement element) { + return getNullableAnnotation(element).map(XAnnotation::getType); } /** Returns the first type that specifies this' nullability, or empty if none. */ @@ -86,71 +77,12 @@ public final class ConfigurationAnnotations { return Optional.empty(); } - /** - * Returns the full set of modules transitively {@linkplain Module#includes included} from the - * given seed modules. If a module is malformed and a type listed in {@link Module#includes} is - * not annotated with {@link Module}, it is ignored. - * - * @deprecated Use {@link ComponentDescriptor#modules()}. - */ - @Deprecated - public static ImmutableSet<TypeElement> getTransitiveModules( - DaggerTypes types, DaggerElements elements, Iterable<TypeElement> seedModules) { - TypeMirror objectType = elements.getTypeElement(Object.class).asType(); - Queue<TypeElement> moduleQueue = new ArrayDeque<>(); - Iterables.addAll(moduleQueue, seedModules); - Set<TypeElement> moduleElements = Sets.newLinkedHashSet(); - for (TypeElement moduleElement : consumingIterable(moduleQueue)) { - moduleAnnotation(moduleElement) - .ifPresent( - moduleAnnotation -> { - ImmutableSet.Builder<TypeElement> moduleDependenciesBuilder = - ImmutableSet.builder(); - moduleDependenciesBuilder.addAll(moduleAnnotation.includes()); - // We don't recur on the parent class because we don't want the parent class as a - // root that the component depends on, and also because we want the dependencies - // rooted against this element, not the parent. - addIncludesFromSuperclasses( - types, moduleElement, moduleDependenciesBuilder, objectType); - ImmutableSet<TypeElement> moduleDependencies = moduleDependenciesBuilder.build(); - moduleElements.add(moduleElement); - for (TypeElement dependencyType : moduleDependencies) { - if (!moduleElements.contains(dependencyType)) { - moduleQueue.add(dependencyType); - } - } - }); - } - return ImmutableSet.copyOf(moduleElements); - } - /** Returns the enclosed types annotated with the given annotation. */ - public static ImmutableList<DeclaredType> enclosedAnnotatedTypes( - TypeElement typeElement, Class<? extends Annotation> annotation) { - final ImmutableList.Builder<DeclaredType> builders = ImmutableList.builder(); - for (TypeElement element : typesIn(typeElement.getEnclosedElements())) { - if (MoreElements.isAnnotationPresent(element, annotation)) { - builders.add(MoreTypes.asDeclared(element.asType())); - } - } - return builders.build(); - } - - /** Traverses includes from superclasses and adds them into the builder. */ - private static void addIncludesFromSuperclasses( - DaggerTypes types, - TypeElement element, - ImmutableSet.Builder<TypeElement> builder, - TypeMirror objectType) { - // Also add the superclass to the queue, in case any @Module definitions were on that. - TypeMirror superclass = element.getSuperclass(); - while (!types.isSameType(objectType, superclass) - && superclass.getKind().equals(TypeKind.DECLARED)) { - element = MoreElements.asType(types.asElement(superclass)); - moduleAnnotation(element) - .ifPresent(moduleAnnotation -> builder.addAll(moduleAnnotation.includes())); - superclass = element.getSuperclass(); - } + public static ImmutableSet<XTypeElement> enclosedAnnotatedTypes( + XTypeElement typeElement, ImmutableSet<ClassName> annotations) { + return typeElement.getEnclosedTypeElements().stream() + .filter(enclosedType -> hasAnyAnnotation(enclosedType, annotations)) + .collect(toImmutableSet()); } private ConfigurationAnnotations() {} diff --git a/java/dagger/internal/codegen/binding/ContributionBinding.java b/java/dagger/internal/codegen/binding/ContributionBinding.java index 1942e8c1f..2c555600d 100644 --- a/java/dagger/internal/codegen/binding/ContributionBinding.java +++ b/java/dagger/internal/codegen/binding/ContributionBinding.java @@ -17,30 +17,26 @@ package dagger.internal.codegen.binding; import static dagger.internal.codegen.base.MoreAnnotationMirrors.unwrapOptionalEquivalence; -import static dagger.internal.codegen.binding.ContributionBinding.FactoryCreationStrategy.CLASS_CONSTRUCTOR; -import static dagger.internal.codegen.binding.ContributionBinding.FactoryCreationStrategy.DELEGATE; -import static dagger.internal.codegen.binding.ContributionBinding.FactoryCreationStrategy.SINGLETON_INSTANCE; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; import static java.util.Arrays.asList; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XElementKt; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.base.Equivalence; -import com.google.common.base.Preconditions; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; import dagger.internal.codegen.base.ContributionType; import dagger.internal.codegen.base.ContributionType.HasContributionType; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.SetType; -import dagger.model.BindingKind; -import dagger.model.DependencyRequest; -import dagger.model.Key; +import dagger.internal.codegen.xprocessing.XTypes; +import dagger.spi.model.BindingKind; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; import java.util.Optional; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; /** * An abstract class for a value object representing the mechanism by which a {@link Key} can be @@ -49,7 +45,7 @@ import javax.lang.model.type.TypeMirror; public abstract class ContributionBinding extends Binding implements HasContributionType { /** Returns the type that specifies this' nullability, absent if not nullable. */ - public abstract Optional<DeclaredType> nullableType(); + public abstract Optional<XType> nullableType(); public abstract Optional<Equivalence.Wrapper<AnnotationMirror>> wrappedMapKeyAnnotation(); @@ -58,16 +54,16 @@ public abstract class ContributionBinding extends Binding implements HasContribu } /** If {@link #bindingElement()} is a method that returns a primitive type, returns that type. */ - public final Optional<TypeMirror> contributedPrimitiveType() { + public final Optional<XType> contributedPrimitiveType() { return bindingElement() - .filter(bindingElement -> bindingElement instanceof ExecutableElement) - .map(bindingElement -> MoreElements.asExecutable(bindingElement).getReturnType()) - .filter(type -> type.getKind().isPrimitive()); + .filter(XElementKt::isMethod) + .map(bindingElement -> asMethod(bindingElement).getReturnType()) + .filter(XTypes::isPrimitive); } @Override public boolean requiresModuleInstance() { - return !isContributingModuleKotlinObject().orElse(false) && super.requiresModuleInstance(); + return !isContributingModuleKotlinObject() && super.requiresModuleInstance(); } @Override @@ -79,51 +75,18 @@ public abstract class ContributionBinding extends Binding implements HasContribu * Returns {@code true} if the contributing module is a Kotlin object. Note that a companion * object is also considered a Kotlin object. */ - abstract Optional<Boolean> isContributingModuleKotlinObject(); - - /** The strategy for getting an instance of a factory for a {@link ContributionBinding}. */ - public enum FactoryCreationStrategy { - /** The factory class is a single instance. */ - SINGLETON_INSTANCE, - /** The factory must be created by calling the constructor. */ - CLASS_CONSTRUCTOR, - /** The factory is simply delegated to another. */ - DELEGATE, + private boolean isContributingModuleKotlinObject() { + return contributingModule().isPresent() + && (contributingModule().get().isKotlinObject() + || contributingModule().get().isCompanionObject()); } /** - * Returns the {@link FactoryCreationStrategy} appropriate for a binding. - * - * <p>Delegate bindings use the {@link FactoryCreationStrategy#DELEGATE} strategy. - * - * <p>Bindings without dependencies that don't require a module instance use the {@link - * FactoryCreationStrategy#SINGLETON_INSTANCE} strategy. - * - * <p>All other bindings use the {@link FactoryCreationStrategy#CLASS_CONSTRUCTOR} strategy. + * The {@link XType type} for the {@code Factory<T>} or {@code Producer<T>} which is created for + * this binding. Uses the binding's key, V in the case of {@code Map<K, FrameworkClass<V>>>}, and + * E {@code Set<E>} for {@link dagger.multibindings.IntoSet @IntoSet} methods. */ - public final FactoryCreationStrategy factoryCreationStrategy() { - switch (kind()) { - case DELEGATE: - return DELEGATE; - case PROVISION: - return dependencies().isEmpty() && !requiresModuleInstance() - ? SINGLETON_INSTANCE - : CLASS_CONSTRUCTOR; - case INJECTION: - case MULTIBOUND_SET: - case MULTIBOUND_MAP: - return dependencies().isEmpty() ? SINGLETON_INSTANCE : CLASS_CONSTRUCTOR; - default: - return CLASS_CONSTRUCTOR; - } - } - - /** - * The {@link TypeMirror type} for the {@code Factory<T>} or {@code Producer<T>} which is created - * for this binding. Uses the binding's key, V in the case of {@code Map<K, FrameworkClass<V>>>}, - * and E {@code Set<E>} for {@link dagger.multibindings.IntoSet @IntoSet} methods. - */ - public final TypeMirror contributedType() { + public final XType contributedType() { switch (contributionType()) { case MAP: return MapType.from(key()).unwrappedFrameworkValueType(); @@ -131,27 +94,11 @@ public abstract class ContributionBinding extends Binding implements HasContribu return SetType.from(key()).elementType(); case SET_VALUES: case UNIQUE: - return key().type(); + return key().type().xprocessing(); } throw new AssertionError(); } - /** - * Returns {@link BindingKind#MULTIBOUND_SET} or {@link - * BindingKind#MULTIBOUND_MAP} if the key is a set or map. - * - * @throws IllegalArgumentException if {@code key} is neither a set nor a map - */ - static BindingKind bindingKindForMultibindingKey(Key key) { - if (SetType.isSet(key)) { - return BindingKind.MULTIBOUND_SET; - } else if (MapType.isMap(key)) { - return BindingKind.MULTIBOUND_MAP; - } else { - throw new IllegalArgumentException(String.format("key is not for a set or map: %s", key)); - } - } - public abstract Builder<?, ?> toBuilder(); /** @@ -170,21 +117,19 @@ public abstract class ContributionBinding extends Binding implements HasContribu public abstract B contributionType(ContributionType contributionType); - public abstract B bindingElement(Element bindingElement); + public abstract B bindingElement(XElement bindingElement); - abstract B bindingElement(Optional<Element> bindingElement); + abstract B bindingElement(Optional<XElement> bindingElement); public final B clearBindingElement() { return bindingElement(Optional.empty()); }; - abstract B contributingModule(TypeElement contributingModule); - - abstract B isContributingModuleKotlinObject(boolean isModuleKotlinObject); + abstract B contributingModule(XTypeElement contributingModule); public abstract B key(Key key); - public abstract B nullableType(Optional<DeclaredType> nullableType); + public abstract B nullableType(Optional<XType> nullableType); abstract B wrappedMapKeyAnnotation( Optional<Equivalence.Wrapper<AnnotationMirror>> wrappedMapKeyAnnotation); @@ -192,16 +137,6 @@ public abstract class ContributionBinding extends Binding implements HasContribu public abstract B kind(BindingKind kind); @CheckReturnValue - abstract C autoBuild(); - - @CheckReturnValue - public C build() { - C binding = autoBuild(); - Preconditions.checkState( - binding.contributingModule().isPresent() - == binding.isContributingModuleKotlinObject().isPresent(), - "The contributionModule and isModuleKotlinObject must both be set together."); - return binding; - } + abstract C build(); } } diff --git a/java/dagger/internal/codegen/binding/DelegateDeclaration.java b/java/dagger/internal/codegen/binding/DelegateDeclaration.java index b6c3c3830..32ecf67fe 100644 --- a/java/dagger/internal/codegen/binding/DelegateDeclaration.java +++ b/java/dagger/internal/codegen/binding/DelegateDeclaration.java @@ -16,12 +16,15 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Preconditions.checkArgument; import static dagger.internal.codegen.base.MoreAnnotationMirrors.wrapOptionalInEquivalence; import static dagger.internal.codegen.binding.MapKeys.getMapKey; -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.base.Equivalence; @@ -29,15 +32,11 @@ import com.google.common.collect.Iterables; import dagger.Binds; import dagger.internal.codegen.base.ContributionType; import dagger.internal.codegen.base.ContributionType.HasContributionType; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.DependencyRequest; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.DependencyRequest; import java.util.Optional; import javax.inject.Inject; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.ExecutableType; /** The declaration for a delegate binding established by a {@link Binds} method. */ @AutoValue @@ -56,26 +55,20 @@ public abstract class DelegateDeclaration extends BindingDeclaration /** A {@link DelegateDeclaration} factory. */ public static final class Factory { - private final DaggerTypes types; private final KeyFactory keyFactory; private final DependencyRequestFactory dependencyRequestFactory; @Inject Factory( - DaggerTypes types, KeyFactory keyFactory, DependencyRequestFactory dependencyRequestFactory) { - this.types = types; this.keyFactory = keyFactory; this.dependencyRequestFactory = dependencyRequestFactory; } - public DelegateDeclaration create( - ExecutableElement bindsMethod, TypeElement contributingModule) { - checkArgument(MoreElements.isAnnotationPresent(bindsMethod, Binds.class)); - ExecutableType resolvedMethod = - MoreTypes.asExecutable( - types.asMemberOf(MoreTypes.asDeclared(contributingModule.asType()), bindsMethod)); + public DelegateDeclaration create(XMethodElement bindsMethod, XTypeElement contributingModule) { + checkArgument(bindsMethod.hasAnnotation(TypeNames.BINDS)); + XMethodType resolvedMethod = bindsMethod.asMemberOf(contributingModule.getType()); DependencyRequest delegateRequest = dependencyRequestFactory.forRequiredResolvedVariable( Iterables.getOnlyElement(bindsMethod.getParameters()), @@ -83,10 +76,10 @@ public abstract class DelegateDeclaration extends BindingDeclaration return new AutoValue_DelegateDeclaration( ContributionType.fromBindingElement(bindsMethod), keyFactory.forBindsMethod(bindsMethod, contributingModule), - Optional.<Element>of(bindsMethod), + Optional.<XElement>of(bindsMethod), Optional.of(contributingModule), delegateRequest, - wrapOptionalInEquivalence(getMapKey(bindsMethod))); + wrapOptionalInEquivalence(getMapKey(toJavac(bindsMethod)))); } } } diff --git a/java/dagger/internal/codegen/binding/DependencyEdgeImpl.java b/java/dagger/internal/codegen/binding/DependencyEdgeImpl.java index f11517e6d..11e1dcf03 100644 --- a/java/dagger/internal/codegen/binding/DependencyEdgeImpl.java +++ b/java/dagger/internal/codegen/binding/DependencyEdgeImpl.java @@ -17,8 +17,9 @@ package dagger.internal.codegen.binding; import dagger.internal.codegen.base.ElementFormatter; -import dagger.model.BindingGraph.DependencyEdge; -import dagger.model.DependencyRequest; +import dagger.spi.model.BindingGraph.DependencyEdge; +import dagger.spi.model.DaggerElement; +import dagger.spi.model.DependencyRequest; /** An implementation of {@link DependencyEdge}. */ final class DependencyEdgeImpl implements DependencyEdge { @@ -46,6 +47,7 @@ final class DependencyEdgeImpl implements DependencyEdge { String string = dependencyRequest .requestElement() + .map(DaggerElement::java) .map(ElementFormatter::elementToString) .orElseGet( () -> diff --git a/java/dagger/internal/codegen/binding/DependencyRequestFactory.java b/java/dagger/internal/codegen/binding/DependencyRequestFactory.java index 707de4ce6..01c3a40d0 100644 --- a/java/dagger/internal/codegen/binding/DependencyRequestFactory.java +++ b/java/dagger/internal/codegen/binding/DependencyRequestFactory.java @@ -16,40 +16,51 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.MoreTypes.isTypeOf; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.RequestKinds.extractKeyType; -import static dagger.internal.codegen.base.RequestKinds.frameworkClass; +import static dagger.internal.codegen.base.RequestKinds.frameworkClassName; import static dagger.internal.codegen.base.RequestKinds.getRequestKind; +import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedParameter; import static dagger.internal.codegen.binding.ConfigurationAnnotations.getNullableType; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.langmodel.DaggerTypes.unwrapType; -import static dagger.model.RequestKind.FUTURE; -import static dagger.model.RequestKind.INSTANCE; -import static dagger.model.RequestKind.MEMBERS_INJECTION; -import static dagger.model.RequestKind.PRODUCER; -import static dagger.model.RequestKind.PROVIDER; +import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; +import static dagger.spi.model.RequestKind.FUTURE; +import static dagger.spi.model.RequestKind.INSTANCE; +import static dagger.spi.model.RequestKind.MEMBERS_INJECTION; +import static dagger.spi.model.RequestKind.PRODUCER; +import static dagger.spi.model.RequestKind.PROVIDER; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XVariableElement; +import androidx.room.compiler.processing.compat.XConverters; import com.google.common.collect.ImmutableSet; -import com.google.common.util.concurrent.ListenableFuture; import dagger.Lazy; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.OptionalType; -import dagger.model.DependencyRequest; -import dagger.model.Key; -import dagger.model.RequestKind; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.DaggerElement; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; +import dagger.spi.model.RequestKind; import java.util.List; import java.util.Optional; import javax.inject.Inject; import javax.inject.Provider; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeMirror; /** @@ -59,15 +70,27 @@ import javax.lang.model.type.TypeMirror; * may mean that the type will be generated in a later round of processing. */ public final class DependencyRequestFactory { + private final XProcessingEnv processingEnv; private final KeyFactory keyFactory; private final InjectionAnnotations injectionAnnotations; @Inject - DependencyRequestFactory(KeyFactory keyFactory, InjectionAnnotations injectionAnnotations) { + DependencyRequestFactory( + XProcessingEnv processingEnv, + KeyFactory keyFactory, + InjectionAnnotations injectionAnnotations) { + this.processingEnv = processingEnv; this.keyFactory = keyFactory; this.injectionAnnotations = injectionAnnotations; } + ImmutableSet<DependencyRequest> forRequiredResolvedXVariables( + List<? extends XVariableElement> variables, List<XType> resolvedTypes) { + return forRequiredResolvedVariables( + variables.stream().map(XConverters::toJavac).collect(toImmutableList()), + resolvedTypes.stream().map(XConverters::toJavac).collect(toImmutableList())); + } + ImmutableSet<DependencyRequest> forRequiredResolvedVariables( List<? extends VariableElement> variables, List<? extends TypeMirror> resolvedTypes) { checkState(resolvedTypes.size() == variables.size()); @@ -114,7 +137,7 @@ public final class DependencyRequestFactory { case MAP: MapType mapType = MapType.from(multibindingKey); for (RequestKind kind : WRAPPING_MAP_VALUE_FRAMEWORK_TYPES) { - if (mapType.valuesAreTypeOf(frameworkClass(kind))) { + if (mapType.valuesAreTypeOf(frameworkClassName(kind))) { return kind; } } @@ -130,44 +153,49 @@ public final class DependencyRequestFactory { } DependencyRequest forRequiredResolvedVariable( + XVariableElement variableElement, XType resolvedType) { + return forRequiredResolvedVariable(toJavac(variableElement), toJavac(resolvedType)); + } + + DependencyRequest forRequiredResolvedVariable( VariableElement variableElement, TypeMirror resolvedType) { checkNotNull(variableElement); checkNotNull(resolvedType); // Ban @Assisted parameters, they are not considered dependency requests. - checkArgument(!AssistedInjectionAnnotations.isAssistedParameter(variableElement)); + checkArgument(!isAssistedParameter(toXProcessing(variableElement, processingEnv))); Optional<AnnotationMirror> qualifier = injectionAnnotations.getQualifier(variableElement); return newDependencyRequest(variableElement, resolvedType, qualifier); } public DependencyRequest forComponentProvisionMethod( - ExecutableElement provisionMethod, ExecutableType provisionMethodType) { + XMethodElement provisionMethod, XMethodType provisionMethodType) { checkNotNull(provisionMethod); checkNotNull(provisionMethodType); checkArgument( provisionMethod.getParameters().isEmpty(), "Component provision methods must be empty: %s", provisionMethod); - Optional<AnnotationMirror> qualifier = injectionAnnotations.getQualifier(provisionMethod); + Optional<XAnnotation> qualifier = injectionAnnotations.getQualifier(provisionMethod); return newDependencyRequest(provisionMethod, provisionMethodType.getReturnType(), qualifier); } public DependencyRequest forComponentProductionMethod( - ExecutableElement productionMethod, ExecutableType productionMethodType) { + XMethodElement productionMethod, XMethodType productionMethodType) { checkNotNull(productionMethod); checkNotNull(productionMethodType); checkArgument( productionMethod.getParameters().isEmpty(), "Component production methods must be empty: %s", productionMethod); - TypeMirror type = productionMethodType.getReturnType(); - Optional<AnnotationMirror> qualifier = injectionAnnotations.getQualifier(productionMethod); + XType type = productionMethodType.getReturnType(); + Optional<XAnnotation> qualifier = injectionAnnotations.getQualifier(productionMethod); // Only a component production method can be a request for a ListenableFuture, so we // special-case it here. - if (isTypeOf(ListenableFuture.class, type)) { + if (isTypeOf(type, TypeNames.LISTENABLE_FUTURE)) { return DependencyRequest.builder() .kind(FUTURE) .key(keyFactory.forQualifiedType(qualifier, unwrapType(type))) - .requestElement(productionMethod) + .requestElement(DaggerElement.from(productionMethod)) .build(); } else { return newDependencyRequest(productionMethod, type, qualifier); @@ -175,17 +203,16 @@ public final class DependencyRequestFactory { } DependencyRequest forComponentMembersInjectionMethod( - ExecutableElement membersInjectionMethod, ExecutableType membersInjectionMethodType) { + XMethodElement membersInjectionMethod, XMethodType membersInjectionMethodType) { checkNotNull(membersInjectionMethod); checkNotNull(membersInjectionMethodType); - Optional<AnnotationMirror> qualifier = - injectionAnnotations.getQualifier(membersInjectionMethod); + Optional<XAnnotation> qualifier = injectionAnnotations.getQualifier(membersInjectionMethod); checkArgument(!qualifier.isPresent()); - TypeMirror membersInjectedType = getOnlyElement(membersInjectionMethodType.getParameterTypes()); + XType membersInjectedType = getOnlyElement(membersInjectionMethodType.getParameterTypes()); return DependencyRequest.builder() .kind(MEMBERS_INJECTION) .key(keyFactory.forMembersInjectedType(membersInjectedType)) - .requestElement(membersInjectionMethod) + .requestElement(DaggerElement.from(membersInjectionMethod)) .build(); } @@ -219,12 +246,18 @@ public final class DependencyRequestFactory { } private DependencyRequest newDependencyRequest( + XElement requestElement, XType type, Optional<XAnnotation> qualifier) { + return newDependencyRequest( + toJavac(requestElement), toJavac(type), qualifier.map(XConverters::toJavac)); + } + + private DependencyRequest newDependencyRequest( Element requestElement, TypeMirror type, Optional<AnnotationMirror> qualifier) { RequestKind requestKind = getRequestKind(type); return DependencyRequest.builder() .kind(requestKind) .key(keyFactory.forQualifiedType(qualifier, extractKeyType(type))) - .requestElement(requestElement) + .requestElement(DaggerElement.from(toXProcessing(requestElement, processingEnv))) .isNullable(allowsNull(requestKind, getNullableType(requestElement))) .build(); } diff --git a/java/dagger/internal/codegen/binding/DependencyRequestFormatter.java b/java/dagger/internal/codegen/binding/DependencyRequestFormatter.java index 888dec2d8..8fe9249c1 100644 --- a/java/dagger/internal/codegen/binding/DependencyRequestFormatter.java +++ b/java/dagger/internal/codegen/binding/DependencyRequestFormatter.java @@ -23,11 +23,11 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import dagger.Provides; import dagger.internal.codegen.base.Formatter; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.DependencyRequest; import dagger.producers.Produces; +import dagger.spi.model.DaggerAnnotation; +import dagger.spi.model.DependencyRequest; import java.util.Optional; import javax.inject.Inject; -import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementVisitor; import javax.lang.model.element.ExecutableElement; @@ -67,7 +67,7 @@ public final class DependencyRequestFormatter extends Formatter<DependencyReques public String format(DependencyRequest request) { return request .requestElement() - .map(element -> element.accept(formatVisitor, request)) + .map(element -> element.java().accept(formatVisitor, request)) .orElse(""); } @@ -101,7 +101,8 @@ public final class DependencyRequestFormatter extends Formatter<DependencyReques @Override public String visitVariable(VariableElement variable, DependencyRequest request) { - TypeMirror requestedType = requestType(request.kind(), request.key().type(), types); + TypeMirror requestedType = + requestType(request.kind(), request.key().type().java(), types); return INDENT + formatQualifier(request.key().qualifier()) + requestedType @@ -122,7 +123,7 @@ public final class DependencyRequestFormatter extends Formatter<DependencyReques } }; - private String formatQualifier(Optional<AnnotationMirror> maybeQualifier) { + private String formatQualifier(Optional<DaggerAnnotation> maybeQualifier) { return maybeQualifier.map(qualifier -> qualifier + " ").orElse(""); } diff --git a/java/dagger/internal/codegen/binding/DependencyVariableNamer.java b/java/dagger/internal/codegen/binding/DependencyVariableNamer.java index e01d22ef2..45076fe3d 100644 --- a/java/dagger/internal/codegen/binding/DependencyVariableNamer.java +++ b/java/dagger/internal/codegen/binding/DependencyVariableNamer.java @@ -22,7 +22,7 @@ import com.google.auto.common.MoreTypes; import com.google.common.base.Ascii; import com.google.common.base.CaseFormat; import dagger.Lazy; -import dagger.model.DependencyRequest; +import dagger.spi.model.DependencyRequest; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Provider; @@ -39,10 +39,10 @@ final class DependencyVariableNamer { static String name(DependencyRequest dependency) { if (!dependency.requestElement().isPresent()) { - return simpleVariableName(MoreTypes.asTypeElement(dependency.key().type())); + return simpleVariableName(MoreTypes.asTypeElement(dependency.key().type().java())); } - String variableName = dependency.requestElement().get().getSimpleName().toString(); + String variableName = dependency.requestElement().get().java().getSimpleName().toString(); if (Ascii.isUpperCase(variableName.charAt(0))) { variableName = toLowerCamel(variableName); } diff --git a/java/dagger/internal/codegen/binding/ErrorMessages.java b/java/dagger/internal/codegen/binding/ErrorMessages.java index 8962ade81..0bc43b208 100644 --- a/java/dagger/internal/codegen/binding/ErrorMessages.java +++ b/java/dagger/internal/codegen/binding/ErrorMessages.java @@ -16,15 +16,19 @@ package dagger.internal.codegen.binding; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; + +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; import dagger.internal.codegen.base.ComponentAnnotation; +import dagger.internal.codegen.base.ComponentCreatorAnnotation; +import dagger.internal.codegen.base.ComponentKind; import java.util.Set; import java.util.function.Function; import java.util.function.UnaryOperator; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; /** The collection of error messages to be reported back to users. */ public final class ErrorMessages { @@ -199,18 +203,18 @@ public final class ErrorMessages { } public final String factoryMethodReturnsSupertypeWithMissingMethods( - TypeElement component, - TypeElement componentBuilder, - TypeMirror returnType, - ExecutableElement buildMethod, - Set<ExecutableElement> additionalMethods) { + XTypeElement component, + XTypeElement componentBuilder, + XType returnType, + XMethodElement buildMethod, + Set<XMethodElement> additionalMethods) { return String.format( "%1$s.%2$s() returns %3$s, but %4$s declares additional component method(s): %5$s. In " + "order to provide type-safe access to these methods, override %2$s() to return " + "%4$s", componentBuilder.getQualifiedName(), - buildMethod.getSimpleName(), - returnType, + getSimpleName(buildMethod), + returnType.getTypeName(), component.getQualifiedName(), Joiner.on(", ").join(additionalMethods)); } diff --git a/java/dagger/internal/codegen/binding/FrameworkField.java b/java/dagger/internal/codegen/binding/FrameworkField.java index 3b0b73fbe..5f72fe2e4 100644 --- a/java/dagger/internal/codegen/binding/FrameworkField.java +++ b/java/dagger/internal/codegen/binding/FrameworkField.java @@ -16,8 +16,10 @@ package dagger.internal.codegen.binding; -import static dagger.model.BindingKind.MEMBERS_INJECTOR; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static dagger.spi.model.BindingKind.MEMBERS_INJECTOR; +import androidx.room.compiler.processing.XType; import com.google.auto.value.AutoValue; import com.google.common.base.CaseFormat; import com.squareup.javapoet.ClassName; @@ -29,7 +31,6 @@ import javax.lang.model.element.ElementVisitor; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementKindVisitor8; /** @@ -69,24 +70,22 @@ public abstract class FrameworkField { * one for the binding's type. */ public static FrameworkField forBinding( - ContributionBinding binding, Optional<ClassName> frameworkClass) { + ContributionBinding binding, Optional<ClassName> frameworkClassName) { return create( - frameworkClass.orElse( - ClassName.get( - FrameworkType.forBindingType(binding.bindingType()).frameworkClass())), - TypeName.get(fieldValueType(binding)), + frameworkClassName.orElse(binding.frameworkType().frameworkClassName()), + fieldValueType(binding).getTypeName(), frameworkFieldName(binding)); } - private static TypeMirror fieldValueType(ContributionBinding binding) { + private static XType fieldValueType(ContributionBinding binding) { return binding.contributionType().isMultibinding() ? binding.contributedType() - : binding.key().type(); + : binding.key().type().xprocessing(); } private static String frameworkFieldName(ContributionBinding binding) { if (binding.bindingElement().isPresent()) { - String name = BINDING_ELEMENT_NAME.visit(binding.bindingElement().get(), binding); + String name = BINDING_ELEMENT_NAME.visit(toJavac(binding.bindingElement().get()), binding); return binding.kind().equals(MEMBERS_INJECTOR) ? name + "MembersInjector" : name; } return KeyVariableNamer.name(binding.key()); diff --git a/java/dagger/internal/codegen/binding/FrameworkType.java b/java/dagger/internal/codegen/binding/FrameworkType.java index 6b160b613..0c0868c19 100644 --- a/java/dagger/internal/codegen/binding/FrameworkType.java +++ b/java/dagger/internal/codegen/binding/FrameworkType.java @@ -18,66 +18,49 @@ package dagger.internal.codegen.binding; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; -import static dagger.model.RequestKind.INSTANCE; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; -import dagger.Lazy; -import dagger.internal.DoubleCheck; -import dagger.internal.ProviderOfLazy; import dagger.internal.codegen.base.RequestKinds; import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.DependencyRequest; -import dagger.model.RequestKind; -import dagger.producers.Produced; -import dagger.producers.Producer; -import dagger.producers.internal.Producers; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.RequestKind; import java.util.Optional; -import javax.inject.Provider; import javax.lang.model.type.TypeMirror; /** One of the core types initialized as fields in a generated component. */ public enum FrameworkType { - /** A {@link Provider}. */ + /** A {@link javax.inject.Provider}. */ PROVIDER { @Override - public Class<?> frameworkClass() { - return Provider.class; - } - - @Override - public Optional<RequestKind> requestKind() { - return Optional.of(RequestKind.PROVIDER); - } - - @Override public CodeBlock to(RequestKind requestKind, CodeBlock from) { switch (requestKind) { case INSTANCE: return CodeBlock.of("$L.get()", from); case LAZY: - return CodeBlock.of("$T.lazy($L)", DoubleCheck.class, from); + return CodeBlock.of("$T.lazy($L)", TypeNames.DOUBLE_CHECK, from); case PROVIDER: return from; case PROVIDER_OF_LAZY: - return CodeBlock.of("$T.create($L)", ProviderOfLazy.class, from); + return CodeBlock.of("$T.create($L)", TypeNames.PROVIDER_OF_LAZY, from); case PRODUCER: - return CodeBlock.of("$T.producerFromProvider($L)", Producers.class, from); + return CodeBlock.of("$T.producerFromProvider($L)", TypeNames.PRODUCERS, from); case FUTURE: - return CodeBlock.of("$T.immediateFuture($L)", Futures.class, to(INSTANCE, from)); + return CodeBlock.of( + "$T.immediateFuture($L)", TypeNames.FUTURES, to(RequestKind.INSTANCE, from)); case PRODUCED: - return CodeBlock.of("$T.successful($L)", Produced.class, to(INSTANCE, from)); + return CodeBlock.of( + "$T.successful($L)", TypeNames.PRODUCED, to(RequestKind.INSTANCE, from)); default: throw new IllegalArgumentException( @@ -96,36 +79,24 @@ public enum FrameworkType { return from; case PROVIDER_OF_LAZY: - TypeMirror lazyType = types.rewrapType(from.type(), Lazy.class); - return Expression.create(types.wrapType(lazyType, Provider.class), codeBlock); + TypeMirror lazyType = types.rewrapType(from.type(), TypeNames.LAZY); + return Expression.create(types.wrapType(lazyType, TypeNames.PROVIDER), codeBlock); case FUTURE: return Expression.create( - types.rewrapType(from.type(), ListenableFuture.class), codeBlock); + types.rewrapType(from.type(), TypeNames.LISTENABLE_FUTURE), codeBlock); default: return Expression.create( - types.rewrapType(from.type(), RequestKinds.frameworkClass(requestKind)), codeBlock); + types.rewrapType(from.type(), RequestKinds.frameworkClassName(requestKind)), + codeBlock); } } }, - /** A {@link Producer}. */ + /** A {@link dagger.producers.Producer}. */ PRODUCER_NODE { @Override - public Class<?> frameworkClass() { - // TODO(cgdecker): Replace this with new class for representing internal producer nodes. - // Currently the new class is CancellableProducer, but it may be changed to ProducerNode and - // made to not implement Producer. - return Producer.class; - } - - @Override - public Optional<RequestKind> requestKind() { - return Optional.empty(); - } - - @Override public CodeBlock to(RequestKind requestKind, CodeBlock from) { switch (requestKind) { case FUTURE: @@ -145,7 +116,7 @@ public enum FrameworkType { switch (requestKind) { case FUTURE: return Expression.create( - types.rewrapType(from.type(), ListenableFuture.class), + types.rewrapType(from.type(), TypeNames.LISTENABLE_FUTURE), to(requestKind, from.codeBlock())); case PRODUCER: @@ -156,8 +127,7 @@ public enum FrameworkType { String.format("Cannot request a %s from a %s", requestKind, this)); } } - }, - ; + }; /** Returns the framework type appropriate for fields for a given binding type. */ public static FrameworkType forBindingType(BindingType bindingType) { @@ -182,15 +152,34 @@ public enum FrameworkType { } /** The class of fields of this type. */ - public abstract Class<?> frameworkClass(); + public ClassName frameworkClassName() { + switch (this) { + case PROVIDER: + return TypeNames.PROVIDER; + case PRODUCER_NODE: + // TODO(cgdecker): Replace this with new class for representing internal producer nodes. + // Currently the new class is CancellableProducer, but it may be changed to ProducerNode and + // made to not implement Producer. + return TypeNames.PRODUCER; + } + throw new AssertionError("Unknown value: " + this.name()); + } - /** Returns the {@link #frameworkClass()} parameterized with a type. */ + /** Returns the {@link #frameworkClassName()} parameterized with a type. */ public ParameterizedTypeName frameworkClassOf(TypeName valueType) { - return ParameterizedTypeName.get(ClassName.get(frameworkClass()), valueType); + return ParameterizedTypeName.get(frameworkClassName(), valueType); } /** The request kind that an instance of this framework type can satisfy directly, if any. */ - public abstract Optional<RequestKind> requestKind(); + public RequestKind requestKind() { + switch (this) { + case PROVIDER: + return RequestKind.PROVIDER; + case PRODUCER_NODE: + return RequestKind.PRODUCER; + } + throw new AssertionError("Unknown value: " + this.name()); + } /** * Returns a {@link CodeBlock} that evaluates to a requested object given an expression that diff --git a/java/dagger/internal/codegen/binding/FrameworkTypeMapper.java b/java/dagger/internal/codegen/binding/FrameworkTypeMapper.java index 85463aff8..776fac6b7 100644 --- a/java/dagger/internal/codegen/binding/FrameworkTypeMapper.java +++ b/java/dagger/internal/codegen/binding/FrameworkTypeMapper.java @@ -18,8 +18,8 @@ package dagger.internal.codegen.binding; import static dagger.internal.codegen.binding.BindingType.PRODUCTION; -import dagger.model.RequestKind; import dagger.producers.Producer; +import dagger.spi.model.RequestKind; import javax.inject.Provider; /** diff --git a/java/dagger/internal/codegen/binding/InjectBindingRegistry.java b/java/dagger/internal/codegen/binding/InjectBindingRegistry.java index 5203130f4..ceb5024b4 100644 --- a/java/dagger/internal/codegen/binding/InjectBindingRegistry.java +++ b/java/dagger/internal/codegen/binding/InjectBindingRegistry.java @@ -16,16 +16,17 @@ package dagger.internal.codegen.binding; +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XFieldElement; +import androidx.room.compiler.processing.XMethodElement; import com.google.errorprone.annotations.CanIgnoreReturnValue; import dagger.Component; import dagger.Provides; import dagger.internal.codegen.base.SourceFileGenerationException; import dagger.internal.codegen.base.SourceFileGenerator; -import dagger.model.Key; +import dagger.spi.model.Key; import java.util.Optional; import javax.inject.Inject; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; /** * Maintains the collection of provision bindings from {@link Inject} constructors and members @@ -53,10 +54,13 @@ public interface InjectBindingRegistry { Optional<ProvisionBinding> getOrFindMembersInjectorProvisionBinding(Key key); @CanIgnoreReturnValue - Optional<ProvisionBinding> tryRegisterConstructor(ExecutableElement constructorElement); + Optional<ProvisionBinding> tryRegisterInjectConstructor(XConstructorElement constructorElement); @CanIgnoreReturnValue - Optional<MembersInjectionBinding> tryRegisterMembersInjectedType(TypeElement typeElement); + Optional<MembersInjectionBinding> tryRegisterInjectField(XFieldElement fieldElement); + + @CanIgnoreReturnValue + Optional<MembersInjectionBinding> tryRegisterInjectMethod(XMethodElement methodElement); /** * This method ensures that sources for all registered {@link Binding bindings} (either explicitly diff --git a/java/dagger/internal/codegen/binding/InjectionAnnotations.java b/java/dagger/internal/codegen/binding/InjectionAnnotations.java index 748755e08..250f5ee59 100644 --- a/java/dagger/internal/codegen/binding/InjectionAnnotations.java +++ b/java/dagger/internal/codegen/binding/InjectionAnnotations.java @@ -16,33 +16,59 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.XElementKt.isConstructor; +import static androidx.room.compiler.processing.XElementKt.isField; +import static androidx.room.compiler.processing.XElementKt.isMethod; +import static androidx.room.compiler.processing.XElementKt.isMethodParameter; +import static androidx.room.compiler.processing.XElementKt.isTypeElement; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; import static com.google.auto.common.MoreElements.asType; import static com.google.auto.common.MoreElements.asVariable; -import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.MoreAnnotationValues.getStringValue; +import static dagger.internal.codegen.binding.SourceFiles.factoryNameForElement; import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable; import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; +import static dagger.internal.codegen.extension.DaggerCollectors.onlyElement; +import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror; +import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; +import static dagger.internal.codegen.xprocessing.XElements.asMethodParameter; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; import static javax.lang.model.element.Modifier.STATIC; import static javax.lang.model.util.ElementFilter.constructorsIn; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.common.AnnotationMirrors; -import com.google.auto.common.SuperficialValidation; import com.google.common.base.Equivalence; import com.google.common.base.Equivalence.Wrapper; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import dagger.internal.InjectedFieldSignature; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.base.DaggerSuperficialValidation; +import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.extension.DaggerCollectors; import dagger.internal.codegen.extension.DaggerStreams; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.spi.model.DaggerAnnotation; +import dagger.spi.model.Scope; import java.util.Optional; import java.util.stream.Stream; import javax.inject.Inject; -import javax.inject.Qualifier; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -56,21 +82,148 @@ public final class InjectionAnnotations { private static final Equivalence<AnnotationMirror> EQUIVALENCE = AnnotationMirrors.equivalence(); + private final XProcessingEnv processingEnv; private final DaggerElements elements; private final KotlinMetadataUtil kotlinMetadataUtil; + private final DaggerSuperficialValidation superficialValidation; + private final CompilerOptions compilerOptions; @Inject - InjectionAnnotations(DaggerElements elements, KotlinMetadataUtil kotlinMetadataUtil) { + InjectionAnnotations( + XProcessingEnv processingEnv, + DaggerElements elements, + KotlinMetadataUtil kotlinMetadataUtil, + DaggerSuperficialValidation superficialValidation, + CompilerOptions compilerOptions) { + this.processingEnv = processingEnv; this.elements = elements; this.kotlinMetadataUtil = kotlinMetadataUtil; + this.superficialValidation = superficialValidation; + this.compilerOptions = compilerOptions; } - public Optional<AnnotationMirror> getQualifier(Element e) { - if (!SuperficialValidation.validateElement(e)) { - throw new TypeNotPresentException(e.toString(), null); + /** + * Returns the scope on the given element if it exists. + * + * <p>The {@code ScopeMetadata} is used to avoid superficial validation on unnecessary + * annotations. If the {@code ScopeMetadata} does not exist, then all annotations must be + * superficially validated before we can determine if they are scopes or not. + * + * @throws IllegalArgumentException if the given element has more than one scope. + */ + public Optional<Scope> getScope(XElement element) { + return getScopes(element).stream().collect(toOptional()); + } + + /** + * Returns the scopes on the given element, or an empty set if none exist. + * + * <p>Note: Use {@link #getScope(XElement)} if the usage of the scope on the given element has + * already been validated and known to be unique. This method should typically only be used in the + * process of such validation. + * + * <p>The {@code ScopeMetadata} is used to avoid superficial validation on unnecessary + * annotations. If the {@code ScopeMetadata} does not exist, then all annotations must be + * superficially validated before we can determine if they are scopes or not. + */ + public ImmutableSet<Scope> getScopes(XElement element) { + superficialValidation.validateTypeOf(element); + ImmutableSet<Scope> scopes = + getScopesFromScopeMetadata(element) + .orElseGet( + () -> { + // Validate the annotation types before we check for @Scope, otherwise the @Scope + // annotation may appear to be missing (b/213880825). + superficialValidation.validateAnnotationTypesOf(element); + return element.getAllAnnotations().stream() + .filter(InjectionAnnotations::hasScopeAnnotation) + .map(DaggerAnnotation::from) + .map(Scope::scope) + .collect(toImmutableSet()); + }); + + // Fully validate each scope to ensure its values are also valid. + scopes.stream() + .map(scope -> scope.scopeAnnotation().xprocessing()) + .forEach(scope -> superficialValidation.validateAnnotationOf(element, scope)); + return scopes; + } + + private Optional<ImmutableSet<Scope>> getScopesFromScopeMetadata(XElement element) { + Optional<XAnnotation> scopeMetadata = getScopeMetadata(element); + if (!scopeMetadata.isPresent()) { + return Optional.empty(); + } + String scopeName = scopeMetadata.get().getAsString("value"); + if (scopeName.isEmpty()) { + return Optional.of(ImmutableSet.of()); + } + XAnnotation scopeAnnotation = + element.getAllAnnotations().stream() + .filter( + annotation -> + scopeName.contentEquals( + annotation.getType().getTypeElement().getQualifiedName())) + .collect(onlyElement()); + // Do superficial validation before we convert to a Scope, otherwise the @Scope annotation may + // appear to be missing from the annotation if it's no longer on the classpath. + superficialValidation.validateAnnotationTypeOf(element, scopeAnnotation); + + // If strictSuperficialValidation is disabled, then we fall back to the old behavior where + // we may potentially miss a scope rather than report an exception. + if (compilerOptions.strictSuperficialValidation()) { + return Optional.of(ImmutableSet.of(Scope.scope(DaggerAnnotation.from(scopeAnnotation)))); + } else { + return Scope.isScope(DaggerAnnotation.from(scopeAnnotation)) + ? Optional.of(ImmutableSet.of(Scope.scope(DaggerAnnotation.from(scopeAnnotation)))) + : Optional.empty(); + } + } + + private Optional<XAnnotation> getScopeMetadata(XElement element) { + return getGeneratedNameForScopeMetadata(element) + .flatMap(factoryName -> Optional.ofNullable(processingEnv.findTypeElement(factoryName))) + .flatMap(factory -> Optional.ofNullable(factory.getAnnotation(TypeNames.SCOPE_METADATA))); + } + + private Optional<ClassName> getGeneratedNameForScopeMetadata(XElement element) { + // Currently, we only support ScopeMetadata for inject-constructor types and provides methods. + if (isTypeElement(element)) { + return asTypeElement(element).getConstructors().stream() + .filter(InjectionAnnotations::hasInjectOrAssistedInjectAnnotation) + .findFirst() + .map(SourceFiles::factoryNameForElement); + } else if (isMethod(element) && element.hasAnnotation(TypeNames.PROVIDES)) { + return Optional.of(factoryNameForElement(asMethod(element))); } + return Optional.empty(); + } + + /* + * Returns the qualifier on the given element if it exists. + * + * <p>The {@code QualifierMetadata} is used to avoid superficial validation on unnecessary + * annotations. If the {@code QualifierMetadata} does not exist, then all annotations must be + * superficially validated before we can determine if they are qualifiers or not. + * + * @throws IllegalArgumentException if the given element has more than one qualifier. + */ + public Optional<XAnnotation> getQualifier(XElement element) { + return getQualifier(toJavac(element)).map(qualifier -> toXProcessing(qualifier, processingEnv)); + } + + /* + * Returns the qualifier on the given element if it exists. + * + * <p>The {@code QualifierMetadata} is used to avoid superficial validation on unnecessary + * annotations. If the {@code QualifierMetadata} does not exist, then all annotations must be + * superficially validated before we can determine if they are qualifiers or not. + * + * @throws IllegalArgumentException if the given element has more than one qualifier. + */ + public Optional<AnnotationMirror> getQualifier(Element e) { checkNotNull(e); - ImmutableCollection<? extends AnnotationMirror> qualifierAnnotations = getQualifiers(e); + ImmutableList<? extends AnnotationMirror> qualifierAnnotations = getQualifiers(e); switch (qualifierAnnotations.size()) { case 0: return Optional.empty(); @@ -82,32 +235,187 @@ public final class InjectionAnnotations { } } - public ImmutableCollection<? extends AnnotationMirror> getQualifiers(Element element) { + /* + * Returns the qualifiers on the given element, or an empty set if none exist. + * + * <p>The {@code QualifierMetadata} is used to avoid superficial validation on unnecessary + * annotations. If the {@code QualifierMetadata} does not exist, then all annotations must be + * superficially validated before we can determine if they are qualifiers or not. + */ + public ImmutableSet<XAnnotation> getQualifiers(XElement element) { + return getQualifiers(toJavac(element)).stream() + .map(qualifier -> toXProcessing(qualifier, processingEnv)) + .collect(toImmutableSet()); + } + + /* + * Returns the qualifiers on the given element, or an empty set if none exist. + * + * <p>The {@code QualifierMetadata} is used to avoid superficial validation on unnecessary + * annotations. If the {@code QualifierMetadata} does not exist, then all annotations must be + * superficially validated before we can determine if they are qualifiers or not. + */ + public ImmutableList<? extends AnnotationMirror> getQualifiers(Element element) { + superficialValidation.validateTypeOf(toXProcessing(element, processingEnv)); ImmutableSet<? extends AnnotationMirror> qualifiers = - AnnotationMirrors.getAnnotatedAnnotations(element, Qualifier.class); + getQualifiersFromQualifierMetadata(element) + .orElseGet( + () -> { + // Validate the annotation types before we check for @Qualifier, otherwise the + // @Qualifier annotation may appear to be missing (b/213880825). + superficialValidation.validateAnnotationTypesOf(element); + return element.getAnnotationMirrors().stream() + .filter(InjectionAnnotations::hasQualifierAnnotation) + .collect(toImmutableSet()); + }); + if (element.getKind() == ElementKind.FIELD // static injected fields are not supported, no need to get qualifier from kotlin metadata && !element.getModifiers().contains(STATIC) - && isAnnotationPresent(element, Inject.class) + && hasInjectAnnotation(element) && kotlinMetadataUtil.hasMetadata(element)) { - return Stream.concat( - qualifiers.stream(), getQualifiersForKotlinProperty(asVariable(element)).stream()) - .map(EQUIVALENCE::wrap) // Wrap in equivalence to deduplicate - .distinct() - .map(Wrapper::get) - .collect(DaggerStreams.toImmutableList()); + qualifiers = + Stream.concat( + qualifiers.stream(), getQualifiersForKotlinProperty(asVariable(element)).stream()) + .map(EQUIVALENCE::wrap) // Wrap in equivalence to deduplicate + .distinct() + .map(Wrapper::get) + .collect(DaggerStreams.toImmutableSet()); + } + + // Fully validate each qualifier to ensure its values are also valid. + qualifiers.forEach( + qualifier -> { + superficialValidation.validateAnnotationOf(element, qualifier); + }); + + return qualifiers.asList(); + } + + private Optional<ImmutableSet<? extends AnnotationMirror>> getQualifiersFromQualifierMetadata( + Element javaElement) { + XElement element = toXProcessing(javaElement, processingEnv); + Optional<XAnnotation> qualifierMetadata = getQualifierMetadata(element); + if (!qualifierMetadata.isPresent()) { + return Optional.empty(); + } + ImmutableSet<String> qualifierNames = + ImmutableSet.copyOf(qualifierMetadata.get().getAsStringList("value")); + if (qualifierNames.isEmpty()) { + return Optional.of(ImmutableSet.of()); + } + ImmutableSet<XAnnotation> qualifierAnnotations = + element.getAllAnnotations().stream() + .filter( + annotation -> + qualifierNames.contains( + annotation.getType().getTypeElement().getQualifiedName())) + .collect(toImmutableSet()); + if (qualifierAnnotations.isEmpty()) { + return Optional.of(ImmutableSet.of()); + } + // We should be guaranteed that there's exactly one qualifier since the existance of + // @QualifierMetadata means that this element has already been processed and multiple + // qualifiers would have been caught already. + XAnnotation qualifierAnnotation = getOnlyElement(qualifierAnnotations); + + // Ensure the annotation type is superficially valid before we check for @Qualifier, otherwise + // the @Qualifier marker may appear to be missing from the annotation (b/213880825). + superficialValidation.validateAnnotationTypeOf(element, qualifierAnnotation); + if (compilerOptions.strictSuperficialValidation()) { + return Optional.of(ImmutableSet.of(toJavac(qualifierAnnotation))); } else { - return qualifiers.asList(); + // If strictSuperficialValidation is disabled, then we fall back to the old behavior where + // we may potentially miss a qualifier rather than report an exception. + return hasQualifierAnnotation(toJavac(qualifierAnnotation)) + ? Optional.of(ImmutableSet.of(toJavac(qualifierAnnotation))) + : Optional.empty(); } } + /** + * Returns {@code QualifierMetadata} annotation. + * + * <p>Currently, {@code QualifierMetadata} is only associated with inject constructor parameters, + * inject fields, inject method parameters, provide methods, and provide method parameters. + */ + private Optional<XAnnotation> getQualifierMetadata(XElement element) { + return getGeneratedNameForQualifierMetadata(element) + .flatMap(name -> Optional.ofNullable(processingEnv.findTypeElement(name))) + .flatMap(type -> Optional.ofNullable(type.getAnnotation(TypeNames.QUALIFIER_METADATA))); + } + + private Optional<ClassName> getGeneratedNameForQualifierMetadata(XElement element) { + // Currently we only support @QualifierMetadata for @Inject fields, @Inject method parameters, + // @Inject constructor parameters, @Provides methods, and @Provides method parameters. + if (isField(element) && hasInjectAnnotation(element)) { + return Optional.of(membersInjectorNameForType(closestEnclosingTypeElement(element))); + } else if (isMethod(element) && element.hasAnnotation(TypeNames.PROVIDES)) { + return Optional.of(factoryNameForElement(asMethod(element))); + } else if (isMethodParameter(element)) { + XExecutableElement executableElement = asMethodParameter(element).getEnclosingMethodElement(); + if (isConstructor(executableElement) + && hasInjectOrAssistedInjectAnnotation(executableElement)) { + return Optional.of(factoryNameForElement(executableElement)); + } + if (isMethod(executableElement) && hasInjectAnnotation(executableElement)) { + return Optional.of(membersInjectorNameForType(closestEnclosingTypeElement(element))); + } + if (isMethod(executableElement) && executableElement.hasAnnotation(TypeNames.PROVIDES)) { + return Optional.of(factoryNameForElement(executableElement)); + } + } + return Optional.empty(); + } + + /** Returns the constructors in {@code type} that are annotated with {@link Inject}. */ + public static ImmutableSet<XConstructorElement> injectedConstructors(XTypeElement type) { + return type.getConstructors().stream() + .filter(InjectionAnnotations::hasInjectAnnotation) + .collect(toImmutableSet()); + } + /** Returns the constructors in {@code type} that are annotated with {@link Inject}. */ public static ImmutableSet<ExecutableElement> injectedConstructors(TypeElement type) { return FluentIterable.from(constructorsIn(type.getEnclosedElements())) - .filter(constructor -> isAnnotationPresent(constructor, Inject.class)) + .filter(InjectionAnnotations::hasInjectAnnotation) .toSet(); } + private static boolean hasQualifierAnnotation(AnnotationMirror annotation) { + return isAnyAnnotationPresent( + annotation.getAnnotationType().asElement(), TypeNames.QUALIFIER, TypeNames.QUALIFIER_JAVAX); + } + + private static boolean hasScopeAnnotation(XAnnotation annotation) { + return annotation + .getType() + .getTypeElement() + .hasAnyAnnotation(TypeNames.SCOPE, TypeNames.SCOPE_JAVAX); + } + + /** Returns true if the given element is annotated with {@link Inject}. */ + public static boolean hasInjectAnnotation(XElement element) { + return element.hasAnyAnnotation(TypeNames.INJECT, TypeNames.INJECT_JAVAX); + } + + /** Returns true if the given element is annotated with {@link Inject}. */ + public static boolean hasInjectAnnotation(Element element) { + return isAnyAnnotationPresent(element, TypeNames.INJECT, TypeNames.INJECT_JAVAX); + } + + /** Returns true if the given element is annotated with {@link Inject}. */ + public static boolean hasInjectOrAssistedInjectAnnotation(XElement element) { + return element.hasAnyAnnotation( + TypeNames.INJECT, TypeNames.INJECT_JAVAX, TypeNames.ASSISTED_INJECT); + } + + /** Returns true if the given element is annotated with {@link Inject}. */ + public static boolean hasInjectOrAssistedInjectAnnotation(Element element) { + return isAnyAnnotationPresent( + element, TypeNames.INJECT, TypeNames.INJECT_JAVAX, TypeNames.ASSISTED_INJECT); + } + /** * Gets the qualifiers annotation of a Kotlin Property. Finding these annotations involve finding * the synthetic method for annotations as described by the Kotlin metadata or finding the @@ -131,7 +439,7 @@ public final class InjectionAnnotations { return ElementFilter.methodsIn(membersInjector.getEnclosedElements()).stream() .filter( method -> - getAnnotationMirror(method, InjectedFieldSignature.class) + getAnnotationMirror(method, TypeNames.INJECTED_FIELD_SIGNATURE) .map(annotation -> getStringValue(annotation, "value")) .map(memberInjectedFieldSignature::equals) // If a method is not an @InjectedFieldSignature method then filter it out @@ -152,7 +460,13 @@ public final class InjectionAnnotations { "No MembersInjector found for " + fieldElement.getEnclosingElement()); } } else { - return kotlinMetadataUtil.getSyntheticPropertyAnnotations(fieldElement, Qualifier.class); + return ImmutableSet.<AnnotationMirror>builder() + .addAll( + kotlinMetadataUtil.getSyntheticPropertyAnnotations(fieldElement, TypeNames.QUALIFIER)) + .addAll( + kotlinMetadataUtil.getSyntheticPropertyAnnotations( + fieldElement, TypeNames.QUALIFIER_JAVAX)) + .build(); } } } diff --git a/java/dagger/internal/codegen/binding/InjectionSiteFactory.java b/java/dagger/internal/codegen/binding/InjectionSiteFactory.java index 7a6f8d2f5..e620c058a 100644 --- a/java/dagger/internal/codegen/binding/InjectionSiteFactory.java +++ b/java/dagger/internal/codegen/binding/InjectionSiteFactory.java @@ -16,11 +16,15 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static com.google.auto.common.MoreTypes.asDeclared; +import static com.google.common.base.Preconditions.checkArgument; import static dagger.internal.codegen.langmodel.DaggerElements.DECLARATION_ORDER; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.STATIC; +import androidx.room.compiler.processing.XType; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableSortedSet; @@ -63,6 +67,12 @@ final class InjectionSiteFactory { } /** Returns the injection sites for a type. */ + ImmutableSortedSet<InjectionSite> getInjectionSites(XType type) { + checkArgument(isDeclared(type)); + return getInjectionSites(asDeclared(toJavac(type))); + } + + /** Returns the injection sites for a type. */ ImmutableSortedSet<InjectionSite> getInjectionSites(DeclaredType declaredType) { Set<InjectionSite> injectionSites = new HashSet<>(); List<TypeElement> ancestors = new ArrayList<>(); @@ -138,7 +148,7 @@ final class InjectionSiteFactory { } private boolean shouldBeInjected(Element injectionSite) { - return isAnnotationPresent(injectionSite, Inject.class) + return InjectionAnnotations.hasInjectAnnotation(injectionSite) && !injectionSite.getModifiers().contains(PRIVATE) && !injectionSite.getModifiers().contains(STATIC); } diff --git a/java/dagger/internal/codegen/binding/KeyFactory.java b/java/dagger/internal/codegen/binding/KeyFactory.java index d9236665d..daae7d73b 100644 --- a/java/dagger/internal/codegen/binding/KeyFactory.java +++ b/java/dagger/internal/codegen/binding/KeyFactory.java @@ -16,24 +16,35 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.MoreElements.isAnnotationPresent; -import static com.google.auto.common.MoreTypes.asExecutable; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; import static com.google.auto.common.MoreTypes.isType; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.internal.codegen.base.ProducerAnnotations.productionImplementationQualifier; +import static dagger.internal.codegen.base.ProducerAnnotations.productionQualifier; import static dagger.internal.codegen.base.RequestKinds.extractKeyType; import static dagger.internal.codegen.binding.MapKeys.getMapKey; import static dagger.internal.codegen.binding.MapKeys.mapKeyType; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.extension.Optionals.firstPresent; +import static dagger.internal.codegen.langmodel.DaggerElements.isAnnotationPresent; import static dagger.internal.codegen.langmodel.DaggerTypes.isFutureType; import static dagger.internal.codegen.langmodel.DaggerTypes.unwrapType; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import static java.util.Arrays.asList; import static javax.lang.model.element.ElementKind.METHOD; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.Binds; import dagger.BindsOptionalOf; import dagger.internal.codegen.base.ContributionType; @@ -42,25 +53,19 @@ import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.OptionalType; import dagger.internal.codegen.base.RequestKinds; import dagger.internal.codegen.base.SetType; -import dagger.internal.codegen.base.SimpleAnnotationMirror; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Key; -import dagger.model.Key.MultibindingContributionIdentifier; -import dagger.model.RequestKind; import dagger.multibindings.Multibinds; -import dagger.producers.Produced; -import dagger.producers.Producer; -import dagger.producers.Production; -import dagger.producers.internal.ProductionImplementation; -import dagger.producers.monitoring.ProductionComponentMonitor; +import dagger.spi.model.DaggerAnnotation; +import dagger.spi.model.DaggerType; +import dagger.spi.model.Key; +import dagger.spi.model.Key.MultibindingContributionIdentifier; +import dagger.spi.model.RequestKind; import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.Executor; import java.util.stream.Stream; import javax.inject.Inject; -import javax.inject.Provider; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; @@ -71,15 +76,20 @@ import javax.lang.model.type.TypeMirror; /** A factory for {@link Key}s. */ public final class KeyFactory { + private final XProcessingEnv processingEnv; private final DaggerTypes types; private final DaggerElements elements; private final InjectionAnnotations injectionAnnotations; @Inject KeyFactory( - DaggerTypes types, DaggerElements elements, InjectionAnnotations injectionAnnotations) { - this.types = checkNotNull(types); - this.elements = checkNotNull(elements); + XProcessingEnv processingEnv, + DaggerTypes types, + DaggerElements elements, + InjectionAnnotations injectionAnnotations) { + this.processingEnv = processingEnv; + this.types = types; + this.elements = elements; this.injectionAnnotations = injectionAnnotations; } @@ -88,95 +98,124 @@ public final class KeyFactory { } private DeclaredType setOf(TypeMirror elementType) { - return types.getDeclaredType(elements.getTypeElement(Set.class), boxPrimitives(elementType)); + return types.getDeclaredType( + elements.getTypeElement(TypeNames.SET), boxPrimitives(elementType)); + } + + private DeclaredType mapOf(XType keyType, XType valueType) { + return mapOf(toJavac(keyType), toJavac(valueType)); } private DeclaredType mapOf(TypeMirror keyType, TypeMirror valueType) { return types.getDeclaredType( - elements.getTypeElement(Map.class), boxPrimitives(keyType), boxPrimitives(valueType)); + elements.getTypeElement(TypeNames.MAP), boxPrimitives(keyType), boxPrimitives(valueType)); } /** Returns {@code Map<KeyType, FrameworkType<ValueType>>}. */ private TypeMirror mapOfFrameworkType( - TypeMirror keyType, TypeElement frameworkType, TypeMirror valueType) { - return mapOf(keyType, types.getDeclaredType(frameworkType, boxPrimitives(valueType))); + XType keyType, ClassName frameworkClassName, XType valueType) { + return mapOfFrameworkType(toJavac(keyType), frameworkClassName, toJavac(valueType)); } - Key forComponentMethod(ExecutableElement componentMethod) { - checkArgument(componentMethod.getKind().equals(METHOD)); + /** Returns {@code Map<KeyType, FrameworkType<ValueType>>}. */ + private TypeMirror mapOfFrameworkType( + TypeMirror keyType, ClassName frameworkClassName, TypeMirror valueType) { + return mapOf( + keyType, + types.getDeclaredType( + elements.getTypeElement(frameworkClassName), boxPrimitives(valueType))); + } + + Key forComponentMethod(XMethodElement componentMethod) { return forMethod(componentMethod, componentMethod.getReturnType()); } - Key forProductionComponentMethod(ExecutableElement componentMethod) { - checkArgument(componentMethod.getKind().equals(METHOD)); - TypeMirror returnType = componentMethod.getReturnType(); - TypeMirror keyType = - isFutureType(returnType) - ? getOnlyElement(MoreTypes.asDeclared(returnType).getTypeArguments()) - : returnType; + Key forProductionComponentMethod(XMethodElement componentMethod) { + XType returnType = componentMethod.getReturnType(); + XType keyType = + isFutureType(returnType) ? getOnlyElement(returnType.getTypeArguments()) : returnType; return forMethod(componentMethod, keyType); } Key forSubcomponentCreatorMethod( - ExecutableElement subcomponentCreatorMethod, DeclaredType declaredContainer) { - checkArgument(subcomponentCreatorMethod.getKind().equals(METHOD)); - ExecutableType resolvedMethod = - asExecutable(types.asMemberOf(declaredContainer, subcomponentCreatorMethod)); - return Key.builder(resolvedMethod.getReturnType()).build(); + XMethodElement subcomponentCreatorMethod, XType declaredContainer) { + checkArgument(isDeclared(declaredContainer)); + XMethodType resolvedMethod = subcomponentCreatorMethod.asMemberOf(declaredContainer); + return Key.builder(DaggerType.from(resolvedMethod.getReturnType())).build(); + } + + public Key forSubcomponentCreator(XType creatorType) { + return Key.builder(DaggerType.from(creatorType)).build(); } - public Key forSubcomponentCreator(TypeMirror creatorType) { - return Key.builder(creatorType).build(); + public Key forProvidesMethod(XMethodElement method, XTypeElement contributingModule) { + return forProvidesMethod(toJavac(method), toJavac(contributingModule)); } public Key forProvidesMethod(ExecutableElement method, TypeElement contributingModule) { - return forBindingMethod( - method, contributingModule, Optional.of(elements.getTypeElement(Provider.class))); + return forBindingMethod(method, contributingModule, Optional.of(TypeNames.PROVIDER)); + } + + public Key forProducesMethod(XMethodElement method, XTypeElement contributingModule) { + return forProducesMethod(toJavac(method), toJavac(contributingModule)); } public Key forProducesMethod(ExecutableElement method, TypeElement contributingModule) { - return forBindingMethod( - method, contributingModule, Optional.of(elements.getTypeElement(Producer.class))); + return forBindingMethod(method, contributingModule, Optional.of(TypeNames.PRODUCER)); + } + + /** Returns the key bound by a {@link Binds} method. */ + Key forBindsMethod(XMethodElement method, XTypeElement contributingModule) { + return forBindsMethod(toJavac(method), toJavac(contributingModule)); } /** Returns the key bound by a {@link Binds} method. */ Key forBindsMethod(ExecutableElement method, TypeElement contributingModule) { - checkArgument(isAnnotationPresent(method, Binds.class)); + checkArgument(isAnnotationPresent(method, TypeNames.BINDS)); return forBindingMethod(method, contributingModule, Optional.empty()); } /** Returns the base key bound by a {@link BindsOptionalOf} method. */ - Key forBindsOptionalOfMethod(ExecutableElement method, TypeElement contributingModule) { - checkArgument(isAnnotationPresent(method, BindsOptionalOf.class)); + Key forBindsOptionalOfMethod(XMethodElement method, XTypeElement contributingModule) { + checkArgument(method.hasAnnotation(TypeNames.BINDS_OPTIONAL_OF)); return forBindingMethod(method, contributingModule, Optional.empty()); } private Key forBindingMethod( + XMethodElement method, + XTypeElement contributingModule, + Optional<ClassName> frameworkClassName) { + return forBindingMethod(toJavac(method), toJavac(contributingModule), frameworkClassName); + } + + private Key forBindingMethod( ExecutableElement method, TypeElement contributingModule, - Optional<TypeElement> frameworkType) { + Optional<ClassName> frameworkClassName) { checkArgument(method.getKind().equals(METHOD)); ExecutableType methodType = MoreTypes.asExecutable( types.asMemberOf(MoreTypes.asDeclared(contributingModule.asType()), method)); ContributionType contributionType = ContributionType.fromBindingElement(method); TypeMirror returnType = methodType.getReturnType(); - if (frameworkType.isPresent() - && frameworkType.get().equals(elements.getTypeElement(Producer.class)) + if (frameworkClassName.isPresent() + && frameworkClassName.get().equals(TypeNames.PRODUCER) && isType(returnType)) { if (isFutureType(methodType.getReturnType())) { returnType = getOnlyElement(MoreTypes.asDeclared(returnType).getTypeArguments()); } else if (contributionType.equals(ContributionType.SET_VALUES) && SetType.isSet(returnType)) { - SetType setType = SetType.from(returnType); + SetType setType = SetType.from(toXProcessing(returnType, processingEnv)); if (isFutureType(setType.elementType())) { returnType = types.getDeclaredType( - elements.getTypeElement(Set.class), unwrapType(setType.elementType())); + elements.getTypeElement(TypeNames.SET), + toJavac(unwrapType(setType.elementType()))); } } } - TypeMirror keyType = bindingMethodKeyType(returnType, method, contributionType, frameworkType); + TypeMirror keyType = + bindingMethodKeyType(returnType, method, contributionType, frameworkClassName); Key key = forMethod(method, keyType); return contributionType.equals(ContributionType.UNIQUE) ? key @@ -192,33 +231,48 @@ public final class KeyFactory { * <p>The key's type is either {@code Set<T>} or {@code Map<K, Provider<V>>}. The latter works * even for maps used by {@code Producer}s. */ - Key forMultibindsMethod(ExecutableType executableType, ExecutableElement method) { - checkArgument(method.getKind().equals(METHOD), "%s must be a method", method); - TypeMirror returnType = executableType.getReturnType(); + Key forMultibindsMethod(XMethodElement method, XMethodType methodType) { + XType returnType = method.getReturnType(); TypeMirror keyType = MapType.isMap(returnType) ? mapOfFrameworkType( MapType.from(returnType).keyType(), - elements.getTypeElement(Provider.class), + TypeNames.PROVIDER, MapType.from(returnType).valueType()) - : returnType; - return forMethod(method, keyType); + : toJavac(returnType); + return forMethod(toJavac(method), keyType); } private TypeMirror bindingMethodKeyType( TypeMirror returnType, ExecutableElement method, ContributionType contributionType, - Optional<TypeElement> frameworkType) { + Optional<ClassName> frameworkClassName) { switch (contributionType) { case UNIQUE: return returnType; case SET: return setOf(returnType); case MAP: - TypeMirror mapKeyType = mapKeyType(getMapKey(method).get(), types); - return frameworkType.isPresent() - ? mapOfFrameworkType(mapKeyType, frameworkType.get(), returnType) + Optional<AnnotationMirror> mapKey = getMapKey(method); + // TODO(bcorso): We've added a special checkState here since a number of people have run + // into this particular case, but technically it shouldn't be necessary if we are properly + // doing superficial validation and deferring on unresolvable types. We should revisit + // whether this is necessary once we're able to properly defer this case. + checkState( + mapKey.isPresent(), + "Missing map key annotation for method: %s#%s. That method was annotated with: %s. If a" + + " map key annotation is included in that list, it means Dagger wasn't able to" + + " detect that it was a map key because the dependency is missing from the" + + " classpath of the current build. To fix, add a dependency for the map key to the" + + " current build. For more details, see" + + " https://github.com/google/dagger/issues/3133#issuecomment-1002790894.", + method.getEnclosingElement(), + method, + method.getAnnotationMirrors()); + TypeMirror mapKeyType = mapKeyType(toXProcessing(mapKey.get(), processingEnv)); + return frameworkClassName.isPresent() + ? mapOfFrameworkType(mapKeyType, frameworkClassName.get(), returnType) : mapOf(mapKeyType, returnType); case SET_VALUES: // TODO(gak): do we want to allow people to use "covariant return" here? @@ -235,47 +289,69 @@ public final class KeyFactory { * from {@link DelegateDeclaration#key()} to {@code Map<K, FrameworkType<V>>}. If {@code * delegateDeclaration} is not a map contribution, its key is returned. */ - Key forDelegateBinding(DelegateDeclaration delegateDeclaration, Class<?> frameworkType) { + Key forDelegateBinding(DelegateDeclaration delegateDeclaration, ClassName frameworkType) { return delegateDeclaration.contributionType().equals(ContributionType.MAP) ? wrapMapValue(delegateDeclaration.key(), frameworkType) : delegateDeclaration.key(); } + private Key forMethod(XMethodElement method, XType keyType) { + return forMethod(toJavac(method), toJavac(keyType)); + } + private Key forMethod(ExecutableElement method, TypeMirror keyType) { return forQualifiedType(injectionAnnotations.getQualifier(method), keyType); } + public Key forInjectConstructorWithResolvedType(XType type) { + return forInjectConstructorWithResolvedType(toJavac(type)); + } + public Key forInjectConstructorWithResolvedType(TypeMirror type) { - return Key.builder(type).build(); + return Key.builder(fromJava(type)).build(); } // TODO(ronshapiro): Remove these conveniences which are simple wrappers around Key.Builder - Key forType(TypeMirror type) { - return Key.builder(type).build(); + Key forType(XType type) { + return Key.builder(DaggerType.from(type)).build(); } public Key forMembersInjectedType(TypeMirror type) { - return Key.builder(type).build(); + return forMembersInjectedType(toXProcessing(type, processingEnv)); + } + + public Key forMembersInjectedType(XType type) { + return Key.builder(DaggerType.from(type)).build(); } Key forQualifiedType(Optional<AnnotationMirror> qualifier, TypeMirror type) { - return Key.builder(boxPrimitives(type)).qualifier(qualifier).build(); + return forQualifiedType( + qualifier.map(annotation -> toXProcessing(annotation, processingEnv)), + toXProcessing(type, processingEnv)); + } + + Key forQualifiedType(Optional<XAnnotation> qualifier, XType type) { + return Key.builder(DaggerType.from(type.boxed())) + .qualifier(qualifier.map(DaggerAnnotation::from)) + .build(); } public Key forProductionExecutor() { - return Key.builder(elements.getTypeElement(Executor.class).asType()) - .qualifier(SimpleAnnotationMirror.of(elements.getTypeElement(Production.class))) + return Key.builder(fromJava(elements.getTypeElement(TypeNames.EXECUTOR).asType())) + .qualifier(fromJava(toJavac(productionQualifier(processingEnv)))) .build(); } public Key forProductionImplementationExecutor() { - return Key.builder(elements.getTypeElement(Executor.class).asType()) - .qualifier(SimpleAnnotationMirror.of(elements.getTypeElement(ProductionImplementation.class))) + return Key.builder(fromJava(elements.getTypeElement(TypeNames.EXECUTOR).asType())) + .qualifier(fromJava(toJavac(productionImplementationQualifier(processingEnv)))) .build(); } public Key forProductionComponentMonitor() { - return Key.builder(elements.getTypeElement(ProductionComponentMonitor.class).asType()).build(); + return Key.builder( + fromJava(elements.getTypeElement(TypeNames.PRODUCTION_COMPONENT_MONITOR).asType())) + .build(); } /** @@ -298,8 +374,8 @@ public final class KeyFactory { */ Optional<Key> implicitMapProviderKeyFrom(Key possibleMapKey) { return firstPresent( - rewrapMapKey(possibleMapKey, Produced.class, Provider.class), - wrapMapKey(possibleMapKey, Provider.class)); + rewrapMapKey(possibleMapKey, TypeNames.PRODUCED, TypeNames.PROVIDER), + wrapMapKey(possibleMapKey, TypeNames.PROVIDER)); } /** @@ -310,8 +386,8 @@ public final class KeyFactory { */ Optional<Key> implicitMapProducerKeyFrom(Key possibleMapKey) { return firstPresent( - rewrapMapKey(possibleMapKey, Produced.class, Producer.class), - wrapMapKey(possibleMapKey, Producer.class)); + rewrapMapKey(possibleMapKey, TypeNames.PRODUCED, TypeNames.PRODUCER), + wrapMapKey(possibleMapKey, TypeNames.PRODUCER)); } /** @@ -325,10 +401,12 @@ public final class KeyFactory { if (MapType.isMap(key)) { MapType mapType = MapType.from(key); if (!mapType.isRawType()) { - for (Class<?> frameworkClass : asList(Provider.class, Producer.class, Produced.class)) { + for (ClassName frameworkClass : + asList(TypeNames.PROVIDER, TypeNames.PRODUCER, TypeNames.PRODUCED)) { if (mapType.valuesAreTypeOf(frameworkClass)) { return key.toBuilder() - .type(mapOf(mapType.keyType(), mapType.unwrappedValueType(frameworkClass))) + .type( + fromJava(mapOf(mapType.keyType(), mapType.unwrappedValueType(frameworkClass)))) .build(); } } @@ -337,13 +415,11 @@ public final class KeyFactory { return key; } - /** - * Converts a {@link Key} of type {@code Map<K, V>} to {@code Map<K, Provider<V>>}. - */ - private Key wrapMapValue(Key key, Class<?> newWrappingClass) { + /** Converts a {@link Key} of type {@code Map<K, V>} to {@code Map<K, Provider<V>>}. */ + private Key wrapMapValue(Key key, ClassName newWrappingClassName) { checkArgument( - FrameworkTypes.isFrameworkType(elements.getTypeElement(newWrappingClass).asType())); - return wrapMapKey(key, newWrappingClass).get(); + FrameworkTypes.isFrameworkType(elements.getTypeElement(newWrappingClassName).asType())); + return wrapMapKey(key, newWrappingClassName).get(); } /** @@ -357,12 +433,12 @@ public final class KeyFactory { * currentWrappingClass} */ public Optional<Key> rewrapMapKey( - Key possibleMapKey, Class<?> currentWrappingClass, Class<?> newWrappingClass) { - checkArgument(!currentWrappingClass.equals(newWrappingClass)); + Key possibleMapKey, ClassName currentWrappingClassName, ClassName newWrappingClassName) { + checkArgument(!currentWrappingClassName.equals(newWrappingClassName)); if (MapType.isMap(possibleMapKey)) { MapType mapType = MapType.from(possibleMapKey); - if (!mapType.isRawType() && mapType.valuesAreTypeOf(currentWrappingClass)) { - TypeElement wrappingElement = elements.getTypeElement(newWrappingClass); + if (!mapType.isRawType() && mapType.valuesAreTypeOf(currentWrappingClassName)) { + TypeElement wrappingElement = elements.getTypeElement(newWrappingClassName); if (wrappingElement == null) { // This target might not be compiled with Producers, so wrappingClass might not have an // associated element. @@ -370,9 +446,11 @@ public final class KeyFactory { } DeclaredType wrappedValueType = types.getDeclaredType( - wrappingElement, mapType.unwrappedValueType(currentWrappingClass)); + wrappingElement, toJavac(mapType.unwrappedValueType(currentWrappingClassName))); return Optional.of( - possibleMapKey.toBuilder().type(mapOf(mapType.keyType(), wrappedValueType)).build()); + possibleMapKey.toBuilder() + .type(fromJava(mapOf(toJavac(mapType.keyType()), wrappedValueType))) + .build()); } } return Optional.empty(); @@ -385,19 +463,22 @@ public final class KeyFactory { * * <p>Returns {@link Optional#empty()} if {@code WrappingClass} is not in the classpath. */ - private Optional<Key> wrapMapKey(Key possibleMapKey, Class<?> wrappingClass) { + private Optional<Key> wrapMapKey(Key possibleMapKey, ClassName wrappingClassName) { if (MapType.isMap(possibleMapKey)) { MapType mapType = MapType.from(possibleMapKey); - if (!mapType.isRawType() && !mapType.valuesAreTypeOf(wrappingClass)) { - TypeElement wrappingElement = elements.getTypeElement(wrappingClass); + if (!mapType.isRawType() && !mapType.valuesAreTypeOf(wrappingClassName)) { + TypeElement wrappingElement = elements.getTypeElement(wrappingClassName); if (wrappingElement == null) { // This target might not be compiled with Producers, so wrappingClass might not have an // associated element. return Optional.empty(); } - DeclaredType wrappedValueType = types.getDeclaredType(wrappingElement, mapType.valueType()); + DeclaredType wrappedValueType = + types.getDeclaredType(wrappingElement, toJavac(mapType.valueType())); return Optional.of( - possibleMapKey.toBuilder().type(mapOf(mapType.keyType(), wrappedValueType)).build()); + possibleMapKey.toBuilder() + .type(fromJava(mapOf(toJavac(mapType.keyType()), wrappedValueType))) + .build()); } } return Optional.empty(); @@ -407,12 +488,14 @@ public final class KeyFactory { * If {@code key}'s type is {@code Set<WrappingClass<Bar>>}, returns a key with type {@code Set * <Bar>} with the same qualifier. Otherwise returns {@link Optional#empty()}. */ - Optional<Key> unwrapSetKey(Key key, Class<?> wrappingClass) { + Optional<Key> unwrapSetKey(Key key, ClassName wrappingClassName) { if (SetType.isSet(key)) { SetType setType = SetType.from(key); - if (!setType.isRawType() && setType.elementsAreTypeOf(wrappingClass)) { + if (!setType.isRawType() && setType.elementsAreTypeOf(wrappingClassName)) { return Optional.of( - key.toBuilder().type(setOf(setType.unwrappedElementType(wrappingClass))).build()); + key.toBuilder() + .type(fromJava(setOf(toJavac(setType.unwrappedElementType(wrappingClassName))))) + .build()); } } return Optional.empty(); @@ -428,7 +511,16 @@ public final class KeyFactory { return Optional.empty(); } - TypeMirror optionalValueType = OptionalType.from(key).valueType(); - return Optional.of(key.toBuilder().type(extractKeyType(optionalValueType)).build()); + XType optionalValueType = OptionalType.from(key).valueType(); + return Optional.of( + key.toBuilder().type(DaggerType.from(extractKeyType(optionalValueType))).build()); + } + + private DaggerAnnotation fromJava(AnnotationMirror annotation) { + return DaggerAnnotation.from(toXProcessing(annotation, processingEnv)); + } + + private DaggerType fromJava(TypeMirror typeMirror) { + return DaggerType.from(toXProcessing(typeMirror, processingEnv)); } } diff --git a/java/dagger/internal/codegen/binding/KeyVariableNamer.java b/java/dagger/internal/codegen/binding/KeyVariableNamer.java index 9ac0efa83..fb42410f5 100644 --- a/java/dagger/internal/codegen/binding/KeyVariableNamer.java +++ b/java/dagger/internal/codegen/binding/KeyVariableNamer.java @@ -16,22 +16,21 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.XTypeKt.isArray; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static dagger.internal.codegen.binding.SourceFiles.protectAgainstKeywords; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XArrayType; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableSet; -import dagger.model.DependencyRequest; -import dagger.model.Key; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; import java.util.Iterator; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVisitor; -import javax.lang.model.util.SimpleTypeVisitor8; /** * Suggests a variable name for a type based on a {@link Key}. Prefer {@link @@ -47,45 +46,6 @@ public final class KeyVariableNamer { "Subcomponent", "Injector"); - private static final TypeVisitor<Void, StringBuilder> TYPE_NAMER = - new SimpleTypeVisitor8<Void, StringBuilder>() { - @Override - public Void visitDeclared(DeclaredType declaredType, StringBuilder builder) { - TypeElement element = MoreTypes.asTypeElement(declaredType); - if (element.getNestingKind().isNested() - && VERY_SIMPLE_NAMES.contains(element.getSimpleName().toString())) { - builder.append(element.getEnclosingElement().getSimpleName()); - } - - builder.append(element.getSimpleName()); - Iterator<? extends TypeMirror> argumentIterator = - declaredType.getTypeArguments().iterator(); - if (argumentIterator.hasNext()) { - builder.append("Of"); - TypeMirror first = argumentIterator.next(); - first.accept(this, builder); - while (argumentIterator.hasNext()) { - builder.append("And"); - argumentIterator.next().accept(this, builder); - } - } - return null; - } - - @Override - public Void visitPrimitive(PrimitiveType type, StringBuilder builder) { - builder.append(LOWER_CAMEL.to(UPPER_CAMEL, type.toString())); - return null; - } - - @Override - public Void visitArray(ArrayType type, StringBuilder builder) { - type.getComponentType().accept(this, builder); - builder.append("Array"); - return null; - } - }; - private KeyVariableNamer() {} public static String name(Key key) { @@ -97,11 +57,36 @@ public final class KeyVariableNamer { if (key.qualifier().isPresent()) { // TODO(gak): Use a better name for fields with qualifiers with members. - builder.append(key.qualifier().get().getAnnotationType().asElement().getSimpleName()); + builder.append(key.qualifier().get().java().getAnnotationType().asElement().getSimpleName()); } - key.type().accept(TYPE_NAMER, builder); - + typeNamer(key.type().xprocessing(), builder); return protectAgainstKeywords(UPPER_CAMEL.to(LOWER_CAMEL, builder.toString())); } + + private static void typeNamer(XType type, StringBuilder builder) { + if (isDeclared(type)) { + XTypeElement element = type.getTypeElement(); + if (element.isNested() && VERY_SIMPLE_NAMES.contains(getSimpleName(element))) { + builder.append(getSimpleName(element.getEnclosingTypeElement())); + } + + builder.append(getSimpleName(element)); + Iterator<? extends XType> argumentIterator = type.getTypeArguments().iterator(); + if (argumentIterator.hasNext()) { + builder.append("Of"); + XType first = argumentIterator.next(); + typeNamer(first, builder); + while (argumentIterator.hasNext()) { + builder.append("And"); + typeNamer(argumentIterator.next(), builder); + } + } + } else if (isPrimitive(type)) { + builder.append(LOWER_CAMEL.to(UPPER_CAMEL, type.toString())); + } else if (isArray(type)) { + typeNamer(((XArrayType) type).getComponentType(), builder); + builder.append("Array"); + } + } } diff --git a/java/dagger/internal/codegen/binding/LegacyBindingGraph.java b/java/dagger/internal/codegen/binding/LegacyBindingGraph.java index 7c0040167..2f8d39a46 100644 --- a/java/dagger/internal/codegen/binding/LegacyBindingGraph.java +++ b/java/dagger/internal/codegen/binding/LegacyBindingGraph.java @@ -16,16 +16,16 @@ package dagger.internal.codegen.binding; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Multimaps; -import dagger.model.Key; -import dagger.model.RequestKind; +import dagger.spi.model.Key; +import dagger.spi.model.RequestKind; import java.util.Collection; import java.util.Map; -import javax.lang.model.element.TypeElement; // TODO(bcorso): Remove the LegacyBindingGraph after we've migrated to the new BindingGraph. /** The canonical representation of a full-resolved graph. */ @@ -69,7 +69,7 @@ final class LegacyBindingGraph { private static ImmutableList<LegacyBindingGraph> checkForDuplicates( ImmutableList<LegacyBindingGraph> graphs) { - Map<TypeElement, Collection<LegacyBindingGraph>> duplicateGraphs = + Map<XTypeElement, Collection<LegacyBindingGraph>> duplicateGraphs = Maps.filterValues( Multimaps.index(graphs, graph -> graph.componentDescriptor().typeElement()).asMap(), overlapping -> overlapping.size() > 1); diff --git a/java/dagger/internal/codegen/binding/MapKeys.java b/java/dagger/internal/codegen/binding/MapKeys.java index ec7d79df1..7b2c4daf9 100644 --- a/java/dagger/internal/codegen/binding/MapKeys.java +++ b/java/dagger/internal/codegen/binding/MapKeys.java @@ -16,18 +16,29 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.XTypeKt.isArray; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations; import static com.google.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults; +import static com.google.auto.common.MoreElements.asExecutable; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.getOnlyElement; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static dagger.internal.codegen.base.MapKeyAccessibility.isMapKeyPubliclyAccessible; import static dagger.internal.codegen.binding.SourceFiles.elementBasedClassName; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; -import static javax.lang.model.util.ElementFilter.methodsIn; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; @@ -35,22 +46,18 @@ import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import dagger.MapKey; +import dagger.internal.codegen.base.DaggerSuperficialValidation; import dagger.internal.codegen.base.MapKeyAccessibility; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.xprocessing.XElements; import java.util.NoSuchElementException; import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleTypeVisitor6; /** Methods for extracting {@link MapKey} annotations and key code blocks from binding elements. */ public final class MapKeys { @@ -61,6 +68,16 @@ public final class MapKeys { * @throws IllegalArgumentException if the element is annotated with more than one {@code MapKey} * annotation */ + static Optional<AnnotationMirror> getMapKey(XElement bindingElement) { + return getMapKey(toJavac(bindingElement)); + } + + /** + * If {@code bindingElement} is annotated with a {@link MapKey} annotation, returns it. + * + * @throws IllegalArgumentException if the element is annotated with more than one {@code MapKey} + * annotation + */ static Optional<AnnotationMirror> getMapKey(Element bindingElement) { ImmutableSet<? extends AnnotationMirror> mapKeys = getMapKeys(bindingElement); return mapKeys.isEmpty() @@ -73,6 +90,11 @@ public final class MapKeys { return getAnnotatedAnnotations(bindingElement, MapKey.class); } + /** Returns all of the {@link MapKey} annotations that annotate {@code bindingElement}. */ + public static ImmutableSet<XAnnotation> getMapKeys(XElement bindingElement) { + return XElements.getAnnotatedAnnotations(bindingElement, TypeNames.MAP_KEY); + } + /** * Returns the annotation value if {@code mapKey}'s type is annotated with * {@link MapKey @MapKey(unwrapValue = true)}. @@ -89,10 +111,10 @@ public final class MapKeys { : Optional.empty(); } - static TypeMirror mapKeyType(AnnotationMirror mapKeyAnnotation, DaggerTypes types) { - return unwrapValue(mapKeyAnnotation).isPresent() - ? getUnwrappedMapKeyType(mapKeyAnnotation.getAnnotationType(), types) - : mapKeyAnnotation.getAnnotationType(); + static TypeMirror mapKeyType(XAnnotation mapKeyAnnotation) { + return unwrapValue(toJavac(mapKeyAnnotation)).isPresent() + ? toJavac(getUnwrappedMapKeyType(mapKeyAnnotation.getType())) + : toJavac(mapKeyAnnotation.getType()); } /** @@ -103,36 +125,24 @@ public final class MapKeys { * has more than one member, or if its single member is an array * @throws NoSuchElementException if the annotation has no members */ - public static DeclaredType getUnwrappedMapKeyType( - final DeclaredType mapKeyAnnotationType, final DaggerTypes types) { + public static XType getUnwrappedMapKeyType(XType mapKeyAnnotationType) { checkArgument( - MoreTypes.asTypeElement(mapKeyAnnotationType).getKind() == ElementKind.ANNOTATION_TYPE, + isDeclared(mapKeyAnnotationType) + && mapKeyAnnotationType.getTypeElement().isAnnotationClass(), "%s is not an annotation type", mapKeyAnnotationType); - final ExecutableElement onlyElement = - getOnlyElement(methodsIn(mapKeyAnnotationType.asElement().getEnclosedElements())); - - SimpleTypeVisitor6<DeclaredType, Void> keyTypeElementVisitor = - new SimpleTypeVisitor6<DeclaredType, Void>() { - - @Override - public DeclaredType visitArray(ArrayType t, Void p) { - throw new IllegalArgumentException( - mapKeyAnnotationType + "." + onlyElement.getSimpleName() + " cannot be an array"); - } - - @Override - public DeclaredType visitPrimitive(PrimitiveType t, Void p) { - return MoreTypes.asDeclared(types.boxedClass(t).asType()); - } - - @Override - public DeclaredType visitDeclared(DeclaredType t, Void p) { - return t; - } - }; - return keyTypeElementVisitor.visit(onlyElement.getReturnType()); + XMethodElement annotationValueMethod = + getOnlyElement(mapKeyAnnotationType.getTypeElement().getDeclaredMethods()); + XType annotationValueType = annotationValueMethod.getReturnType(); + if (isArray(annotationValueType)) { + throw new IllegalArgumentException( + mapKeyAnnotationType + + "." + + getSimpleName(annotationValueMethod) + + " cannot be an array"); + } + return isPrimitive(annotationValueType) ? annotationValueType.boxed() : annotationValueType; } /** @@ -145,11 +155,11 @@ public final class MapKeys { * map} contribution. */ public static CodeBlock getMapKeyExpression( - ContributionBinding binding, ClassName requestingClass, DaggerElements elements) { + ContributionBinding binding, ClassName requestingClass, XProcessingEnv processingEnv) { AnnotationMirror mapKeyAnnotation = binding.mapKeyAnnotation().get(); return MapKeyAccessibility.isMapKeyAccessibleFrom( mapKeyAnnotation, requestingClass.packageName()) - ? directMapKeyExpression(mapKeyAnnotation, elements) + ? directMapKeyExpression(mapKeyAnnotation, processingEnv) : CodeBlock.of("$T.create()", mapKeyProxyClassName(binding)); } @@ -166,19 +176,20 @@ public final class MapKeys { * annotation */ private static CodeBlock directMapKeyExpression( - AnnotationMirror mapKey, DaggerElements elements) { + AnnotationMirror mapKey, XProcessingEnv processingEnv) { Optional<? extends AnnotationValue> unwrappedValue = unwrapValue(mapKey); AnnotationExpression annotationExpression = new AnnotationExpression(mapKey); if (MoreTypes.asTypeElement(mapKey.getAnnotationType()) .getQualifiedName() .contentEquals("dagger.android.AndroidInjectionKey")) { - TypeElement unwrappedType = - elements.checkTypePresent((String) unwrappedValue.get().getValue()); + XTypeElement unwrappedType = + DaggerSuperficialValidation.requireTypeElement( + processingEnv, (String) unwrappedValue.get().getValue()); return CodeBlock.of( "$T.of($S)", ClassName.get("dagger.android.internal", "AndroidInjectionKeys"), - ClassName.get(unwrappedType).reflectionName()); + unwrappedType.getClassName().reflectionName()); } if (unwrappedValue.isPresent()) { @@ -195,8 +206,7 @@ public final class MapKeys { * DaggerTypes, DaggerElements)} is generated. */ public static ClassName mapKeyProxyClassName(ContributionBinding binding) { - return elementBasedClassName( - MoreElements.asExecutable(binding.bindingElement().get()), "MapKey"); + return elementBasedClassName(asExecutable(toJavac(binding.bindingElement().get())), "MapKey"); } /** @@ -205,7 +215,7 @@ public final class MapKeys { * accessible. */ public static Optional<MethodSpec> mapKeyFactoryMethod( - ContributionBinding binding, DaggerTypes types, DaggerElements elements) { + ContributionBinding binding, XProcessingEnv processingEnv) { return binding .mapKeyAnnotation() .filter(mapKey -> !isMapKeyPubliclyAccessible(mapKey)) @@ -213,8 +223,8 @@ public final class MapKeys { mapKey -> methodBuilder("create") .addModifiers(PUBLIC, STATIC) - .returns(TypeName.get(mapKeyType(mapKey, types))) - .addStatement("return $L", directMapKeyExpression(mapKey, elements)) + .returns(TypeName.get(mapKeyType(toXProcessing(mapKey, processingEnv)))) + .addStatement("return $L", directMapKeyExpression(mapKey, processingEnv)) .build()); } diff --git a/java/dagger/internal/codegen/binding/MembersInjectionBinding.java b/java/dagger/internal/codegen/binding/MembersInjectionBinding.java index 3dd101657..695aea6a7 100644 --- a/java/dagger/internal/codegen/binding/MembersInjectionBinding.java +++ b/java/dagger/internal/codegen/binding/MembersInjectionBinding.java @@ -16,38 +16,49 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static java.util.stream.Collectors.toList; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; -import dagger.model.BindingKind; -import dagger.model.DependencyRequest; +import dagger.spi.model.BindingKind; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; import java.util.Optional; -import javax.inject.Inject; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; /** Represents the full members injection of a particular type. */ @AutoValue public abstract class MembersInjectionBinding extends Binding { + static MembersInjectionBinding create( + Key key, + ImmutableSet<DependencyRequest> dependencies, + Optional<MembersInjectionBinding> unresolved, + ImmutableSortedSet<InjectionSite> injectionSites) { + return new AutoValue_MembersInjectionBinding(key, dependencies, unresolved, injectionSites); + } + @Override - public final Optional<Element> bindingElement() { + public final Optional<XElement> bindingElement() { return Optional.of(membersInjectedType()); } - public abstract TypeElement membersInjectedType(); + public final XTypeElement membersInjectedType() { + return key().type().xprocessing().getTypeElement(); + } @Override public abstract Optional<MembersInjectionBinding> unresolved(); @Override - public Optional<TypeElement> contributingModule() { + public Optional<XTypeElement> contributingModule() { return Optional.empty(); } @@ -73,11 +84,13 @@ public abstract class MembersInjectionBinding extends Binding { * Returns {@code true} if any of this binding's injection sites are directly on the bound type. */ public boolean hasLocalInjectionSites() { - return injectionSites() - .stream() + return injectionSites().stream() .anyMatch( injectionSite -> - injectionSite.element().getEnclosingElement().equals(membersInjectedType())); + injectionSite + .element() + .getEnclosingElement() + .equals(toJavac(membersInjectedType()))); } @Override @@ -119,7 +132,7 @@ public abstract class MembersInjectionBinding extends Binding { .getEnclosingElement() .getEnclosedElements() .stream() - .filter(element -> isAnnotationPresent(element, Inject.class)) + .filter(InjectionAnnotations::hasInjectAnnotation) .filter(element -> !element.getModifiers().contains(Modifier.PRIVATE)) .filter(element -> element.getSimpleName().equals(this.element().getSimpleName())) .collect(toList()) diff --git a/java/dagger/internal/codegen/binding/MethodSignature.java b/java/dagger/internal/codegen/binding/MethodSignature.java new file mode 100644 index 000000000..e982ca5a6 --- /dev/null +++ b/java/dagger/internal/codegen/binding/MethodSignature.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 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.binding; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; + +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XType; +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.TypeName; +import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; + +/** A class that defines proper {@code equals} and {@code hashcode} for a method signature. */ +@AutoValue +public abstract class MethodSignature { + + abstract String name(); + + abstract ImmutableList<TypeName> parameterTypes(); + + abstract ImmutableList<TypeName> thrownTypes(); + + public static MethodSignature forComponentMethod( + ComponentMethodDescriptor componentMethod, XType componentType) { + XMethodType methodType = componentMethod.methodElement().asMemberOf(componentType); + return new AutoValue_MethodSignature( + getSimpleName(componentMethod.methodElement()), + toJavac(methodType).getParameterTypes().stream() + .map(TypeName::get) + .collect(toImmutableList()), + toJavac(methodType).getThrownTypes().stream() + .map(TypeName::get) + .collect(toImmutableList())); + } +} diff --git a/java/dagger/internal/codegen/binding/MethodSignatureFormatter.java b/java/dagger/internal/codegen/binding/MethodSignatureFormatter.java index 97c680f4c..6a1a5a1af 100644 --- a/java/dagger/internal/codegen/binding/MethodSignatureFormatter.java +++ b/java/dagger/internal/codegen/binding/MethodSignatureFormatter.java @@ -16,54 +16,55 @@ package dagger.internal.codegen.binding; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static dagger.internal.codegen.base.DiagnosticFormatting.stripCommonTypePrefixes; +import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XExecutableType; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.XVariableElement; import dagger.internal.codegen.base.Formatter; -import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.xprocessing.XAnnotations; import java.util.Iterator; import java.util.List; import java.util.Optional; import javax.inject.Inject; -import javax.lang.model.element.AnnotationMirror; -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.ExecutableType; -import javax.lang.model.type.TypeMirror; -/** Formats the signature of an {@link ExecutableElement} suitable for use in error messages. */ -public final class MethodSignatureFormatter extends Formatter<ExecutableElement> { - private final DaggerTypes types; +/** Formats the signature of an {@link XExecutableElement} suitable for use in error messages. */ +public final class MethodSignatureFormatter extends Formatter<XExecutableElement> { private final InjectionAnnotations injectionAnnotations; @Inject - public MethodSignatureFormatter(DaggerTypes types, InjectionAnnotations injectionAnnotations) { - this.types = types; + MethodSignatureFormatter(InjectionAnnotations injectionAnnotations) { this.injectionAnnotations = injectionAnnotations; } /** * A formatter that uses the type where the method is declared for the annotations and name of the - * method, but the method's resolved type as a member of {@code declaredType} for the key. + * method, but the method's resolved type as a member of {@code type} for the key. */ - public Formatter<ExecutableElement> typedFormatter(DeclaredType declaredType) { - return new Formatter<ExecutableElement>() { + public Formatter<XMethodElement> typedFormatter(XType type) { + checkArgument(isDeclared(type)); + return new Formatter<XMethodElement>() { @Override - public String format(ExecutableElement method) { + public String format(XMethodElement method) { return MethodSignatureFormatter.this.format( - method, - MoreTypes.asExecutable(types.asMemberOf(declaredType, method)), - MoreElements.asType(method.getEnclosingElement())); + method, method.asMemberOf(type), closestEnclosingTypeElement(method)); } }; } @Override - public String format(ExecutableElement method) { + public String format(XExecutableElement method) { return format(method, Optional.empty()); } @@ -71,23 +72,18 @@ public final class MethodSignatureFormatter extends Formatter<ExecutableElement> * Formats an ExecutableElement as if it were contained within the container, if the container is * present. */ - public String format(ExecutableElement method, Optional<DeclaredType> container) { - TypeElement type = MoreElements.asType(method.getEnclosingElement()); - ExecutableType executableType = MoreTypes.asExecutable(method.asType()); - if (container.isPresent()) { - executableType = MoreTypes.asExecutable(types.asMemberOf(container.get(), method)); - type = MoreElements.asType(container.get().asElement()); - } - return format(method, executableType, type); + public String format(XExecutableElement method, Optional<XType> container) { + return container.isPresent() + ? format(method, method.asMemberOf(container.get()), container.get().getTypeElement()) + : format(method, method.getExecutableType(), closestEnclosingTypeElement(method)); } private String format( - ExecutableElement method, ExecutableType methodType, TypeElement declaringType) { + XExecutableElement method, XExecutableType methodType, XTypeElement container) { StringBuilder builder = new StringBuilder(); - // TODO(user): AnnotationMirror formatter. - List<? extends AnnotationMirror> annotations = method.getAnnotationMirrors(); + List<XAnnotation> annotations = method.getAllAnnotations(); if (!annotations.isEmpty()) { - Iterator<? extends AnnotationMirror> annotationIterator = annotations.iterator(); + Iterator<XAnnotation> annotationIterator = annotations.iterator(); for (int i = 0; annotationIterator.hasNext(); i++) { if (i > 0) { builder.append(' '); @@ -96,20 +92,20 @@ public final class MethodSignatureFormatter extends Formatter<ExecutableElement> } builder.append(' '); } - if (method.getSimpleName().contentEquals("<init>")) { - builder.append(declaringType.getQualifiedName()); + if (getSimpleName(method).contentEquals("<init>")) { + builder.append(container.getQualifiedName()); } else { builder - .append(nameOfType(methodType.getReturnType())) + .append(nameOfType(((XMethodType) methodType).getReturnType())) .append(' ') - .append(declaringType.getQualifiedName()) + .append(container.getQualifiedName()) .append('.') - .append(method.getSimpleName()); + .append(getSimpleName(method)); } builder.append('('); checkState(method.getParameters().size() == methodType.getParameterTypes().size()); - Iterator<? extends VariableElement> parameters = method.getParameters().iterator(); - Iterator<? extends TypeMirror> parameterTypes = methodType.getParameterTypes().iterator(); + Iterator<XExecutableParameterElement> parameters = method.getParameters().iterator(); + Iterator<XType> parameterTypes = methodType.getParameterTypes().iterator(); for (int i = 0; parameters.hasNext(); i++) { if (i > 0) { builder.append(", "); @@ -120,21 +116,19 @@ public final class MethodSignatureFormatter extends Formatter<ExecutableElement> return builder.toString(); } - private void appendParameter(StringBuilder builder, VariableElement parameter, TypeMirror type) { + private void appendParameter( + StringBuilder builder, XVariableElement parameter, XType parameterType) { injectionAnnotations .getQualifier(parameter) - .ifPresent( - qualifier -> { - builder.append(formatAnnotation(qualifier)).append(' '); - }); - builder.append(nameOfType(type)); + .ifPresent(qualifier -> builder.append(formatAnnotation(qualifier)).append(' ')); + builder.append(nameOfType(parameterType)); } - private static String nameOfType(TypeMirror type) { + private static String nameOfType(XType type) { return stripCommonTypePrefixes(type.toString()); } - private static String formatAnnotation(AnnotationMirror annotation) { - return stripCommonTypePrefixes(annotation.toString()); + private static String formatAnnotation(XAnnotation annotation) { + return stripCommonTypePrefixes(XAnnotations.toString(annotation)); } } diff --git a/java/dagger/internal/codegen/binding/ModuleDescriptor.java b/java/dagger/internal/codegen/binding/ModuleDescriptor.java index c471f94b6..c6271f735 100644 --- a/java/dagger/internal/codegen/binding/ModuleDescriptor.java +++ b/java/dagger/internal/codegen/binding/ModuleDescriptor.java @@ -16,59 +16,57 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; import static com.google.auto.common.MoreElements.asExecutable; -import static com.google.auto.common.MoreElements.getPackage; -import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Verify.verify; -import static com.google.common.collect.Iterables.transform; +import static com.google.common.collect.Collections2.transform; import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; import static dagger.internal.codegen.binding.SourceFiles.classFileName; +import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.langmodel.DaggerElements.getMethodDescriptor; -import static dagger.internal.codegen.langmodel.DaggerElements.isAnnotationPresent; -import static javax.lang.model.type.TypeKind.DECLARED; -import static javax.lang.model.type.TypeKind.NONE; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import static javax.lang.model.util.ElementFilter.methodsIn; -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XElementKt; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableSet; import com.google.common.graph.Traverser; -import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; import dagger.Binds; import dagger.BindsOptionalOf; import dagger.Module; -import dagger.Provides; import dagger.internal.codegen.base.ClearableCache; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; +import dagger.internal.codegen.base.DaggerSuperficialValidation; +import dagger.internal.codegen.base.ModuleKind; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.model.Key; -import dagger.multibindings.Multibinds; -import dagger.producers.Produces; +import dagger.internal.codegen.xprocessing.XElements; +import dagger.spi.model.Key; +import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; /** Contains metadata that describes a module. */ @AutoValue public abstract class ModuleDescriptor { - public abstract TypeElement moduleElement(); - - abstract ImmutableSet<TypeElement> includedModules(); + public abstract XTypeElement moduleElement(); public abstract ImmutableSet<ContributionBinding> bindings(); @@ -107,38 +105,41 @@ public abstract class ModuleDescriptor { /** A {@link ModuleDescriptor} factory. */ @Singleton public static final class Factory implements ClearableCache { + private final XProcessingEnv processingEnv; private final DaggerElements elements; - private final KotlinMetadataUtil metadataUtil; private final BindingFactory bindingFactory; private final MultibindingDeclaration.Factory multibindingDeclarationFactory; private final DelegateDeclaration.Factory bindingDelegateDeclarationFactory; private final SubcomponentDeclaration.Factory subcomponentDeclarationFactory; private final OptionalBindingDeclaration.Factory optionalBindingDeclarationFactory; - private final Map<TypeElement, ModuleDescriptor> cache = new HashMap<>(); + private final DaggerSuperficialValidation superficialValidation; + private final Map<XTypeElement, ModuleDescriptor> cache = new HashMap<>(); @Inject Factory( + XProcessingEnv processingEnv, DaggerElements elements, - KotlinMetadataUtil metadataUtil, BindingFactory bindingFactory, MultibindingDeclaration.Factory multibindingDeclarationFactory, DelegateDeclaration.Factory bindingDelegateDeclarationFactory, SubcomponentDeclaration.Factory subcomponentDeclarationFactory, - OptionalBindingDeclaration.Factory optionalBindingDeclarationFactory) { + OptionalBindingDeclaration.Factory optionalBindingDeclarationFactory, + DaggerSuperficialValidation superficialValidation) { + this.processingEnv = processingEnv; this.elements = elements; - this.metadataUtil = metadataUtil; this.bindingFactory = bindingFactory; this.multibindingDeclarationFactory = multibindingDeclarationFactory; this.bindingDelegateDeclarationFactory = bindingDelegateDeclarationFactory; this.subcomponentDeclarationFactory = subcomponentDeclarationFactory; this.optionalBindingDeclarationFactory = optionalBindingDeclarationFactory; + this.superficialValidation = superficialValidation; } - public ModuleDescriptor create(TypeElement moduleElement) { + public ModuleDescriptor create(XTypeElement moduleElement) { return reentrantComputeIfAbsent(cache, moduleElement, this::createUncached); } - public ModuleDescriptor createUncached(TypeElement moduleElement) { + public ModuleDescriptor createUncached(XTypeElement moduleElement) { ImmutableSet.Builder<ContributionBinding> bindings = ImmutableSet.builder(); ImmutableSet.Builder<DelegateDeclaration> delegates = ImmutableSet.builder(); ImmutableSet.Builder<MultibindingDeclaration> multibindingDeclarations = @@ -146,33 +147,40 @@ public abstract class ModuleDescriptor { ImmutableSet.Builder<OptionalBindingDeclaration> optionalDeclarations = ImmutableSet.builder(); - for (ExecutableElement moduleMethod : methodsIn(elements.getAllMembers(moduleElement))) { - if (isAnnotationPresent(moduleMethod, Provides.class)) { - bindings.add(bindingFactory.providesMethodBinding(moduleMethod, moduleElement)); - } - if (isAnnotationPresent(moduleMethod, Produces.class)) { - bindings.add(bindingFactory.producesMethodBinding(moduleMethod, moduleElement)); - } - if (isAnnotationPresent(moduleMethod, Binds.class)) { - delegates.add(bindingDelegateDeclarationFactory.create(moduleMethod, moduleElement)); - } - if (isAnnotationPresent(moduleMethod, Multibinds.class)) { - multibindingDeclarations.add( - multibindingDeclarationFactory.forMultibindsMethod(moduleMethod, moduleElement)); - } - if (isAnnotationPresent(moduleMethod, BindsOptionalOf.class)) { - optionalDeclarations.add( - optionalBindingDeclarationFactory.forMethod(moduleMethod, moduleElement)); - } - } + methodsIn(elements.getAllMembers(toJavac(moduleElement))).stream() + .map(method -> toXProcessing(method, processingEnv)) + .filter(XElementKt::isMethod) + .map(XElements::asMethod) + .forEach( + moduleMethod -> { + if (moduleMethod.hasAnnotation(TypeNames.PROVIDES)) { + bindings.add(bindingFactory.providesMethodBinding(moduleMethod, moduleElement)); + } + if (moduleMethod.hasAnnotation(TypeNames.PRODUCES)) { + bindings.add(bindingFactory.producesMethodBinding(moduleMethod, moduleElement)); + } + if (moduleMethod.hasAnnotation(TypeNames.BINDS)) { + delegates.add( + bindingDelegateDeclarationFactory.create(moduleMethod, moduleElement)); + } + if (moduleMethod.hasAnnotation(TypeNames.MULTIBINDS)) { + multibindingDeclarations.add( + multibindingDeclarationFactory.forMultibindsMethod( + moduleMethod, moduleElement)); + } + if (moduleMethod.hasAnnotation(TypeNames.BINDS_OPTIONAL_OF)) { + optionalDeclarations.add( + optionalBindingDeclarationFactory.forMethod(moduleMethod, moduleElement)); + } + }); - if (metadataUtil.hasEnclosedCompanionObject(moduleElement)) { - collectCompanionModuleBindings(moduleElement, bindings); - } + moduleElement.getEnclosedTypeElements().stream() + .filter(XTypeElement::isCompanionObject) + .collect(toOptional()) + .ifPresent(companionModule -> collectCompanionModuleBindings(companionModule, bindings)); return new AutoValue_ModuleDescriptor( moduleElement, - ImmutableSet.copyOf(collectIncludedModules(new LinkedHashSet<>(), moduleElement)), bindings.build(), multibindingDeclarations.build(), subcomponentDeclarationFactory.forModule(moduleElement), @@ -182,17 +190,21 @@ public abstract class ModuleDescriptor { } private void collectCompanionModuleBindings( - TypeElement moduleElement, ImmutableSet.Builder<ContributionBinding> bindings) { - checkArgument(metadataUtil.hasEnclosedCompanionObject(moduleElement)); - TypeElement companionModule = metadataUtil.getEnclosedCompanionObject(moduleElement); + XTypeElement companionModule, ImmutableSet.Builder<ContributionBinding> bindings) { ImmutableSet<String> bindingElementDescriptors = bindings.build().stream() - .map(binding -> getMethodDescriptor(asExecutable(binding.bindingElement().get()))) + .map( + binding -> + getMethodDescriptor(asExecutable(toJavac(binding.bindingElement().get())))) .collect(toImmutableSet()); - methodsIn(elements.getAllMembers(companionModule)).stream() + + methodsIn(elements.getAllMembers(toJavac(companionModule))).stream() + .map(method -> toXProcessing(method, processingEnv)) + .filter(XElementKt::isMethod) + .map(XElements::asMethod) // Binding methods in companion objects with @JvmStatic are mirrored in the enclosing // class, therefore we should ignore it or else it'll be a duplicate binding. - .filter(method -> !KotlinMetadataUtil.isJvmStaticPresent(method)) + .filter(method -> !method.hasAnnotation(TypeNames.JVM_STATIC)) // Fallback strategy for de-duping contributing bindings in the companion module with // @JvmStatic by comparing descriptors. Contributing bindings are the only valid bindings // a companion module can declare. See: https://youtrack.jetbrains.com/issue/KT-35104 @@ -200,35 +212,39 @@ public abstract class ModuleDescriptor { .filter(method -> !bindingElementDescriptors.contains(getMethodDescriptor(method))) .forEach( method -> { - if (isAnnotationPresent(method, Provides.class)) { + if (method.hasAnnotation(TypeNames.PROVIDES)) { bindings.add(bindingFactory.providesMethodBinding(method, companionModule)); } - if (isAnnotationPresent(method, Produces.class)) { + if (method.hasAnnotation(TypeNames.PRODUCES)) { bindings.add(bindingFactory.producesMethodBinding(method, companionModule)); } }); } /** Returns all the modules transitively included by given modules, including the arguments. */ - ImmutableSet<ModuleDescriptor> transitiveModules(Iterable<TypeElement> modules) { + ImmutableSet<ModuleDescriptor> transitiveModules(Collection<XTypeElement> modules) { + // Traverse as a graph to automatically handle modules with cyclic includes. return ImmutableSet.copyOf( Traverser.forGraph( - (ModuleDescriptor module) -> transform(module.includedModules(), this::create)) + (ModuleDescriptor module) -> transform(includedModules(module), this::create)) .depthFirstPreOrder(transform(modules, this::create))); } - @CanIgnoreReturnValue - private Set<TypeElement> collectIncludedModules( - Set<TypeElement> includedModules, TypeElement moduleElement) { - TypeMirror superclass = moduleElement.getSuperclass(); - if (!superclass.getKind().equals(NONE)) { - verify(superclass.getKind().equals(DECLARED)); - TypeElement superclassElement = MoreTypes.asTypeElement(superclass); - if (!superclassElement.getQualifiedName().contentEquals(Object.class.getCanonicalName())) { - collectIncludedModules(includedModules, superclassElement); + private ImmutableSet<XTypeElement> includedModules(ModuleDescriptor moduleDescriptor) { + return ImmutableSet.copyOf( + collectIncludedModules(new LinkedHashSet<>(), moduleDescriptor.moduleElement())); + } + + private Set<XTypeElement> collectIncludedModules( + Set<XTypeElement> includedModules, XTypeElement moduleElement) { + XType superclass = moduleElement.getSuperType(); + if (superclass != null) { + verify(isDeclared(superclass)); + if (!TypeName.OBJECT.equals(superclass.getTypeName())) { + collectIncludedModules(includedModules, superclass.getTypeElement()); } } - moduleAnnotation(moduleElement) + moduleAnnotation(moduleElement, superficialValidation) .ifPresent( moduleAnnotation -> { includedModules.addAll(moduleAnnotation.includes()); @@ -237,26 +253,31 @@ public abstract class ModuleDescriptor { return includedModules; } + private static final ClassName CONTRIBUTES_ANDROID_INJECTOR = + ClassName.get("dagger.android", "ContributesAndroidInjector"); + // @ContributesAndroidInjector generates a module that is implicitly included in the enclosing // module - private ImmutableSet<TypeElement> implicitlyIncludedModules(TypeElement moduleElement) { - TypeElement contributesAndroidInjector = - elements.getTypeElement("dagger.android.ContributesAndroidInjector"); - if (contributesAndroidInjector == null) { + private ImmutableSet<XTypeElement> implicitlyIncludedModules(XTypeElement module) { + if (processingEnv.findTypeElement(CONTRIBUTES_ANDROID_INJECTOR) == null) { return ImmutableSet.of(); } - return methodsIn(moduleElement.getEnclosedElements()).stream() - .filter(method -> isAnnotationPresent(method, contributesAndroidInjector.asType())) - .map(method -> elements.checkTypePresent(implicitlyIncludedModuleName(method))) + return module.getDeclaredMethods().stream() + .filter(method -> method.hasAnnotation(CONTRIBUTES_ANDROID_INJECTOR)) + .map( + method -> + DaggerSuperficialValidation.requireTypeElement( + processingEnv, implicitlyIncludedModuleName(module, method))) .collect(toImmutableSet()); } - private String implicitlyIncludedModuleName(ExecutableElement method) { - return getPackage(method).getQualifiedName() - + "." - + classFileName(ClassName.get(MoreElements.asType(method.getEnclosingElement()))) - + "_" - + LOWER_CAMEL.to(UPPER_CAMEL, method.getSimpleName().toString()); + private ClassName implicitlyIncludedModuleName(XTypeElement module, XMethodElement method) { + return ClassName.get( + module.getPackageName(), + String.format( + "%s_%s", + classFileName(module.getClassName()), + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(method)))); } @Override diff --git a/java/dagger/internal/codegen/binding/MultibindingDeclaration.java b/java/dagger/internal/codegen/binding/MultibindingDeclaration.java index f1d3f8d75..746feaf5c 100644 --- a/java/dagger/internal/codegen/binding/MultibindingDeclaration.java +++ b/java/dagger/internal/codegen/binding/MultibindingDeclaration.java @@ -16,28 +16,25 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.Preconditions.checkArgument; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import dagger.internal.codegen.base.ContributionType; import dagger.internal.codegen.base.ContributionType.HasContributionType; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.SetType; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Key; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.multibindings.Multibinds; +import dagger.spi.model.Key; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.TypeMirror; /** * A declaration that a multibinding with a certain key is available to be injected in a component @@ -71,43 +68,36 @@ public abstract class MultibindingDeclaration extends BindingDeclaration /** A factory for {@link MultibindingDeclaration}s. */ public static final class Factory { - private final DaggerTypes types; private final KeyFactory keyFactory; @Inject - Factory(DaggerTypes types, KeyFactory keyFactory) { - this.types = types; + Factory(KeyFactory keyFactory) { this.keyFactory = keyFactory; } /** A multibinding declaration for a {@link Multibinds @Multibinds} method. */ MultibindingDeclaration forMultibindsMethod( - ExecutableElement moduleMethod, TypeElement moduleElement) { - checkArgument(isAnnotationPresent(moduleMethod, Multibinds.class)); + XMethodElement moduleMethod, XTypeElement moduleElement) { + checkArgument(moduleMethod.hasAnnotation(TypeNames.MULTIBINDS)); return forDeclaredMethod( - moduleMethod, - MoreTypes.asExecutable( - types.asMemberOf(MoreTypes.asDeclared(moduleElement.asType()), moduleMethod)), - moduleElement); + moduleMethod, moduleMethod.asMemberOf(moduleElement.getType()), moduleElement); } private MultibindingDeclaration forDeclaredMethod( - ExecutableElement method, - ExecutableType methodType, - TypeElement contributingType) { - TypeMirror returnType = methodType.getReturnType(); + XMethodElement method, XMethodType methodType, XTypeElement contributingType) { + XType returnType = methodType.getReturnType(); checkArgument( SetType.isSet(returnType) || MapType.isMap(returnType), "%s must return a set or map", method); return new AutoValue_MultibindingDeclaration( - Optional.<Element>of(method), + Optional.of(method), Optional.of(contributingType), - keyFactory.forMultibindsMethod(methodType, method), + keyFactory.forMultibindsMethod(method, methodType), contributionType(returnType)); } - private ContributionType contributionType(TypeMirror returnType) { + private ContributionType contributionType(XType returnType) { if (MapType.isMap(returnType)) { return ContributionType.MAP; } else if (SetType.isSet(returnType)) { diff --git a/java/dagger/internal/codegen/binding/OptionalBindingDeclaration.java b/java/dagger/internal/codegen/binding/OptionalBindingDeclaration.java index d7ba7bcfc..d708c0ac2 100644 --- a/java/dagger/internal/codegen/binding/OptionalBindingDeclaration.java +++ b/java/dagger/internal/codegen/binding/OptionalBindingDeclaration.java @@ -16,18 +16,17 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.Preconditions.checkArgument; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import dagger.BindsOptionalOf; -import dagger.model.Key; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.Key; import java.util.Optional; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; /** A {@link BindsOptionalOf} declaration. */ @AutoValue @@ -57,10 +56,10 @@ abstract class OptionalBindingDeclaration extends BindingDeclaration { this.keyFactory = keyFactory; } - OptionalBindingDeclaration forMethod(ExecutableElement method, TypeElement contributingModule) { - checkArgument(isAnnotationPresent(method, BindsOptionalOf.class)); + OptionalBindingDeclaration forMethod(XMethodElement method, XTypeElement contributingModule) { + checkArgument(method.hasAnnotation(TypeNames.BINDS_OPTIONAL_OF)); return new AutoValue_OptionalBindingDeclaration( - Optional.<Element>of(method), + Optional.of(method), Optional.of(contributingModule), keyFactory.forBindsOptionalOfMethod(method, contributingModule)); } diff --git a/java/dagger/internal/codegen/binding/ProductionBinding.java b/java/dagger/internal/codegen/binding/ProductionBinding.java index ad85a68d0..b803089e8 100644 --- a/java/dagger/internal/codegen/binding/ProductionBinding.java +++ b/java/dagger/internal/codegen/binding/ProductionBinding.java @@ -19,6 +19,8 @@ package dagger.internal.codegen.binding; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.langmodel.DaggerTypes.isFutureType; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableList; @@ -26,12 +28,10 @@ import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import dagger.internal.codegen.base.ContributionType; import dagger.internal.codegen.base.SetType; -import dagger.model.DependencyRequest; -import dagger.model.Key; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; import java.util.Optional; import java.util.stream.Stream; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; /** A value object representing the mechanism by which a {@link Key} can be produced. */ @AutoValue @@ -63,7 +63,7 @@ public abstract class ProductionBinding extends ContributionBinding { SET_OF_FUTURE; /** Returns the kind of object a {@code @Produces}-annotated method returns. */ - public static ProductionKind fromProducesMethod(ExecutableElement producesMethod) { + public static ProductionKind fromProducesMethod(XMethodElement producesMethod) { if (isFutureType(producesMethod.getReturnType())) { return FUTURE; } else if (ContributionType.fromBindingElement(producesMethod) @@ -84,7 +84,7 @@ public abstract class ProductionBinding extends ContributionBinding { public abstract Optional<ProductionKind> productionKind(); /** Returns the list of types in the throws clause of the method. */ - public abstract ImmutableList<? extends TypeMirror> thrownTypes(); + public abstract ImmutableList<XType> thrownTypes(); /** * If this production requires an executor, this will be the corresponding request. All @@ -110,7 +110,7 @@ public abstract class ProductionBinding extends ContributionBinding { public static Builder builder() { return new AutoValue_ProductionBinding.Builder() .explicitDependencies(ImmutableList.<DependencyRequest>of()) - .thrownTypes(ImmutableList.<TypeMirror>of()); + .thrownTypes(ImmutableList.<XType>of()); } @Override @@ -142,7 +142,7 @@ public abstract class ProductionBinding extends ContributionBinding { @Override public abstract Builder unresolved(ProductionBinding unresolved); - abstract Builder thrownTypes(Iterable<? extends TypeMirror> thrownTypes); + abstract Builder thrownTypes(Iterable<XType> thrownTypes); abstract Builder executorRequest(DependencyRequest executorRequest); diff --git a/java/dagger/internal/codegen/binding/ProvisionBinding.java b/java/dagger/internal/codegen/binding/ProvisionBinding.java index c917dd6b5..2447e30de 100644 --- a/java/dagger/internal/codegen/binding/ProvisionBinding.java +++ b/java/dagger/internal/codegen/binding/ProvisionBinding.java @@ -17,8 +17,8 @@ package dagger.internal.codegen.binding; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import static dagger.model.BindingKind.COMPONENT_PROVISION; -import static dagger.model.BindingKind.PROVISION; +import static dagger.spi.model.BindingKind.COMPONENT_PROVISION; +import static dagger.spi.model.BindingKind.PROVISION; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; @@ -27,10 +27,10 @@ import com.google.common.collect.ImmutableSortedSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.model.BindingKind; -import dagger.model.DependencyRequest; -import dagger.model.Key; -import dagger.model.Scope; +import dagger.spi.model.BindingKind; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; +import dagger.spi.model.Scope; import java.util.Optional; /** A value object representing the mechanism by which a {@link Key} can be provided. */ diff --git a/java/dagger/internal/codegen/binding/ResolvedBindings.java b/java/dagger/internal/codegen/binding/ResolvedBindings.java index 74301feda..2ef9bd63f 100644 --- a/java/dagger/internal/codegen/binding/ResolvedBindings.java +++ b/java/dagger/internal/codegen/binding/ResolvedBindings.java @@ -16,6 +16,7 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.getOnlyElement; @@ -26,7 +27,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimap; -import dagger.model.Key; +import dagger.spi.model.Key; import javax.lang.model.element.TypeElement; /** @@ -99,7 +100,7 @@ abstract class ResolvedBindings { /** All bindings for {@link #key()} that are owned by a component. */ ImmutableSet<? extends Binding> bindingsOwnedBy(ComponentDescriptor component) { - return allBindings().get(component.typeElement()); + return allBindings().get(toJavac(component.typeElement())); } /** @@ -151,7 +152,7 @@ abstract class ResolvedBindings { return new AutoValue_ResolvedBindings( key, ImmutableSetMultimap.of(), - ImmutableMap.of(owningComponent.typeElement(), ownedMembersInjectionBinding), + ImmutableMap.of(toJavac(owningComponent.typeElement()), ownedMembersInjectionBinding), ImmutableSet.of(), ImmutableSet.of(), ImmutableSet.of()); diff --git a/java/dagger/internal/codegen/binding/SourceFiles.java b/java/dagger/internal/codegen/binding/SourceFiles.java index 3b6d9d9d1..84bdd2ba5 100644 --- a/java/dagger/internal/codegen/binding/SourceFiles.java +++ b/java/dagger/internal/codegen/binding/SourceFiles.java @@ -16,6 +16,8 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static com.google.auto.common.MoreElements.asType; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.Preconditions.checkArgument; @@ -28,16 +30,21 @@ import static dagger.internal.codegen.javapoet.TypeNames.MAP_OF_PRODUCED_PRODUCE import static dagger.internal.codegen.javapoet.TypeNames.MAP_OF_PRODUCER_PRODUCER; import static dagger.internal.codegen.javapoet.TypeNames.MAP_PRODUCER; import static dagger.internal.codegen.javapoet.TypeNames.MAP_PROVIDER_FACTORY; +import static dagger.internal.codegen.javapoet.TypeNames.PRODUCER; +import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER; import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER_OF_LAZY; import static dagger.internal.codegen.javapoet.TypeNames.SET_FACTORY; import static dagger.internal.codegen.javapoet.TypeNames.SET_OF_PRODUCED_PRODUCER; import static dagger.internal.codegen.javapoet.TypeNames.SET_PRODUCER; -import static dagger.model.BindingKind.ASSISTED_INJECTION; -import static dagger.model.BindingKind.INJECTION; -import static dagger.model.BindingKind.MULTIBOUND_MAP; -import static dagger.model.BindingKind.MULTIBOUND_SET; +import static dagger.internal.codegen.xprocessing.XElements.asExecutable; +import static dagger.spi.model.BindingKind.ASSISTED_INJECTION; +import static dagger.spi.model.BindingKind.INJECTION; +import static dagger.spi.model.BindingKind.MULTIBOUND_MAP; +import static dagger.spi.model.BindingKind.MULTIBOUND_SET; import static javax.lang.model.SourceVersion.isName; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.common.MoreElements; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; @@ -51,17 +58,12 @@ import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeVariableName; -import dagger.internal.SetFactory; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.SetType; -import dagger.model.DependencyRequest; -import dagger.model.RequestKind; -import dagger.producers.Produced; -import dagger.producers.Producer; -import dagger.producers.internal.SetOfProducedProducer; -import dagger.producers.internal.SetProducer; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.RequestKind; import java.util.List; -import javax.inject.Provider; import javax.lang.model.SourceVersion; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -97,9 +99,8 @@ public class SourceFiles { binding.dependencies(), dependency -> FrameworkField.create( - ClassName.get( - frameworkTypeMapper.getFrameworkType(dependency.kind()).frameworkClass()), - TypeName.get(dependency.key().type()), + frameworkTypeMapper.getFrameworkType(dependency.kind()).frameworkClassName(), + TypeName.get(dependency.key().type().java()), DependencyVariableNamer.name(dependency))); } @@ -144,11 +145,10 @@ public class SourceFiles { case INJECTION: case PROVISION: case PRODUCTION: - return elementBasedClassName( - MoreElements.asExecutable(binding.bindingElement().get()), "Factory"); + return factoryNameForElement(asExecutable(binding.bindingElement().get())); case ASSISTED_FACTORY: - return siblingClassName(MoreElements.asType(binding.bindingElement().get()), "_Impl"); + return siblingClassName(asType(toJavac(binding.bindingElement().get())), "_Impl"); default: throw new AssertionError(); @@ -162,6 +162,18 @@ public class SourceFiles { } /** + * Returns the generated factory name for the given element. + * + * <p>This method is useful during validation before a {@link Binding} can be created. If a + * binding already exists for the given element, prefer to call {@link + * #generatedClassNameForBinding(Binding)} instead since this method does not validate that the + * given element is actually a binding element or not. + */ + public static ClassName factoryNameForElement(XExecutableElement element) { + return elementBasedClassName(toJavac(element), "Factory"); + } + + /** * Calculates an appropriate {@link ClassName} for a generated class that is based on {@code * element}, appending {@code suffix} at the end. * @@ -188,8 +200,12 @@ public class SourceFiles { : ParameterizedTypeName.get(className, Iterables.toArray(typeParameters, TypeName.class)); } + public static ClassName membersInjectorNameForType(XTypeElement typeElement) { + return membersInjectorNameForType(toJavac(typeElement)); + } + public static ClassName membersInjectorNameForType(TypeElement typeElement) { - return siblingClassName(typeElement, "_MembersInjector"); + return siblingClassName(typeElement, "_MembersInjector"); } public static String memberInjectedFieldSignatureForVariable(VariableElement variableElement) { @@ -202,8 +218,8 @@ public class SourceFiles { return CLASS_FILE_NAME_JOINER.join(className.simpleNames()); } - public static ClassName generatedMonitoringModuleName(TypeElement componentElement) { - return siblingClassName(componentElement, "_MonitoringModule"); + public static ClassName generatedMonitoringModuleName(XTypeElement componentElement) { + return siblingClassName(toJavac(componentElement), "_MonitoringModule"); } // TODO(ronshapiro): when JavaPoet migration is complete, replace the duplicated code @@ -217,9 +233,10 @@ public class SourceFiles { * The {@link java.util.Set} factory class name appropriate for set bindings. * * <ul> - * <li>{@link SetFactory} for provision bindings. - * <li>{@link SetProducer} for production bindings for {@code Set<T>}. - * <li>{@link SetOfProducedProducer} for production bindings for {@code Set<Produced<T>>}. + * <li>{@link dagger.producers.internal.SetFactory} for provision bindings. + * <li>{@link dagger.producers.internal.SetProducer} for production bindings for {@code Set<T>}. + * <li>{@link dagger.producers.internal.SetOfProducedProducer} for production bindings for + * {@code Set<Produced<T>>}. * </ul> */ public static ClassName setFactoryClassName(ContributionBinding binding) { @@ -228,7 +245,9 @@ public class SourceFiles { return SET_FACTORY; } else { SetType setType = SetType.from(binding.key()); - return setType.elementsAreTypeOf(Produced.class) ? SET_OF_PRODUCED_PRODUCER : SET_PRODUCER; + return setType.elementsAreTypeOf(TypeNames.PRODUCED) + ? SET_OF_PRODUCED_PRODUCER + : SET_PRODUCER; } } @@ -238,10 +257,10 @@ public class SourceFiles { MapType mapType = MapType.from(binding.key()); switch (binding.bindingType()) { case PROVISION: - return mapType.valuesAreTypeOf(Provider.class) ? MAP_PROVIDER_FACTORY : MAP_FACTORY; + return mapType.valuesAreTypeOf(PROVIDER) ? MAP_PROVIDER_FACTORY : MAP_FACTORY; case PRODUCTION: return mapType.valuesAreFrameworkType() - ? mapType.valuesAreTypeOf(Producer.class) + ? mapType.valuesAreTypeOf(PRODUCER) ? MAP_OF_PRODUCER_PRODUCER : MAP_OF_PRODUCED_PRODUCER : MAP_PRODUCER; @@ -261,7 +280,7 @@ public class SourceFiles { } } List<? extends TypeParameterElement> typeParameters = - binding.bindingTypeElement().get().getTypeParameters(); + toJavac(binding.bindingTypeElement().get()).getTypeParameters(); return typeParameters.stream().map(TypeVariableName::get).collect(toImmutableList()); } @@ -272,7 +291,16 @@ public class SourceFiles { */ // TODO(gak): maybe this should be a function of TypeMirrors instead of Elements? public static String simpleVariableName(TypeElement typeElement) { - String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, typeElement.getSimpleName().toString()); + return simpleVariableName(ClassName.get(typeElement)); + } + + /** + * Returns a name to be used for variables of the given {@linkplain ClassName}. Prefer + * semantically meaningful variable names, but if none can be derived, this will produce something + * readable. + */ + public static String simpleVariableName(ClassName className) { + String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, className.simpleName()); String variableName = protectAgainstKeywords(candidateName); verify(isName(variableName), "'%s' was expected to be a valid variable name"); return variableName; diff --git a/java/dagger/internal/codegen/binding/SubcomponentCreatorBindingEdgeImpl.java b/java/dagger/internal/codegen/binding/SubcomponentCreatorBindingEdgeImpl.java index 93e79b5b9..5998b98e8 100644 --- a/java/dagger/internal/codegen/binding/SubcomponentCreatorBindingEdgeImpl.java +++ b/java/dagger/internal/codegen/binding/SubcomponentCreatorBindingEdgeImpl.java @@ -22,12 +22,12 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static java.util.stream.Collectors.joining; import com.google.common.collect.ImmutableSet; -import dagger.model.BindingGraph.SubcomponentCreatorBindingEdge; -import javax.lang.model.element.TypeElement; +import com.squareup.javapoet.ClassName; +import dagger.spi.model.BindingGraph.SubcomponentCreatorBindingEdge; +import dagger.spi.model.DaggerTypeElement; /** An implementation of {@link SubcomponentCreatorBindingEdge}. */ public final class SubcomponentCreatorBindingEdgeImpl implements SubcomponentCreatorBindingEdge { - private final ImmutableSet<SubcomponentDeclaration> subcomponentDeclarations; SubcomponentCreatorBindingEdgeImpl( @@ -36,10 +36,11 @@ public final class SubcomponentCreatorBindingEdgeImpl implements SubcomponentCre } @Override - public ImmutableSet<TypeElement> declaringModules() { + public ImmutableSet<DaggerTypeElement> declaringModules() { return subcomponentDeclarations.stream() .map(SubcomponentDeclaration::contributingModule) .flatMap(presentValues()) + .map(DaggerTypeElement::from) .collect(toImmutableSet()); } @@ -47,9 +48,10 @@ public final class SubcomponentCreatorBindingEdgeImpl implements SubcomponentCre public String toString() { return "subcomponent declared by " + (subcomponentDeclarations.size() == 1 - ? getOnlyElement(declaringModules()).getQualifiedName() + ? getOnlyElement(declaringModules()).className().canonicalName() : declaringModules().stream() - .map(TypeElement::getQualifiedName) + .map(DaggerTypeElement::className) + .map(ClassName::canonicalName) .collect(joining(", ", "{", "}"))); } } diff --git a/java/dagger/internal/codegen/binding/SubcomponentDeclaration.java b/java/dagger/internal/codegen/binding/SubcomponentDeclaration.java index 4f1f3efd2..dfc290587 100644 --- a/java/dagger/internal/codegen/binding/SubcomponentDeclaration.java +++ b/java/dagger/internal/codegen/binding/SubcomponentDeclaration.java @@ -16,18 +16,20 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.AnnotationMirrors.getAnnotationElementAndValue; import static dagger.internal.codegen.binding.ConfigurationAnnotations.getSubcomponentCreator; +import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableSet; +import dagger.internal.codegen.base.DaggerSuperficialValidation; import dagger.internal.codegen.base.ModuleAnnotation; -import dagger.model.Key; +import dagger.spi.model.Key; import java.util.Optional; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; /** * A declaration for a subcomponent that is included in a module via {@link @@ -46,7 +48,7 @@ public abstract class SubcomponentDeclaration extends BindingDeclaration { * The type element that defines the {@link dagger.Subcomponent} or {@link * dagger.producers.ProductionSubcomponent} for this declaration. */ - abstract TypeElement subcomponentType(); + abstract XTypeElement subcomponentType(); /** The module annotation. */ public abstract ModuleAnnotation moduleAnnotation(); @@ -61,24 +63,31 @@ public abstract class SubcomponentDeclaration extends BindingDeclaration { /** A {@link SubcomponentDeclaration} factory. */ public static class Factory { private final KeyFactory keyFactory; + private final DaggerSuperficialValidation superficialValidation; @Inject - Factory(KeyFactory keyFactory) { + Factory(KeyFactory keyFactory, DaggerSuperficialValidation superficialValidation) { this.keyFactory = keyFactory; + this.superficialValidation = superficialValidation; } - ImmutableSet<SubcomponentDeclaration> forModule(TypeElement module) { + ImmutableSet<SubcomponentDeclaration> forModule(XTypeElement module) { + ModuleAnnotation moduleAnnotation = + ModuleAnnotation.moduleAnnotation(module, superficialValidation).get(); + XElement subcomponentAttribute = + moduleAnnotation.annotation().getType().getTypeElement().getDeclaredMethods().stream() + .filter(method -> getSimpleName(method).contentEquals("subcomponents")) + .collect(toOptional()) + .get(); + ImmutableSet.Builder<SubcomponentDeclaration> declarations = ImmutableSet.builder(); - ModuleAnnotation moduleAnnotation = ModuleAnnotation.moduleAnnotation(module).get(); - Element subcomponentAttribute = - getAnnotationElementAndValue(moduleAnnotation.annotation(), "subcomponents").getKey(); - for (TypeElement subcomponent : moduleAnnotation.subcomponents()) { + for (XTypeElement subcomponent : moduleAnnotation.subcomponents()) { declarations.add( new AutoValue_SubcomponentDeclaration( Optional.of(subcomponentAttribute), Optional.of(module), keyFactory.forSubcomponentCreator( - getSubcomponentCreator(subcomponent).get().asType()), + getSubcomponentCreator(subcomponent).get().getType()), subcomponent, moduleAnnotation)); } diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/BUILD b/java/dagger/internal/codegen/bindinggraphvalidation/BUILD index da35b516a..cb7db877c 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/BUILD +++ b/java/dagger/internal/codegen/bindinggraphvalidation/BUILD @@ -30,16 +30,18 @@ java_library( "//java/dagger/internal/codegen/binding", "//java/dagger/internal/codegen/compileroption", "//java/dagger/internal/codegen/extension", + "//java/dagger/internal/codegen/javapoet", "//java/dagger/internal/codegen/kotlin", "//java/dagger/internal/codegen/langmodel", "//java/dagger/internal/codegen/validation", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:graph", - "//java/dagger/producers", + "//java/dagger/internal/codegen/xprocessing", "//java/dagger/spi", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/jsr330_inject", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/graph", + "//third_party/java/javapoet", + "//third_party/java/jsr330_inject", ], ) diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/BindingGraphValidationModule.java b/java/dagger/internal/codegen/bindinggraphvalidation/BindingGraphValidationModule.java index a1f1848cf..f689427ac 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/BindingGraphValidationModule.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/BindingGraphValidationModule.java @@ -22,7 +22,7 @@ import dagger.Provides; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.validation.CompositeBindingGraphPlugin; import dagger.internal.codegen.validation.Validation; -import dagger.spi.BindingGraphPlugin; +import dagger.spi.model.BindingGraphPlugin; /** Binds the set of {@link BindingGraphPlugin}s used to implement Dagger validation. */ @Module @@ -42,7 +42,8 @@ public interface BindingGraphValidationModule { MissingBindingValidator validation7, NullableBindingValidator validation8, ProvisionDependencyOnProducerBindingValidator validation9, - SubcomponentFactoryMethodValidator validation10) { + SetMultibindingValidator validation10, + SubcomponentFactoryMethodValidator validation11) { ImmutableSet<BindingGraphPlugin> plugins = ImmutableSet.of( validation1, validation2, @@ -53,7 +54,8 @@ public interface BindingGraphValidationModule { validation7, validation8, validation9, - validation10); + validation10, + validation11); if (compilerOptions.experimentalDaggerErrorMessages()) { return ImmutableSet.of(factory.create(plugins, "Dagger/Validation")); } else { diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/DependencyCycleValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/DependencyCycleValidator.java index bc89ebbb2..f2cb90b75 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/DependencyCycleValidator.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/DependencyCycleValidator.java @@ -29,6 +29,7 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static javax.tools.Diagnostic.Kind.ERROR; +import androidx.room.compiler.processing.XType; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -38,25 +39,26 @@ import com.google.common.graph.Graphs; import com.google.common.graph.ImmutableNetwork; import com.google.common.graph.MutableNetwork; import com.google.common.graph.NetworkBuilder; +import dagger.internal.codegen.base.Formatter; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.OptionalType; import dagger.internal.codegen.binding.DependencyRequestFormatter; -import dagger.model.BindingGraph; -import dagger.model.BindingGraph.ComponentNode; -import dagger.model.BindingGraph.DependencyEdge; -import dagger.model.BindingGraph.Node; -import dagger.model.BindingKind; -import dagger.model.DependencyRequest; -import dagger.model.RequestKind; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraph.ComponentNode; +import dagger.spi.model.BindingGraph.DependencyEdge; +import dagger.spi.model.BindingGraph.Node; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.BindingKind; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.DiagnosticReporter; +import dagger.spi.model.RequestKind; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; import javax.inject.Inject; -import javax.inject.Provider; -import javax.lang.model.type.TypeMirror; /** Reports errors for dependency cycles. */ final class DependencyCycleValidator implements BindingGraphPlugin { @@ -143,7 +145,11 @@ final class DependencyCycleValidator implements BindingGraphPlugin { DependencyEdge dependencyToReport = chooseDependencyEdgeConnecting(previousNode, cycleStartNode, bindingGraph); diagnosticReporter.reportDependency( - ERROR, dependencyToReport, errorMessage(cycle.shift(cycleStartNode), bindingGraph)); + ERROR, + dependencyToReport, + errorMessage(cycle.shift(cycleStartNode), bindingGraph) + // The actual dependency trace is included from the reportDependency call. + + "\n\nThe cycle is requested via:"); } private ImmutableList<Node> shortestPathToCycleFromAnEntryPoint( @@ -182,6 +188,10 @@ final class DependencyCycleValidator implements BindingGraphPlugin { .collect(toImmutableList()) .reverse(); dependencyRequestFormatter.formatIndentedList(message, cycleRequests, 0); + message.append("\n") + .append(dependencyRequestFormatter.format(cycleRequests.get(0))) + .append("\n") + .append(Formatter.INDENT).append("..."); return message.toString(); } @@ -203,22 +213,22 @@ final class DependencyCycleValidator implements BindingGraphPlugin { if (edge.dependencyRequest().key().multibindingContributionIdentifier().isPresent()) { return false; } - if (breaksCycle(edge.dependencyRequest().key().type(), edge.dependencyRequest().kind())) { + if (breaksCycle( + edge.dependencyRequest().key().type().xprocessing(), edge.dependencyRequest().kind())) { return true; } Node target = graph.network().incidentNodes(edge).target(); - if (target instanceof dagger.model.Binding - && ((dagger.model.Binding) target).kind().equals(BindingKind.OPTIONAL)) { + if (target instanceof Binding && ((Binding) target).kind().equals(BindingKind.OPTIONAL)) { /* For @BindsOptionalOf bindings, unwrap the type inside the Optional. If the unwrapped type * breaks the cycle, so does the optional binding. */ - TypeMirror optionalValueType = OptionalType.from(edge.dependencyRequest().key()).valueType(); + XType optionalValueType = OptionalType.from(edge.dependencyRequest().key()).valueType(); RequestKind requestKind = getRequestKind(optionalValueType); return breaksCycle(extractKeyType(optionalValueType), requestKind); } return false; } - private boolean breaksCycle(TypeMirror requestedType, RequestKind requestKind) { + private boolean breaksCycle(XType requestedType, RequestKind requestKind) { switch (requestKind) { case PROVIDER: case LAZY: @@ -227,8 +237,7 @@ final class DependencyCycleValidator implements BindingGraphPlugin { case INSTANCE: if (MapType.isMap(requestedType)) { - MapType mapType = MapType.from(requestedType); - return !mapType.isRawType() && mapType.valuesAreTypeOf(Provider.class); + return MapType.from(requestedType).valuesAreTypeOf(TypeNames.PROVIDER); } // fall through diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/DependsOnProductionExecutorValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/DependsOnProductionExecutorValidator.java index 08e2c3e65..1d44133ce 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/DependsOnProductionExecutorValidator.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/DependsOnProductionExecutorValidator.java @@ -21,11 +21,12 @@ import static javax.tools.Diagnostic.Kind.ERROR; import dagger.internal.codegen.binding.KeyFactory; import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.model.BindingGraph; -import dagger.model.BindingGraph.MaybeBinding; -import dagger.model.Key; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraph.MaybeBinding; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.DiagnosticReporter; +import dagger.spi.model.Key; import javax.inject.Inject; /** @@ -64,7 +65,7 @@ final class DependsOnProductionExecutorValidator implements BindingGraphPlugin { .forEach(binding -> reportError(diagnosticReporter, binding)); } - private void reportError(DiagnosticReporter diagnosticReporter, dagger.model.Binding binding) { + private void reportError(DiagnosticReporter diagnosticReporter, Binding binding) { diagnosticReporter.reportBinding( ERROR, binding, "%s may not depend on the production executor", binding.key()); } diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/DuplicateBindingsValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/DuplicateBindingsValidator.java index 5c4da51b5..db8d38238 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/DuplicateBindingsValidator.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/DuplicateBindingsValidator.java @@ -21,8 +21,8 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.Formatter.INDENT; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap; -import static dagger.model.BindingKind.INJECTION; -import static dagger.model.BindingKind.MEMBERS_INJECTION; +import static dagger.spi.model.BindingKind.INJECTION; +import static dagger.spi.model.BindingKind.MEMBERS_INJECTION; import static java.util.Comparator.comparing; import static javax.tools.Diagnostic.Kind.ERROR; @@ -42,14 +42,16 @@ import dagger.internal.codegen.binding.BindingDeclarationFormatter; import dagger.internal.codegen.binding.BindingNode; import dagger.internal.codegen.binding.MultibindingDeclaration; import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.model.Binding; -import dagger.model.BindingGraph; -import dagger.model.BindingGraph.ComponentNode; -import dagger.model.BindingKind; -import dagger.model.ComponentPath; -import dagger.model.Key; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraph.ComponentNode; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.BindingKind; +import dagger.spi.model.ComponentPath; +import dagger.spi.model.DaggerElement; +import dagger.spi.model.DaggerTypeElement; +import dagger.spi.model.DiagnosticReporter; +import dagger.spi.model.Key; import java.util.Comparator; import java.util.HashSet; import java.util.Optional; @@ -238,7 +240,7 @@ final class DuplicateBindingsValidator implements BindingGraphPlugin { private String incompatibleBindingsMessage( Binding oneBinding, ImmutableSet<Binding> duplicateBindings, BindingGraph graph) { Key key = oneBinding.key(); - ImmutableSet<dagger.model.Binding> multibindings = + ImmutableSet<dagger.spi.model.Binding> multibindings = duplicateBindings.stream() .filter(binding -> binding.kind().isMultibinding()) .collect(toImmutableSet()); @@ -248,11 +250,11 @@ final class DuplicateBindingsValidator implements BindingGraphPlugin { java.util.Formatter messageFormatter = new java.util.Formatter(message); messageFormatter.format("%s has incompatible bindings or declarations:\n", key); message.append(INDENT); - dagger.model.Binding multibinding = getOnlyElement(multibindings); + dagger.spi.model.Binding multibinding = getOnlyElement(multibindings); messageFormatter.format("%s bindings and declarations:", multibindingTypeString(multibinding)); formatDeclarations(message, 2, declarations(graph, multibindings)); - Set<dagger.model.Binding> uniqueBindings = + Set<dagger.spi.model.Binding> uniqueBindings = Sets.filter(duplicateBindings, binding -> !binding.equals(multibinding)); message.append('\n').append(INDENT).append("Unique bindings and declarations:"); formatDeclarations( @@ -276,7 +278,7 @@ final class DuplicateBindingsValidator implements BindingGraphPlugin { } private ImmutableSet<BindingDeclaration> declarations( - BindingGraph graph, Set<dagger.model.Binding> bindings) { + BindingGraph graph, Set<dagger.spi.model.Binding> bindings) { return bindings.stream() .flatMap(binding -> declarations(graph, binding).stream()) .distinct() @@ -285,7 +287,7 @@ final class DuplicateBindingsValidator implements BindingGraphPlugin { } private ImmutableSet<BindingDeclaration> declarations( - BindingGraph graph, dagger.model.Binding binding) { + BindingGraph graph, dagger.spi.model.Binding binding) { ImmutableSet.Builder<BindingDeclaration> declarations = ImmutableSet.builder(); BindingNode bindingNode = (BindingNode) binding; bindingNode.associatedDeclarations().forEach(declarations::add); @@ -299,7 +301,7 @@ final class DuplicateBindingsValidator implements BindingGraphPlugin { return declarations.build(); } - private String multibindingTypeString(dagger.model.Binding multibinding) { + private String multibindingTypeString(dagger.spi.model.Binding multibinding) { switch (multibinding.kind()) { case MULTIBOUND_MAP: return "Map"; @@ -339,7 +341,9 @@ final class DuplicateBindingsValidator implements BindingGraphPlugin { private static BindingElement forBinding(Binding binding) { return new AutoValue_DuplicateBindingsValidator_BindingElement( - binding.kind(), binding.bindingElement(), binding.contributingModule()); + binding.kind(), + binding.bindingElement().map(DaggerElement::java), + binding.contributingModule().map(DaggerTypeElement::java)); } } } diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/IncompatiblyScopedBindingsValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/IncompatiblyScopedBindingsValidator.java index a01dbaf62..b9cf89c33 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/IncompatiblyScopedBindingsValidator.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/IncompatiblyScopedBindingsValidator.java @@ -18,22 +18,23 @@ package dagger.internal.codegen.bindinggraphvalidation; import static dagger.internal.codegen.base.Formatter.INDENT; import static dagger.internal.codegen.base.Scopes.getReadableSource; -import static dagger.internal.codegen.langmodel.DaggerElements.closestEnclosingTypeElement; -import static dagger.model.BindingKind.INJECTION; +import static dagger.internal.codegen.xprocessing.XElements.asExecutable; +import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; +import static dagger.spi.model.BindingKind.INJECTION; import static java.util.stream.Collectors.joining; import static javax.tools.Diagnostic.Kind.ERROR; -import com.google.auto.common.MoreElements; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimaps; import dagger.internal.codegen.base.Scopes; import dagger.internal.codegen.binding.MethodSignatureFormatter; import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.model.Binding; -import dagger.model.BindingGraph; -import dagger.model.BindingGraph.ComponentNode; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.internal.codegen.validation.DiagnosticMessageGenerator; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraph.ComponentNode; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.DiagnosticReporter; import java.util.Optional; import java.util.Set; import javax.inject.Inject; @@ -47,12 +48,16 @@ final class IncompatiblyScopedBindingsValidator implements BindingGraphPlugin { private final MethodSignatureFormatter methodSignatureFormatter; private final CompilerOptions compilerOptions; + private final DiagnosticMessageGenerator.Factory diagnosticMessageGeneratorFactory; @Inject IncompatiblyScopedBindingsValidator( - MethodSignatureFormatter methodSignatureFormatter, CompilerOptions compilerOptions) { + MethodSignatureFormatter methodSignatureFormatter, + CompilerOptions compilerOptions, + DiagnosticMessageGenerator.Factory diagnosticMessageGeneratorFactory) { this.methodSignatureFormatter = methodSignatureFormatter; this.compilerOptions = compilerOptions; + this.diagnosticMessageGeneratorFactory = diagnosticMessageGeneratorFactory; } @Override @@ -62,9 +67,11 @@ final class IncompatiblyScopedBindingsValidator implements BindingGraphPlugin { @Override public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { - ImmutableSetMultimap.Builder<ComponentNode, dagger.model.Binding> incompatibleBindings = + DiagnosticMessageGenerator diagnosticMessageGenerator = + diagnosticMessageGeneratorFactory.create(bindingGraph); + ImmutableSetMultimap.Builder<ComponentNode, dagger.spi.model.Binding> incompatibleBindings = ImmutableSetMultimap.builder(); - for (dagger.model.Binding binding : bindingGraph.bindings()) { + for (dagger.spi.model.Binding binding : bindingGraph.bindings()) { binding .scope() .filter(scope -> !scope.isReusable()) @@ -85,16 +92,19 @@ final class IncompatiblyScopedBindingsValidator implements BindingGraphPlugin { }); } Multimaps.asMap(incompatibleBindings.build()) - .forEach((componentNode, bindings) -> report(componentNode, bindings, diagnosticReporter)); + .forEach((componentNode, bindings) -> + report(componentNode, bindings, diagnosticReporter, diagnosticMessageGenerator)); } private void report( ComponentNode componentNode, Set<Binding> bindings, - DiagnosticReporter diagnosticReporter) { + DiagnosticReporter diagnosticReporter, + DiagnosticMessageGenerator diagnosticMessageGenerator) { Diagnostic.Kind diagnosticKind = ERROR; StringBuilder message = - new StringBuilder(componentNode.componentPath().currentComponent().getQualifiedName()); + new StringBuilder( + componentNode.componentPath().currentComponent().className().canonicalName()); if (!componentNode.isRealComponent()) { // If the "component" is really a module, it will have no scopes attached. We want to report @@ -125,7 +135,7 @@ final class IncompatiblyScopedBindingsValidator implements BindingGraphPlugin { case PROVISION: message.append( methodSignatureFormatter.format( - MoreElements.asExecutable(binding.bindingElement().get()))); + asExecutable(binding.bindingElement().get().xprocessing()))); break; case INJECTION: @@ -133,12 +143,17 @@ final class IncompatiblyScopedBindingsValidator implements BindingGraphPlugin { .append(getReadableSource(binding.scope().get())) .append(" class ") .append( - closestEnclosingTypeElement(binding.bindingElement().get()).getQualifiedName()); + closestEnclosingTypeElement(binding.bindingElement().get().xprocessing()) + .getQualifiedName()) + .append(diagnosticMessageGenerator.getMessage(binding)); + break; default: throw new AssertionError(binding); } + + message.append('\n'); } diagnosticReporter.reportComponent(diagnosticKind, componentNode, message.toString()); } diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/InjectBindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/InjectBindingValidator.java index fe1c3e044..a250612b0 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/InjectBindingValidator.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/InjectBindingValidator.java @@ -16,25 +16,29 @@ package dagger.internal.codegen.bindinggraphvalidation; -import static dagger.model.BindingKind.INJECTION; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; +import static com.google.auto.common.MoreTypes.asTypeElement; +import static dagger.spi.model.BindingKind.INJECTION; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XProcessingEnv; import dagger.internal.codegen.validation.InjectValidator; import dagger.internal.codegen.validation.ValidationReport; import dagger.internal.codegen.validation.ValidationReport.Item; -import dagger.model.BindingGraph; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.DiagnosticReporter; import javax.inject.Inject; -import javax.lang.model.element.TypeElement; /** Validates bindings from {@code @Inject}-annotated constructors. */ final class InjectBindingValidator implements BindingGraphPlugin { + private final XProcessingEnv processingEnv; private final InjectValidator injectValidator; @Inject - InjectBindingValidator(InjectValidator injectValidator) { + InjectBindingValidator(XProcessingEnv processingEnv, InjectValidator injectValidator) { + this.processingEnv = processingEnv; this.injectValidator = injectValidator.whenGeneratingCode(); } @@ -50,10 +54,10 @@ final class InjectBindingValidator implements BindingGraphPlugin { .forEach(binding -> validateInjectionBinding(binding, diagnosticReporter)); } - private void validateInjectionBinding( - dagger.model.Binding node, DiagnosticReporter diagnosticReporter) { - ValidationReport<TypeElement> typeReport = - injectValidator.validateType(MoreTypes.asTypeElement(node.key().type())); + private void validateInjectionBinding(Binding node, DiagnosticReporter diagnosticReporter) { + ValidationReport typeReport = + injectValidator.validate( + toXProcessing(asTypeElement(node.key().type().java()), processingEnv)); for (Item item : typeReport.allItems()) { diagnosticReporter.reportBinding(item.kind(), node, item.message()); } diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/MapMultibindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/MapMultibindingValidator.java index 481c6d82b..feaf88b2c 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/MapMultibindingValidator.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/MapMultibindingValidator.java @@ -21,31 +21,29 @@ import static com.google.common.collect.Multimaps.filterKeys; import static dagger.internal.codegen.base.Formatter.INDENT; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap; -import static dagger.model.BindingKind.MULTIBOUND_MAP; +import static dagger.spi.model.BindingKind.MULTIBOUND_MAP; import static javax.tools.Diagnostic.Kind.ERROR; -import com.google.auto.common.MoreTypes; -import com.google.common.base.Equivalence; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; +import com.squareup.javapoet.TypeName; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.binding.BindingDeclaration; import dagger.internal.codegen.binding.BindingDeclarationFormatter; import dagger.internal.codegen.binding.BindingNode; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.KeyFactory; -import dagger.model.BindingGraph; -import dagger.model.Key; -import dagger.producers.Producer; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.DiagnosticReporter; +import dagger.spi.model.Key; import java.util.Set; import javax.inject.Inject; -import javax.inject.Provider; -import javax.lang.model.type.DeclaredType; /** * Reports an error for any map binding with either more than one contribution with the same map key @@ -91,36 +89,38 @@ final class MapMultibindingValidator implements BindingGraphPlugin { * <li>{@code Map<K, Producer<V>>} * </ol> */ - private ImmutableSet<dagger.model.Binding> mapMultibindings(BindingGraph bindingGraph) { - ImmutableSetMultimap<Key, dagger.model.Binding> mapMultibindings = + private ImmutableSet<Binding> mapMultibindings(BindingGraph bindingGraph) { + ImmutableSetMultimap<Key, Binding> mapMultibindings = bindingGraph.bindings().stream() .filter(node -> node.kind().equals(MULTIBOUND_MAP)) - .collect(toImmutableSetMultimap(dagger.model.Binding::key, node -> node)); + .collect(toImmutableSetMultimap(Binding::key, node -> node)); // Mutlbindings for Map<K, V> - SetMultimap<Key, dagger.model.Binding> plainValueMapMultibindings = + SetMultimap<Key, Binding> plainValueMapMultibindings = filterKeys(mapMultibindings, key -> !MapType.from(key).valuesAreFrameworkType()); // Multibindings for Map<K, Provider<V>> where Map<K, V> isn't in plainValueMapMultibindings - SetMultimap<Key, dagger.model.Binding> providerValueMapMultibindings = + SetMultimap<Key, Binding> providerValueMapMultibindings = filterKeys( mapMultibindings, key -> - MapType.from(key).valuesAreTypeOf(Provider.class) + MapType.from(key).valuesAreTypeOf(TypeNames.PROVIDER) && !plainValueMapMultibindings.containsKey(keyFactory.unwrapMapValueType(key))); // Multibindings for Map<K, Producer<V>> where Map<K, V> isn't in plainValueMapMultibindings and // Map<K, Provider<V>> isn't in providerValueMapMultibindings - SetMultimap<Key, dagger.model.Binding> producerValueMapMultibindings = + SetMultimap<Key, Binding> producerValueMapMultibindings = filterKeys( mapMultibindings, key -> - MapType.from(key).valuesAreTypeOf(Producer.class) + MapType.from(key).valuesAreTypeOf(TypeNames.PRODUCER) && !plainValueMapMultibindings.containsKey(keyFactory.unwrapMapValueType(key)) && !providerValueMapMultibindings.containsKey( - keyFactory.rewrapMapKey(key, Producer.class, Provider.class).get())); + keyFactory + .rewrapMapKey(key, TypeNames.PRODUCER, TypeNames.PROVIDER) + .get())); - return new ImmutableSet.Builder<dagger.model.Binding>() + return new ImmutableSet.Builder<Binding>() .addAll(plainValueMapMultibindings.values()) .addAll(providerValueMapMultibindings.values()) .addAll(producerValueMapMultibindings.values()) @@ -128,7 +128,7 @@ final class MapMultibindingValidator implements BindingGraphPlugin { } private ImmutableSet<ContributionBinding> mapBindingContributions( - dagger.model.Binding binding, BindingGraph bindingGraph) { + Binding binding, BindingGraph bindingGraph) { checkArgument(binding.kind().equals(MULTIBOUND_MAP)); return bindingGraph.requestedBindings(binding).stream() .map(b -> (BindingNode) b) @@ -137,7 +137,7 @@ final class MapMultibindingValidator implements BindingGraphPlugin { } private void checkForDuplicateMapKeys( - dagger.model.Binding multiboundMapBinding, + Binding multiboundMapBinding, ImmutableSet<ContributionBinding> contributions, DiagnosticReporter diagnosticReporter) { ImmutableSetMultimap<?, ContributionBinding> contributionsByMapKey = @@ -156,11 +156,11 @@ final class MapMultibindingValidator implements BindingGraphPlugin { } private void checkForInconsistentMapKeyAnnotationTypes( - dagger.model.Binding multiboundMapBinding, + Binding multiboundMapBinding, ImmutableSet<ContributionBinding> contributions, DiagnosticReporter diagnosticReporter) { - ImmutableSetMultimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding> - contributionsByMapKeyAnnotationType = indexByMapKeyAnnotationType(contributions); + ImmutableSetMultimap<TypeName, ContributionBinding> contributionsByMapKeyAnnotationType = + indexByMapKeyAnnotationType(contributions); if (contributionsByMapKeyAnnotationType.keySet().size() > 1) { diagnosticReporter.reportBinding( @@ -171,19 +171,16 @@ final class MapMultibindingValidator implements BindingGraphPlugin { } } - private static ImmutableSetMultimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding> - indexByMapKeyAnnotationType(ImmutableSet<ContributionBinding> contributions) { + private static ImmutableSetMultimap<TypeName, ContributionBinding> indexByMapKeyAnnotationType( + ImmutableSet<ContributionBinding> contributions) { return ImmutableSetMultimap.copyOf( Multimaps.index( contributions, - mapBinding -> - MoreTypes.equivalence() - .wrap(mapBinding.mapKeyAnnotation().get().getAnnotationType()))); + mapBinding -> TypeName.get(mapBinding.mapKeyAnnotation().get().getAnnotationType()))); } private String inconsistentMapKeyAnnotationTypesErrorMessage( - ImmutableSetMultimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding> - contributionsByMapKeyAnnotationType, + ImmutableSetMultimap<TypeName, ContributionBinding> contributionsByMapKeyAnnotationType, Key mapBindingKey) { StringBuilder message = new StringBuilder(mapBindingKey.toString()) @@ -191,7 +188,7 @@ final class MapMultibindingValidator implements BindingGraphPlugin { Multimaps.asMap(contributionsByMapKeyAnnotationType) .forEach( (annotationType, contributions) -> { - message.append('\n').append(INDENT).append(annotationType.get()).append(':'); + message.append('\n').append(INDENT).append(annotationType).append(':'); bindingDeclarationFormatter.formatIndentedList(message, contributions, 2); }); return message.toString(); diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java index 7334cd9c8..fdcccf968 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java @@ -17,36 +17,50 @@ package dagger.internal.codegen.bindinggraphvalidation; import static com.google.common.base.Verify.verify; +import static com.google.common.collect.Iterables.getLast; import static dagger.internal.codegen.base.Keys.isValidImplicitProvisionKey; import static dagger.internal.codegen.base.Keys.isValidMembersInjectionKey; import static dagger.internal.codegen.base.RequestKinds.canBeSatisfiedByProductionBinding; +import static dagger.internal.codegen.binding.DependencyRequestFormatter.DOUBLE_INDENT; import static dagger.internal.codegen.extension.DaggerStreams.instancesOf; +import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; import static javax.tools.Diagnostic.Kind.ERROR; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import dagger.internal.codegen.binding.DependencyRequestFormatter; import dagger.internal.codegen.binding.InjectBindingRegistry; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.BindingGraph; -import dagger.model.BindingGraph.ComponentNode; -import dagger.model.BindingGraph.DependencyEdge; -import dagger.model.BindingGraph.MissingBinding; -import dagger.model.BindingGraph.Node; -import dagger.model.Key; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.internal.codegen.validation.DiagnosticMessageGenerator; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraph.ComponentNode; +import dagger.spi.model.BindingGraph.DependencyEdge; +import dagger.spi.model.BindingGraph.Edge; +import dagger.spi.model.BindingGraph.MissingBinding; +import dagger.spi.model.BindingGraph.Node; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.ComponentPath; +import dagger.spi.model.DiagnosticReporter; +import dagger.spi.model.Key; +import java.util.List; +import java.util.stream.Collectors; import javax.inject.Inject; -import javax.lang.model.type.TypeKind; /** Reports errors for missing bindings. */ final class MissingBindingValidator implements BindingGraphPlugin { - private final DaggerTypes types; private final InjectBindingRegistry injectBindingRegistry; + private final DependencyRequestFormatter dependencyRequestFormatter; + private final DiagnosticMessageGenerator.Factory diagnosticMessageGeneratorFactory; @Inject MissingBindingValidator( - DaggerTypes types, InjectBindingRegistry injectBindingRegistry) { - this.types = types; + InjectBindingRegistry injectBindingRegistry, + DependencyRequestFormatter dependencyRequestFormatter, + DiagnosticMessageGenerator.Factory diagnosticMessageGeneratorFactory) { this.injectBindingRegistry = injectBindingRegistry; + this.dependencyRequestFormatter = dependencyRequestFormatter; + this.diagnosticMessageGeneratorFactory = diagnosticMessageGeneratorFactory; } @Override @@ -68,18 +82,33 @@ final class MissingBindingValidator implements BindingGraphPlugin { private void reportMissingBinding( MissingBinding missingBinding, BindingGraph graph, DiagnosticReporter diagnosticReporter) { - diagnosticReporter.reportBinding( - ERROR, missingBinding, missingBindingErrorMessage(missingBinding, graph)); + List<ComponentPath> alternativeComponents = + graph.bindings(missingBinding.key()).stream() + .map(Binding::componentPath) + .distinct() + .collect(Collectors.toList()); + // Print component name for each binding along the dependency path if the missing binding + // exists in a different component than expected + if (alternativeComponents.isEmpty()) { + diagnosticReporter.reportBinding( + ERROR, missingBinding, missingBindingErrorMessage(missingBinding, graph)); + } else { + diagnosticReporter.reportComponent( + ERROR, + graph.componentNode(missingBinding.componentPath()).get(), + missingBindingErrorMessage(missingBinding, graph) + + wrongComponentErrorMessage(missingBinding, alternativeComponents, graph)); + } } private String missingBindingErrorMessage(MissingBinding missingBinding, BindingGraph graph) { Key key = missingBinding.key(); StringBuilder errorMessage = new StringBuilder(); // Wildcards should have already been checked by DependencyRequestValidator. - verify(!key.type().getKind().equals(TypeKind.WILDCARD), "unexpected wildcard request: %s", key); + verify(!isWildcard(key.type().xprocessing()), "unexpected wildcard request: %s", key); // TODO(ronshapiro): replace "provided" with "satisfied"? errorMessage.append(key).append(" cannot be provided without "); - if (isValidImplicitProvisionKey(key, types)) { + if (isValidImplicitProvisionKey(key)) { errorMessage.append("an @Inject constructor or "); } errorMessage.append("an @Provides-"); // TODO(dpb): s/an/a @@ -91,17 +120,64 @@ final class MissingBindingValidator implements BindingGraphPlugin { errorMessage.append( " This type supports members injection but cannot be implicitly provided."); } - graph.bindings(key).stream() - .map(binding -> binding.componentPath().currentComponent()) - .distinct() - .forEach( - component -> - errorMessage - .append("\nA binding with matching key exists in component: ") - .append(component.getQualifiedName())); return errorMessage.toString(); } + private String wrongComponentErrorMessage( + MissingBinding missingBinding, + List<ComponentPath> alternativeComponentPath, + BindingGraph graph) { + ImmutableSet<DependencyEdge> entryPoints = + graph.entryPointEdgesDependingOnBinding(missingBinding); + DiagnosticMessageGenerator generator = diagnosticMessageGeneratorFactory.create(graph); + ImmutableList<DependencyEdge> dependencyTrace = + generator.dependencyTrace(missingBinding, entryPoints); + StringBuilder message = + graph.isFullBindingGraph() + ? new StringBuilder() + : new StringBuilder(dependencyTrace.size() * 100 /* a guess heuristic */); + // Check in which component the missing binding is requested. This can be different from the + // component the missing binding is in because we'll try to search up the parent components for + // a binding which makes missing bindings end up at the root component. This is different from + // the place we are logically requesting the binding from. Note that this is related to the + // particular dependency trace being shown and so is not necessarily stable. + String missingComponentName = + getComponentFromDependencyEdge(dependencyTrace.get(0), graph, false); + boolean hasSameComponentName = false; + for (ComponentPath component : alternativeComponentPath) { + message.append("\nA binding for ").append(missingBinding.key()).append(" exists in "); + String currentComponentName = component.currentComponent().className().canonicalName(); + if (currentComponentName.contentEquals(missingComponentName)) { + hasSameComponentName = true; + message.append("[").append(component).append("]"); + } else { + message.append(currentComponentName); + } + message.append(":"); + } + for (DependencyEdge edge : dependencyTrace) { + String line = dependencyRequestFormatter.format(edge.dependencyRequest()); + if (line.isEmpty()) { + continue; + } + // If we ran into a rare case where the component names collide and we need to show the full + // path, only show the full path for the first dependency request. This is guaranteed to be + // the component in question since the logic for checking for a collision uses the first + // edge in the trace. Do not expand subsequent component paths to reduce spam. + String componentName = + String.format("[%s] ", getComponentFromDependencyEdge(edge, graph, hasSameComponentName)); + hasSameComponentName = false; + message.append("\n").append(line.replace(DOUBLE_INDENT, DOUBLE_INDENT + componentName)); + } + if (!dependencyTrace.isEmpty()) { + generator.appendComponentPathUnlessAtRoot(message, source(getLast(dependencyTrace), graph)); + } + message.append( + generator.getRequestsNotInTrace( + dependencyTrace, generator.requests(missingBinding), entryPoints)); + return message.toString(); + } + private boolean allIncomingDependenciesCanUseProduction( MissingBinding missingBinding, BindingGraph graph) { return graph.network().inEdges(missingBinding).stream() @@ -116,11 +192,11 @@ final class MissingBindingValidator implements BindingGraphPlugin { if (source instanceof ComponentNode) { return canBeSatisfiedByProductionBinding(edge.dependencyRequest().kind()); } - if (source instanceof dagger.model.Binding) { - return ((dagger.model.Binding) source).isProduction(); + if (source instanceof dagger.spi.model.Binding) { + return ((dagger.spi.model.Binding) source).isProduction(); } throw new IllegalArgumentException( - "expected a dagger.model.Binding or ComponentNode: " + source); + "expected a dagger.spi.model.Binding or ComponentNode: " + source); } private boolean typeHasInjectionSites(Key key) { @@ -129,4 +205,16 @@ final class MissingBindingValidator implements BindingGraphPlugin { .map(binding -> !binding.injectionSites().isEmpty()) .orElse(false); } + + private static String getComponentFromDependencyEdge( + DependencyEdge edge, BindingGraph graph, boolean completePath) { + ComponentPath componentPath = graph.network().incidentNodes(edge).source().componentPath(); + return completePath + ? componentPath.toString() + : componentPath.currentComponent().className().canonicalName(); + } + + private Node source(Edge edge, BindingGraph graph) { + return graph.network().incidentNodes(edge).source(); + } } diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidator.java index 9800b15f2..4130fe97e 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidator.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidator.java @@ -24,10 +24,11 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.model.BindingGraph; -import dagger.model.BindingGraph.DependencyEdge; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraph.DependencyEdge; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.DiagnosticReporter; import javax.inject.Inject; /** @@ -45,7 +46,7 @@ final class NullableBindingValidator implements BindingGraphPlugin { @Override public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { - for (dagger.model.Binding binding : nullableBindings(bindingGraph)) { + for (Binding binding : nullableBindings(bindingGraph)) { for (DependencyEdge dependencyEdge : nonNullableDependencies(bindingGraph, binding)) { diagnosticReporter.reportDependency( compilerOptions.nullableValidationKind(), @@ -62,14 +63,14 @@ final class NullableBindingValidator implements BindingGraphPlugin { return "Dagger/Nullable"; } - private ImmutableList<dagger.model.Binding> nullableBindings(BindingGraph bindingGraph) { + private ImmutableList<Binding> nullableBindings(BindingGraph bindingGraph) { return bindingGraph.bindings().stream() .filter(binding -> binding.isNullable()) .collect(toImmutableList()); } private ImmutableSet<DependencyEdge> nonNullableDependencies( - BindingGraph bindingGraph, dagger.model.Binding binding) { + BindingGraph bindingGraph, Binding binding) { return bindingGraph.network().inEdges(binding).stream() .flatMap(instancesOf(DependencyEdge.class)) .filter(edge -> !edge.dependencyRequest().isNullable()) diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/ProvisionDependencyOnProducerBindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/ProvisionDependencyOnProducerBindingValidator.java index 7d742f96f..53904a780 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/ProvisionDependencyOnProducerBindingValidator.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/ProvisionDependencyOnProducerBindingValidator.java @@ -22,11 +22,12 @@ import static dagger.internal.codegen.base.RequestKinds.canBeSatisfiedByProducti import static dagger.internal.codegen.extension.DaggerStreams.instancesOf; import static javax.tools.Diagnostic.Kind.ERROR; -import dagger.model.BindingGraph; -import dagger.model.BindingGraph.DependencyEdge; -import dagger.model.BindingGraph.Node; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraph.DependencyEdge; +import dagger.spi.model.BindingGraph.Node; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.DiagnosticReporter; import java.util.stream.Stream; import javax.inject.Inject; @@ -68,8 +69,7 @@ final class ProvisionDependencyOnProducerBindingValidator implements BindingGrap /** Returns the dependencies on {@code binding}. */ // TODO(dpb): Move to BindingGraph. - private Stream<DependencyEdge> incomingDependencies( - dagger.model.Binding binding, BindingGraph bindingGraph) { + private Stream<DependencyEdge> incomingDependencies(Binding binding, BindingGraph bindingGraph) { return bindingGraph.network().inEdges(binding).stream() .flatMap(instancesOf(DependencyEdge.class)); } @@ -88,16 +88,16 @@ final class ProvisionDependencyOnProducerBindingValidator implements BindingGrap * DependencyEdge#isEntryPoint() entry point}. */ // TODO(dpb): Move to BindingGraph. - private dagger.model.Binding bindingRequestingDependency( + private Binding bindingRequestingDependency( DependencyEdge dependency, BindingGraph bindingGraph) { checkArgument(!dependency.isEntryPoint()); Node source = bindingGraph.network().incidentNodes(dependency).source(); verify( - source instanceof dagger.model.Binding, + source instanceof Binding, "expected source of %s to be a binding, but was: %s", dependency, source); - return (dagger.model.Binding) source; + return (Binding) source; } private String entryPointErrorMessage(DependencyEdge entryPoint) { diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidator.java new file mode 100644 index 000000000..1f79f872e --- /dev/null +++ b/java/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidator.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021 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.bindinggraphvalidation; + +import static dagger.spi.model.BindingKind.DELEGATE; +import static dagger.spi.model.BindingKind.MULTIBOUND_SET; +import static javax.tools.Diagnostic.Kind.ERROR; + +import com.google.common.base.Joiner; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.DiagnosticReporter; +import dagger.spi.model.Key; +import javax.inject.Inject; + +/** Validates that there are not multiple set binding contributions to the same binding. */ +final class SetMultibindingValidator implements BindingGraphPlugin { + + @Inject + SetMultibindingValidator() { + } + + @Override + public String pluginName() { + return "Dagger/SetMultibinding"; + } + + @Override + public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { + bindingGraph.bindings().stream() + .filter(binding -> binding.kind().equals(MULTIBOUND_SET)) + .forEach( + binding -> + checkForDuplicateSetContributions(binding, bindingGraph, diagnosticReporter)); + } + + private void checkForDuplicateSetContributions( + Binding binding, BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { + // Map of delegate target key to the original contribution binding + Multimap<Key, Binding> dereferencedBindsTargets = HashMultimap.create(); + for (Binding dep : bindingGraph.requestedBindings(binding)) { + if (dep.kind().equals(DELEGATE)) { + dereferencedBindsTargets.put(dereferenceDelegateBinding(dep, bindingGraph), dep); + } + } + + dereferencedBindsTargets + .asMap() + .forEach( + (targetKey, contributions) -> { + if (contributions.size() > 1) { + diagnosticReporter.reportComponent( + ERROR, + bindingGraph.componentNode(binding.componentPath()).get(), + "Multiple set contributions into %s for the same contribution key: %s.\n\n" + + " %s\n", + binding.key(), + targetKey, + Joiner.on("\n ").join(contributions)); + } + }); + } + + /** Returns the delegate target of a delegate binding (going through other delegates as well). */ + private Key dereferenceDelegateBinding(Binding binding, BindingGraph bindingGraph) { + ImmutableSet<Binding> delegateSet = bindingGraph.requestedBindings(binding); + if (delegateSet.isEmpty()) { + // There may not be a delegate if the delegate is missing. In this case, we just take the + // requested key and return that. + return Iterables.getOnlyElement(binding.dependencies()).key(); + } + // If there is a binding, first we check if that is a delegate binding so we can dereference + // that binding if needed. + Binding delegate = Iterables.getOnlyElement(delegateSet); + if (delegate.kind().equals(DELEGATE)) { + return dereferenceDelegateBinding(delegate, bindingGraph); + } + return delegate.key(); + } +} diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/SubcomponentFactoryMethodValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/SubcomponentFactoryMethodValidator.java index bf83a69f9..6650c4368 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/SubcomponentFactoryMethodValidator.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/SubcomponentFactoryMethodValidator.java @@ -16,49 +16,39 @@ package dagger.internal.codegen.bindinggraphvalidation; -import static com.google.auto.common.MoreTypes.asDeclared; -import static com.google.auto.common.MoreTypes.asExecutable; -import static com.google.auto.common.MoreTypes.asTypeElements; import static com.google.common.collect.Sets.union; import static dagger.internal.codegen.binding.ComponentRequirement.componentCanMakeNewInstances; import static dagger.internal.codegen.extension.DaggerStreams.instancesOf; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static javax.tools.Diagnostic.Kind.ERROR; +import androidx.room.compiler.processing.XExecutableType; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; import dagger.internal.codegen.base.Util; import dagger.internal.codegen.binding.ComponentNodeImpl; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.BindingGraph; -import dagger.model.BindingGraph.ChildFactoryMethodEdge; -import dagger.model.BindingGraph.ComponentNode; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge; +import dagger.spi.model.BindingGraph.ComponentNode; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.DiagnosticReporter; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.function.Function; import javax.inject.Inject; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ExecutableType; /** Reports an error if a subcomponent factory method is missing required modules. */ final class SubcomponentFactoryMethodValidator implements BindingGraphPlugin { - private final DaggerTypes types; - private final KotlinMetadataUtil metadataUtil; - private final Map<ComponentNode, Set<TypeElement>> inheritedModulesCache = new HashMap<>(); + private final Map<ComponentNode, Set<XTypeElement>> inheritedModulesCache = new HashMap<>(); @Inject - SubcomponentFactoryMethodValidator(DaggerTypes types, KotlinMetadataUtil metadataUtil) { - this.types = types; - this.metadataUtil = metadataUtil; - } + SubcomponentFactoryMethodValidator() {} @Override public String pluginName() { @@ -77,7 +67,7 @@ final class SubcomponentFactoryMethodValidator implements BindingGraphPlugin { .flatMap(instancesOf(ChildFactoryMethodEdge.class)) .forEach( edge -> { - ImmutableSet<TypeElement> missingModules = findMissingModules(edge, bindingGraph); + ImmutableSet<XTypeElement> missingModules = findMissingModules(edge, bindingGraph); if (!missingModules.isEmpty()) { reportMissingModuleParameters( edge, missingModules, bindingGraph, diagnosticReporter); @@ -85,49 +75,50 @@ final class SubcomponentFactoryMethodValidator implements BindingGraphPlugin { }); } - private ImmutableSet<TypeElement> findMissingModules( + private ImmutableSet<XTypeElement> findMissingModules( ChildFactoryMethodEdge edge, BindingGraph graph) { - ImmutableSet<TypeElement> factoryMethodParameters = + ImmutableSet<XTypeElement> factoryMethodParameters = subgraphFactoryMethodParameters(edge, graph); ComponentNode child = (ComponentNode) graph.network().incidentNodes(edge).target(); - SetView<TypeElement> modulesOwnedByChild = ownedModules(child, graph); + SetView<XTypeElement> modulesOwnedByChild = ownedModules(child, graph); return graph.bindings().stream() // bindings owned by child .filter(binding -> binding.componentPath().equals(child.componentPath())) // that require a module instance .filter(binding -> binding.requiresModuleInstance()) - .map(binding -> binding.contributingModule().get()) + .map(binding -> binding.contributingModule().get().xprocessing()) .distinct() // module owned by child .filter(module -> modulesOwnedByChild.contains(module)) // module not in the method parameters .filter(module -> !factoryMethodParameters.contains(module)) // module doesn't have an accessible no-arg constructor - .filter(moduleType -> !componentCanMakeNewInstances(moduleType, metadataUtil)) + .filter(moduleType -> !componentCanMakeNewInstances(moduleType)) .collect(toImmutableSet()); } - private ImmutableSet<TypeElement> subgraphFactoryMethodParameters( + private ImmutableSet<XTypeElement> subgraphFactoryMethodParameters( ChildFactoryMethodEdge edge, BindingGraph bindingGraph) { ComponentNode parent = (ComponentNode) bindingGraph.network().incidentNodes(edge).source(); - DeclaredType parentType = asDeclared(parent.componentPath().currentComponent().asType()); - ExecutableType factoryMethodType = - asExecutable(types.asMemberOf(parentType, edge.factoryMethod())); - return asTypeElements(factoryMethodType.getParameterTypes()); + XType parentType = parent.componentPath().currentComponent().xprocessing().getType(); + XExecutableType factoryMethodType = edge.factoryMethod().xprocessing().asMemberOf(parentType); + return factoryMethodType.getParameterTypes().stream() + .map(XType::getTypeElement) + .collect(toImmutableSet()); } - private SetView<TypeElement> ownedModules(ComponentNode component, BindingGraph graph) { + private SetView<XTypeElement> ownedModules(ComponentNode component, BindingGraph graph) { return Sets.difference( ((ComponentNodeImpl) component).componentDescriptor().moduleTypes(), inheritedModules(component, graph)); } - private Set<TypeElement> inheritedModules(ComponentNode component, BindingGraph graph) { + private Set<XTypeElement> inheritedModules(ComponentNode component, BindingGraph graph) { return Util.reentrantComputeIfAbsent( inheritedModulesCache, component, uncachedInheritedModules(graph)); } - private Function<ComponentNode, Set<TypeElement>> uncachedInheritedModules(BindingGraph graph) { + private Function<ComponentNode, Set<XTypeElement>> uncachedInheritedModules(BindingGraph graph) { return componentNode -> componentNode.componentPath().atRoot() ? ImmutableSet.of() @@ -139,7 +130,7 @@ final class SubcomponentFactoryMethodValidator implements BindingGraphPlugin { private void reportMissingModuleParameters( ChildFactoryMethodEdge edge, - ImmutableSet<TypeElement> missingModules, + ImmutableSet<XTypeElement> missingModules, BindingGraph graph, DiagnosticReporter diagnosticReporter) { diagnosticReporter.reportSubcomponentFactoryMethod( @@ -153,7 +144,8 @@ final class SubcomponentFactoryMethodValidator implements BindingGraphPlugin { .target() .componentPath() .currentComponent() - .getQualifiedName(), + .className() + .canonicalName(), Joiner.on(", ").join(missingModules)); } } diff --git a/java/dagger/internal/codegen/bootstrap/bootstrap_compiler_deploy.jar b/java/dagger/internal/codegen/bootstrap/bootstrap_compiler_deploy.jar Binary files differindex b69dc65d5..69e859d4c 100644 --- a/java/dagger/internal/codegen/bootstrap/bootstrap_compiler_deploy.jar +++ b/java/dagger/internal/codegen/bootstrap/bootstrap_compiler_deploy.jar diff --git a/java/dagger/internal/codegen/compileroption/BUILD b/java/dagger/internal/codegen/compileroption/BUILD index ef39b34b4..6af5401d5 100644 --- a/java/dagger/internal/codegen/compileroption/BUILD +++ b/java/dagger/internal/codegen/compileroption/BUILD @@ -27,12 +27,12 @@ java_library( deps = [ "//java/dagger:core", "//java/dagger/internal/codegen/extension", + "//java/dagger/internal/codegen/javapoet", "//java/dagger/internal/codegen/langmodel", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/producers", - "@google_bazel_common//third_party/java/google_java_format", - "@google_bazel_common//third_party/java/jsr330_inject", - "@maven//:com_google_auto_auto_common", + "//java/dagger/internal/codegen/xprocessing", + "//third_party/java/auto:common", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/jsr330_inject", ], ) diff --git a/java/dagger/internal/codegen/compileroption/CompilerOptions.java b/java/dagger/internal/codegen/compileroption/CompilerOptions.java index a0d1cda34..bd65d4309 100644 --- a/java/dagger/internal/codegen/compileroption/CompilerOptions.java +++ b/java/dagger/internal/codegen/compileroption/CompilerOptions.java @@ -16,7 +16,7 @@ package dagger.internal.codegen.compileroption; -import javax.lang.model.element.TypeElement; +import androidx.room.compiler.processing.XTypeElement; import javax.tools.Diagnostic; /** A collection of options that dictate how the compiler will run. */ @@ -24,6 +24,16 @@ public abstract class CompilerOptions { public abstract boolean usesProducers(); /** + * Returns true if the experimental Android mode is enabled. + * + * <p><b>Warning: Do Not use! This flag is for internal, experimental use only!</b> + * + * <p>Issues related to this flag will not be supported. This flag could break your build, cause + * memory leaks in your app, or cause other unknown issues at runtime. + */ + public abstract boolean experimentalMergedMode(XTypeElement element); + + /** * Returns true if the fast initialization flag, {@code fastInit}, is enabled. * * <p>If enabled, the generated code will attempt to optimize for fast component initialization. @@ -31,7 +41,7 @@ public abstract class CompilerOptions { * number of eagerly initialized fields at the cost of potential memory leaks and higher * per-provision instantiation time. */ - public abstract boolean fastInit(TypeElement element); + public abstract boolean fastInit(XTypeElement element); public abstract boolean formatGeneratedSource(); @@ -48,6 +58,15 @@ public abstract class CompilerOptions { public abstract Diagnostic.Kind staticMemberValidationKind(); /** + * Returns {@code true} if the stacktrace should be included in the deferred error message. + * + * <p>The default for this option is {@code false}. The stacktrace is mostly useful for special + * debugging purposes to gather more information about where the exception was thrown from within + * Dagger's own processors. + */ + public abstract boolean includeStacktraceWithDeferredErrorMessages(); + + /** * If {@code true}, Dagger will generate factories and components even if some members-injected * types have {@code private} or {@code static} {@code @Inject}-annotated members. * @@ -83,7 +102,7 @@ public abstract class CompilerOptions { * * @throws IllegalArgumentException if {@code element} is not a module or (sub)component */ - public abstract boolean pluginsVisitFullBindingGraphs(TypeElement element); + public abstract boolean pluginsVisitFullBindingGraphs(XTypeElement element); public abstract Diagnostic.Kind moduleHasDifferentScopesDiagnosticKind(); @@ -91,8 +110,27 @@ public abstract class CompilerOptions { public abstract boolean experimentalDaggerErrorMessages(); + /** + * Returns {@code true} if strict superficial validation is enabled. + * + * <p>This option is enabled by default and allows Dagger to detect and fail if an element that + * supports being annotated with a scope or qualifier annotation is annotated with any + * unresolvable annotation types. This option is considered "strict" because in most cases we must + * fail for any unresolvable annotation types, not just scopes and qualifiers. In particular, if + * an annotation type is not resolvable, we don't have enough information to tell if it's a scope + * or qualifier, so we must fail for all unresolvable annotations. + * + * <p>This option can be disabled to allow easier migration from the legacy behavior of Dagger + * (i.e. versions less than or equal to 2.40.5). However, we will remove this option in a future + * version of Dagger. + * + * <p>Warning:Disabling this option means that Dagger may miss a scope or qualifier on a binding, + * leading to a (wrong) unscoped binding or a (wrong) unqualified binding, respectively. + */ + public abstract boolean strictSuperficialValidation(); + /** Returns the number of bindings allowed per shard. */ - public int keysPerComponentShard(TypeElement component) { + public int keysPerComponentShard(XTypeElement component) { return 3500; } diff --git a/java/dagger/internal/codegen/compileroption/ProcessingEnvironmentCompilerOptions.java b/java/dagger/internal/codegen/compileroption/ProcessingEnvironmentCompilerOptions.java index 06d15a236..3135d5cd7 100644 --- a/java/dagger/internal/codegen/compileroption/ProcessingEnvironmentCompilerOptions.java +++ b/java/dagger/internal/codegen/compileroption/ProcessingEnvironmentCompilerOptions.java @@ -19,6 +19,7 @@ package dagger.internal.codegen.compileroption; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Sets.immutableEnumSet; import static dagger.internal.codegen.compileroption.FeatureStatus.DISABLED; import static dagger.internal.codegen.compileroption.FeatureStatus.ENABLED; @@ -29,8 +30,10 @@ import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompil import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.FLOATING_BINDS_METHODS; import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.FORMAT_GENERATED_SOURCE; import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.IGNORE_PRIVATE_AND_STATIC_INJECTION_FOR_COMPONENT; +import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.INCLUDE_STACKTRACE_WITH_DEFERRED_ERROR_MESSAGES; import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.PLUGINS_VISIT_FULL_BINDING_GRAPHS; import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.STRICT_MULTIBINDING_VALIDATION; +import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.STRICT_SUPERFICIAL_VALIDATION; import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.VALIDATE_TRANSITIVE_COMPONENT_DEPENDENCIES; import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.WARN_IF_INJECTION_FACTORY_NOT_GENERATED_UPSTREAM; import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.WRITE_PRODUCER_NAME_IN_TOKEN; @@ -50,13 +53,14 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static java.util.stream.Collectors.joining; import static java.util.stream.Stream.concat; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.producers.Produces; import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; @@ -64,34 +68,33 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; -import javax.annotation.processing.ProcessingEnvironment; import javax.inject.Inject; -import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; -/** {@link CompilerOptions} for the given {@link ProcessingEnvironment}. */ +/** {@link CompilerOptions} for the given processor. */ 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 final ProcessingEnvironment processingEnvironment; - private final DaggerElements daggerElements; + private final XMessager messager; + private final Map<String, String> options; + private final DaggerElements elements; private final Map<EnumOption<?>, Object> enumOptions = new HashMap<>(); private final Map<EnumOption<?>, ImmutableMap<String, ? extends Enum<?>>> allCommandLineOptions = new HashMap<>(); @Inject ProcessingEnvironmentCompilerOptions( - ProcessingEnvironment processingEnvironment, DaggerElements daggerElements) { - this.processingEnvironment = processingEnvironment; - this.daggerElements = daggerElements; + XMessager messager, @ProcessingOptions Map<String, String> options, DaggerElements elements) { + this.messager = messager; + this.options = options; + this.elements = elements; checkValid(); } @Override public boolean usesProducers() { - return processingEnvironment.getElementUtils().getTypeElement(Produces.class.getCanonicalName()) - != null; + return elements.getTypeElement(TypeNames.PRODUCES) != null; } @Override @@ -100,10 +103,37 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions } @Override - public boolean fastInit(TypeElement component) { + public boolean experimentalMergedMode(XTypeElement component) { + boolean isExperimental = experimentalMergedModeInternal(); + if (isExperimental) { + checkState( + !fastInitInternal(component), + "Both fast init and experimental merged mode were turned on, please specify exactly one" + + " compilation mode."); + } + return isExperimental; + } + + @Override + public boolean fastInit(XTypeElement component) { + boolean isFastInit = fastInitInternal(component); + if (isFastInit) { + checkState( + !experimentalMergedModeInternal(), + "Both fast init and experimental merged mode were turned on, please specify exactly one" + + " compilation mode."); + } + return isFastInit; + } + + private boolean fastInitInternal(XTypeElement component) { return isEnabled(FAST_INIT); } + private boolean experimentalMergedModeInternal() { + return false; + } + @Override public boolean formatGeneratedSource() { return isEnabled(FORMAT_GENERATED_SOURCE); @@ -130,6 +160,11 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions } @Override + public boolean includeStacktraceWithDeferredErrorMessages() { + return isEnabled(INCLUDE_STACKTRACE_WITH_DEFERRED_ERROR_MESSAGES); + } + + @Override public boolean ignorePrivateAndStaticInjectionForComponent() { return isEnabled(IGNORE_PRIVATE_AND_STATIC_INJECTION_FOR_COMPONENT); } @@ -155,7 +190,7 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions } @Override - public boolean pluginsVisitFullBindingGraphs(TypeElement component) { + public boolean pluginsVisitFullBindingGraphs(XTypeElement component) { return isEnabled(PLUGINS_VISIT_FULL_BINDING_GRAPHS); } @@ -180,20 +215,23 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions } @Override - public int keysPerComponentShard(TypeElement component) { - if (processingEnvironment.getOptions().containsKey(KEYS_PER_COMPONENT_SHARD)) { + public boolean strictSuperficialValidation() { + return isEnabled(STRICT_SUPERFICIAL_VALIDATION); + } + + @Override + public int keysPerComponentShard(XTypeElement component) { + if (options.containsKey(KEYS_PER_COMPONENT_SHARD)) { checkArgument( - "dagger.internal.codegen".contentEquals( - MoreElements.getPackage(component).getQualifiedName()), + component.getClassName().packageName().startsWith("dagger."), "Cannot set %s. It is only meant for internal testing.", KEYS_PER_COMPONENT_SHARD); - return Integer.parseInt( - processingEnvironment.getOptions().get(KEYS_PER_COMPONENT_SHARD)); + return Integer.parseInt(options.get(KEYS_PER_COMPONENT_SHARD)); } return super.keysPerComponentShard(component); } private boolean isEnabled(KeyOnlyOption keyOnlyOption) { - return processingEnvironment.getOptions().containsKey(keyOnlyOption.toString()); + return options.containsKey(keyOnlyOption.toString()); } private boolean isEnabled(Feature feature) { @@ -206,9 +244,6 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions @SuppressWarnings("CheckReturnValue") private ProcessingEnvironmentCompilerOptions checkValid() { - for (KeyOnlyOption keyOnlyOption : KeyOnlyOption.values()) { - isEnabled(keyOnlyOption); - } for (Feature feature : Feature.values()) { parseOption(feature); } @@ -223,11 +258,9 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions } private void noLongerRecognized(CommandLineOption commandLineOption) { - if (processingEnvironment.getOptions().containsKey(commandLineOption.toString())) { - processingEnvironment - .getMessager() - .printMessage( - Diagnostic.Kind.WARNING, commandLineOption + " is no longer recognized by Dagger"); + if (options.containsKey(commandLineOption.toString())) { + messager.printMessage( + Diagnostic.Kind.WARNING, commandLineOption + " is no longer recognized by Dagger"); } } @@ -290,6 +323,8 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions WARN_IF_INJECTION_FACTORY_NOT_GENERATED_UPSTREAM, + INCLUDE_STACKTRACE_WITH_DEFERRED_ERROR_MESSAGES, + IGNORE_PRIVATE_AND_STATIC_INJECTION_FOR_COMPONENT, EXPERIMENTAL_AHEAD_OF_TIME_SUBCOMPONENTS, @@ -306,6 +341,8 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions STRICT_MULTIBINDING_VALIDATION, + STRICT_SUPERFICIAL_VALIDATION(ENABLED), + VALIDATE_TRANSITIVE_COMPONENT_DEPENDENCIES(ENABLED) ; @@ -458,13 +495,11 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions private void reportUseOfDifferentNamesForOption( Diagnostic.Kind diagnosticKind, EnumOption<?> option, ImmutableSet<String> usedNames) { - processingEnvironment - .getMessager() - .printMessage( - diagnosticKind, - String.format( - "Only one of the equivalent options (%s) should be used; prefer -A%s", - usedNames.stream().map(name -> "-A" + name).collect(joining(", ")), option)); + messager.printMessage( + diagnosticKind, + String.format( + "Only one of the equivalent options (%s) should be used; prefer -A%s", + usedNames.stream().map(name -> "-A" + name).collect(joining(", ")), option)); } private <T extends Enum<T>> ImmutableMap<String, T> parseOptionWithAllNames( @@ -486,12 +521,10 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions } private <T extends Enum<T>> Optional<T> parseOptionWithName(EnumOption<T> option, String key) { - checkArgument(processingEnvironment.getOptions().containsKey(key), "key %s not found", key); - String stringValue = processingEnvironment.getOptions().get(key); + checkArgument(options.containsKey(key), "key %s not found", key); + String stringValue = options.get(key); if (stringValue == null) { - processingEnvironment - .getMessager() - .printMessage(Diagnostic.Kind.ERROR, "Processor option -A" + key + " needs a value"); + messager.printMessage(Diagnostic.Kind.ERROR, "Processor option -A" + key + " needs a value"); } else { try { T value = @@ -502,19 +535,16 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions } catch (IllegalArgumentException e) { // handled below } - processingEnvironment - .getMessager() - .printMessage( - Diagnostic.Kind.ERROR, - String.format( - "Processor option -A%s may only have the values %s " - + "(case insensitive), found: %s", - key, option.validValues(), stringValue)); + messager.printMessage( + Diagnostic.Kind.ERROR, + String.format( + "Processor option -A%s may only have the values %s (case insensitive), found: %s", + key, option.validValues(), stringValue)); } return Optional.empty(); } private Stream<String> getUsedNames(CommandLineOption option) { - return option.allNames().filter(name -> processingEnvironment.getOptions().containsKey(name)); + return option.allNames().filter(options::containsKey); } } diff --git a/java/dagger/internal/codegen/componentgenerator/BUILD b/java/dagger/internal/codegen/componentgenerator/BUILD index d898d4d91..859bc0d0b 100644 --- a/java/dagger/internal/codegen/componentgenerator/BUILD +++ b/java/dagger/internal/codegen/componentgenerator/BUILD @@ -25,19 +25,15 @@ java_library( "//java/dagger:core", "//java/dagger/internal/codegen/base", "//java/dagger/internal/codegen/binding", - "//java/dagger/internal/codegen/compileroption", - "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/javapoet", "//java/dagger/internal/codegen/kotlin", "//java/dagger/internal/codegen/langmodel", "//java/dagger/internal/codegen/writing", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/producers", - "//java/dagger/spi", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/jsr330_inject", - "@maven//:com_google_auto_auto_common", + "//java/dagger/internal/codegen/xprocessing", + "//third_party/java/auto:common", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", + "//third_party/java/jsr330_inject", ], ) diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java b/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java index 098da81e6..5442b6481 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java +++ b/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java @@ -17,52 +17,56 @@ package dagger.internal.codegen.componentgenerator; import static com.google.common.base.Verify.verify; -import static dagger.internal.codegen.binding.SourceFiles.classFileName; +import static dagger.internal.codegen.writing.ComponentNames.getRootComponentClassName; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; import com.google.common.collect.ImmutableList; -import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeSpec; import dagger.Component; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.writing.ComponentImplementation; -import javax.annotation.processing.Filer; +import java.util.Optional; import javax.inject.Inject; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; /** Generates the implementation of the abstract types annotated with {@link Component}. */ final class ComponentGenerator extends SourceFileGenerator<BindingGraph> { - private final ComponentImplementationFactory componentImplementationFactory; + private final TopLevelImplementationComponent.Factory topLevelImplementationComponentFactory; @Inject ComponentGenerator( - Filer filer, + XFiler filer, DaggerElements elements, SourceVersion sourceVersion, - ComponentImplementationFactory componentImplementationFactory) { + TopLevelImplementationComponent.Factory topLevelImplementationComponentFactory) { super(filer, elements, sourceVersion); - this.componentImplementationFactory = componentImplementationFactory; - } - - static ClassName componentName(TypeElement componentDefinitionType) { - ClassName componentName = ClassName.get(componentDefinitionType); - return ClassName.get(componentName.packageName(), "Dagger" + classFileName(componentName)); + this.topLevelImplementationComponentFactory = topLevelImplementationComponentFactory; } @Override - public Element originatingElement(BindingGraph input) { + public XElement originatingElement(BindingGraph input) { return input.componentTypeElement(); } @Override public ImmutableList<TypeSpec.Builder> topLevelTypes(BindingGraph bindingGraph) { ComponentImplementation componentImplementation = - componentImplementationFactory.createComponentImplementation(bindingGraph); + topLevelImplementationComponentFactory + .create(bindingGraph) + .currentImplementationSubcomponentBuilder() + .bindingGraph(bindingGraph) + .parentImplementation(Optional.empty()) + .parentRequestRepresentations(Optional.empty()) + .parentRequirementExpressions(Optional.empty()) + .build() + .componentImplementation(); verify( - componentImplementation.name().equals(componentName(bindingGraph.componentTypeElement()))); - return ImmutableList.of(componentImplementation.generate()); + componentImplementation + .name() + .equals(getRootComponentClassName(bindingGraph.componentDescriptor()))); + return ImmutableList.of(componentImplementation.generate().toBuilder()); } } diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java b/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java index c8b8c97c3..7bd9ddcaa 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java +++ b/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java @@ -16,22 +16,27 @@ package dagger.internal.codegen.componentgenerator; -import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.squareup.javapoet.MethodSpec.constructorBuilder; -import static dagger.internal.codegen.binding.ComponentCreatorKind.BUILDER; -import static dagger.internal.codegen.componentgenerator.ComponentGenerator.componentName; +import static dagger.internal.codegen.base.ComponentCreatorKind.BUILDER; import static dagger.internal.codegen.javapoet.TypeSpecs.addSupertype; import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom; -import static javax.lang.model.element.Modifier.ABSTRACT; +import static dagger.internal.codegen.writing.ComponentNames.getRootComponentClassName; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XTypeElements.getAllUnimplementedMethods; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; @@ -39,25 +44,19 @@ import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import dagger.BindsInstance; +import dagger.internal.codegen.base.ComponentCreatorKind; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.ComponentCreatorDescriptor; -import dagger.internal.codegen.binding.ComponentCreatorKind; import dagger.internal.codegen.binding.ComponentDescriptor; import dagger.internal.codegen.binding.ComponentRequirement; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; +import dagger.internal.codegen.binding.MethodSignature; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.producers.internal.CancellationListener; +import dagger.internal.codegen.xprocessing.MethodSpecs; import java.util.Set; import java.util.stream.Stream; -import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; /** * A component generator that emits only API, without any actual implementation. @@ -73,40 +72,28 @@ import javax.lang.model.type.DeclaredType; * normal step. Method bodies are omitted as Turbine ignores them entirely. */ final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescriptor> { - private final DaggerElements elements; - private final DaggerTypes types; - private final KotlinMetadataUtil metadataUtil; - @Inject - ComponentHjarGenerator( - Filer filer, - DaggerElements elements, - DaggerTypes types, - SourceVersion sourceVersion, - KotlinMetadataUtil metadataUtil) { + ComponentHjarGenerator(XFiler filer, DaggerElements elements, SourceVersion sourceVersion) { super(filer, elements, sourceVersion); - this.elements = elements; - this.types = types; - this.metadataUtil = metadataUtil; } @Override - public Element originatingElement(ComponentDescriptor input) { + public XElement originatingElement(ComponentDescriptor input) { return input.typeElement(); } @Override public ImmutableList<TypeSpec.Builder> topLevelTypes(ComponentDescriptor componentDescriptor) { - ClassName generatedTypeName = componentName(componentDescriptor.typeElement()); + ClassName generatedTypeName = getRootComponentClassName(componentDescriptor); TypeSpec.Builder generatedComponent = TypeSpec.classBuilder(generatedTypeName) .addModifiers(FINAL) .addMethod(privateConstructor()); - if (componentDescriptor.typeElement().getModifiers().contains(PUBLIC)) { + if (componentDescriptor.typeElement().isPublic()) { generatedComponent.addModifiers(PUBLIC); } - TypeElement componentElement = componentDescriptor.typeElement(); + XTypeElement componentElement = componentDescriptor.typeElement(); addSupertype(generatedComponent, componentElement); TypeName builderMethodReturnType; @@ -114,7 +101,7 @@ final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescript boolean noArgFactoryMethod; if (componentDescriptor.creatorDescriptor().isPresent()) { ComponentCreatorDescriptor creatorDescriptor = componentDescriptor.creatorDescriptor().get(); - builderMethodReturnType = ClassName.get(creatorDescriptor.typeElement()); + builderMethodReturnType = creatorDescriptor.typeElement().getClassName(); creatorKind = creatorDescriptor.kind(); noArgFactoryMethod = creatorDescriptor.factoryParameters().isEmpty(); } else { @@ -122,7 +109,7 @@ final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescript TypeSpec.classBuilder("Builder") .addModifiers(STATIC, FINAL) .addMethod(privateConstructor()); - if (componentDescriptor.typeElement().getModifiers().contains(PUBLIC)) { + if (componentDescriptor.typeElement().isPublic()) { builder.addModifiers(PUBLIC); } @@ -142,21 +129,18 @@ final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescript if (noArgFactoryMethod && !hasBindsInstanceMethods(componentDescriptor) && componentRequirements(componentDescriptor) - .noneMatch( - requirement -> requirement.requiresAPassedInstance(elements, metadataUtil))) { + .noneMatch(ComponentRequirement::requiresAPassedInstance)) { generatedComponent.addMethod(createMethod(componentDescriptor)); } - DeclaredType componentType = MoreTypes.asDeclared(componentElement.asType()); + XType componentType = componentElement.getType(); // TODO(ronshapiro): unify with ComponentImplementationBuilder Set<MethodSignature> methodSignatures = Sets.newHashSetWithExpectedSize(componentDescriptor.componentMethods().size()); componentDescriptor.componentMethods().stream() .filter( - method -> { - return methodSignatures.add( - MethodSignature.forComponentMethod(method, componentType, types)); - }) + method -> + methodSignatures.add(MethodSignature.forComponentMethod(method, componentType))) .forEach( method -> generatedComponent.addMethod( @@ -164,16 +148,15 @@ final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescript if (componentDescriptor.isProduction()) { generatedComponent - .addSuperinterface(ClassName.get(CancellationListener.class)) + .addSuperinterface(TypeNames.CANCELLATION_LISTENER) .addMethod(onProducerFutureCancelledMethod()); } return ImmutableList.of(generatedComponent); } - private MethodSpec emptyComponentMethod(TypeElement typeElement, ExecutableElement baseMethod) { - return MethodSpec.overriding(baseMethod, MoreTypes.asDeclared(typeElement.asType()), types) - .build(); + private MethodSpec emptyComponentMethod(XTypeElement typeElement, XMethodElement baseMethod) { + return MethodSpecs.overriding(baseMethod, typeElement.getType()).build(); } private static MethodSpec privateConstructor() { @@ -194,40 +177,32 @@ final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescript component.modules().stream() .filter( module -> - !module.moduleElement().getModifiers().contains(ABSTRACT) + !module.moduleElement().isAbstract() && isElementAccessibleFrom( module.moduleElement(), - ClassName.get(component.typeElement()).packageName())) - .map(module -> ComponentRequirement.forModule(module.moduleElement().asType()))); + component.typeElement().getClassName().packageName())) + .map(module -> ComponentRequirement.forModule(module.moduleElement().getType()))); } private boolean hasBindsInstanceMethods(ComponentDescriptor componentDescriptor) { return componentDescriptor.creatorDescriptor().isPresent() - && elements - .getUnimplementedMethods(componentDescriptor.creatorDescriptor().get().typeElement()) + && getAllUnimplementedMethods(componentDescriptor.creatorDescriptor().get().typeElement()) .stream() .anyMatch(method -> isBindsInstance(method)); } - private static boolean isBindsInstance(ExecutableElement method) { - if (isAnnotationPresent(method, BindsInstance.class)) { - return true; - } - - if (method.getParameters().size() == 1) { - return isAnnotationPresent(method.getParameters().get(0), BindsInstance.class); - } - - return false; + private static boolean isBindsInstance(XMethodElement method) { + return method.hasAnnotation(TypeNames.BINDS_INSTANCE) + || (method.getParameters().size() == 1 + && getOnlyElement(method.getParameters()).hasAnnotation(TypeNames.BINDS_INSTANCE)); } private static MethodSpec builderSetterMethod( - TypeElement componentRequirement, ClassName builderClass) { - String simpleName = - UPPER_CAMEL.to(LOWER_CAMEL, componentRequirement.getSimpleName().toString()); + XTypeElement componentRequirement, ClassName builderClass) { + String simpleName = UPPER_CAMEL.to(LOWER_CAMEL, getSimpleName(componentRequirement)); return MethodSpec.methodBuilder(simpleName) .addModifiers(PUBLIC) - .addParameter(ClassName.get(componentRequirement), simpleName) + .addParameter(componentRequirement.getClassName(), simpleName) .returns(builderClass) .build(); } @@ -235,7 +210,7 @@ final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescript private static MethodSpec builderBuildMethod(ComponentDescriptor component) { return MethodSpec.methodBuilder("build") .addModifiers(PUBLIC) - .returns(ClassName.get(component.typeElement())) + .returns(component.typeElement().getClassName()) .build(); } @@ -250,7 +225,7 @@ final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescript private static MethodSpec createMethod(ComponentDescriptor componentDescriptor) { return MethodSpec.methodBuilder("create") .addModifiers(PUBLIC, STATIC) - .returns(ClassName.get(componentDescriptor.typeElement())) + .returns(componentDescriptor.typeElement().getClassName()) .build(); } diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java b/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java deleted file mode 100644 index 2be7d3861..000000000 --- a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java +++ /dev/null @@ -1,524 +0,0 @@ -/* - * Copyright (C) 2015 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.componentgenerator; - -import static com.google.auto.common.MoreTypes.asDeclared; -import static com.google.common.base.Preconditions.checkState; -import static com.squareup.javapoet.MethodSpec.constructorBuilder; -import static com.squareup.javapoet.MethodSpec.methodBuilder; -import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; -import static dagger.internal.codegen.binding.ComponentCreatorKind.BUILDER; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; -import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; -import static dagger.internal.codegen.javapoet.CodeBlocks.parameterNames; -import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.BUILDER_METHOD; -import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.CANCELLATION_LISTENER_METHOD; -import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.COMPONENT_METHOD; -import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.CONSTRUCTOR; -import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.INITIALIZE_METHOD; -import static dagger.internal.codegen.writing.ComponentImplementation.TypeSpecKind.COMPONENT_CREATOR; -import static dagger.internal.codegen.writing.ComponentImplementation.TypeSpecKind.SUBCOMPONENT; -import static dagger.producers.CancellationPolicy.Propagation.PROPAGATE; -import static javax.lang.model.element.Modifier.FINAL; -import static javax.lang.model.element.Modifier.PRIVATE; -import static javax.lang.model.element.Modifier.PUBLIC; -import static javax.lang.model.element.Modifier.STATIC; - -import com.google.auto.common.MoreTypes; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimaps; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterSpec; -import com.squareup.javapoet.TypeSpec; -import dagger.internal.Preconditions; -import dagger.internal.codegen.binding.BindingGraph; -import dagger.internal.codegen.binding.ComponentCreatorDescriptor; -import dagger.internal.codegen.binding.ComponentCreatorKind; -import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; -import dagger.internal.codegen.binding.ComponentRequirement; -import dagger.internal.codegen.binding.FrameworkType; -import dagger.internal.codegen.javapoet.AnnotationSpecs; -import dagger.internal.codegen.javapoet.CodeBlocks; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.internal.codegen.writing.ComponentBindingExpressions; -import dagger.internal.codegen.writing.ComponentCreatorImplementation; -import dagger.internal.codegen.writing.ComponentImplementation; -import dagger.internal.codegen.writing.ComponentRequirementExpressions; -import dagger.internal.codegen.writing.ParentComponent; -import dagger.model.Key; -import dagger.producers.internal.CancellationListener; -import dagger.producers.internal.Producers; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import javax.inject.Inject; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.DeclaredType; - -/** A builder of {@link ComponentImplementation}s. */ -// This only needs to be public because it's referenced in an entry point. -public final class ComponentImplementationBuilder { - private static final String MAY_INTERRUPT_IF_RUNNING = "mayInterruptIfRunning"; - - /** - * How many statements per {@code initialize()} or {@code onProducerFutureCancelled()} method - * before they get partitioned. - */ - private static final int STATEMENTS_PER_METHOD = 100; - - private static final String CANCELLATION_LISTENER_METHOD_NAME = "onProducerFutureCancelled"; - - private final Optional<ComponentImplementationBuilder> parent; - private final BindingGraph graph; - private final ComponentBindingExpressions bindingExpressions; - private final ComponentRequirementExpressions componentRequirementExpressions; - private final ComponentImplementation componentImplementation; - private final ComponentCreatorImplementationFactory componentCreatorImplementationFactory; - private final TopLevelImplementationComponent topLevelImplementationComponent; - private final DaggerTypes types; - private final DaggerElements elements; - private final KotlinMetadataUtil metadataUtil; - private boolean done; - - @Inject - ComponentImplementationBuilder( - @ParentComponent Optional<ComponentImplementationBuilder> parent, - BindingGraph graph, - ComponentBindingExpressions bindingExpressions, - ComponentRequirementExpressions componentRequirementExpressions, - ComponentImplementation componentImplementation, - ComponentCreatorImplementationFactory componentCreatorImplementationFactory, - TopLevelImplementationComponent topLevelImplementationComponent, - DaggerTypes types, - DaggerElements elements, - KotlinMetadataUtil metadataUtil) { - this.parent = parent; - this.graph = graph; - this.bindingExpressions = bindingExpressions; - this.componentRequirementExpressions = componentRequirementExpressions; - this.componentImplementation = componentImplementation; - this.componentCreatorImplementationFactory = componentCreatorImplementationFactory; - this.types = types; - this.elements = elements; - this.topLevelImplementationComponent = topLevelImplementationComponent; - this.metadataUtil = metadataUtil; - } - - /** - * Returns a {@link ComponentImplementation} for this component. This is only intended to be - * called once (and will throw on successive invocations). If the component must be regenerated, - * use a new instance. - */ - ComponentImplementation build() { - checkState( - !done, - "ComponentImplementationBuilder has already built the ComponentImplementation for [%s].", - componentImplementation.name()); - setSupertype(); - - componentCreatorImplementationFactory.create() - .map(ComponentCreatorImplementation::spec) - .ifPresent(this::addCreatorClass); - - elements - .getLocalAndInheritedMethods(graph.componentTypeElement()) - .forEach(method -> componentImplementation.claimMethodName(method.getSimpleName())); - - addFactoryMethods(); - addInterfaceMethods(); - addChildComponents(); - - addConstructorAndInitializationMethods(); - - if (graph.componentDescriptor().isProduction()) { - addCancellationListenerImplementation(); - } - - done = true; - return componentImplementation; - } - - /** Set the supertype for this generated class. */ - private void setSupertype() { - componentImplementation.addSupertype(graph.componentTypeElement()); - } - - private void addCreatorClass(TypeSpec creator) { - if (parent.isPresent()) { - // In an inner implementation of a subcomponent the creator is a peer class. - parent.get().componentImplementation.addType(SUBCOMPONENT, creator); - } else { - componentImplementation.addType(COMPONENT_CREATOR, creator); - } - } - - private void addFactoryMethods() { - if (parent.isPresent()) { - graph.factoryMethod().ifPresent(this::createSubcomponentFactoryMethod); - } else { - createRootComponentFactoryMethod(); - } - } - - private void addInterfaceMethods() { - // Each component method may have been declared by several supertypes. We want to implement - // only one method for each distinct signature. - ImmutableListMultimap<MethodSignature, ComponentMethodDescriptor> componentMethodsBySignature = - Multimaps.index(graph.componentDescriptor().entryPointMethods(), this::getMethodSignature); - for (List<ComponentMethodDescriptor> methodsWithSameSignature : - Multimaps.asMap(componentMethodsBySignature).values()) { - ComponentMethodDescriptor anyOneMethod = methodsWithSameSignature.stream().findAny().get(); - MethodSpec methodSpec = bindingExpressions.getComponentMethod(anyOneMethod); - - componentImplementation.addMethod(COMPONENT_METHOD, methodSpec); - } - } - - private void addCancellationListenerImplementation() { - componentImplementation.addSupertype(elements.getTypeElement(CancellationListener.class)); - componentImplementation.claimMethodName(CANCELLATION_LISTENER_METHOD_NAME); - - ImmutableList<ParameterSpec> parameters = - ImmutableList.of(ParameterSpec.builder(boolean.class, MAY_INTERRUPT_IF_RUNNING).build()); - - MethodSpec.Builder methodBuilder = - methodBuilder(CANCELLATION_LISTENER_METHOD_NAME) - .addModifiers(PUBLIC) - .addAnnotation(Override.class) - .addParameters(parameters); - - ImmutableList<CodeBlock> cancellationStatements = cancellationStatements(); - - if (cancellationStatements.size() < STATEMENTS_PER_METHOD) { - methodBuilder.addCode(CodeBlocks.concat(cancellationStatements)).build(); - } else { - ImmutableList<MethodSpec> cancelProducersMethods = - createPartitionedMethods( - "cancelProducers", - parameters, - cancellationStatements, - methodName -> methodBuilder(methodName).addModifiers(PRIVATE)); - for (MethodSpec cancelProducersMethod : cancelProducersMethods) { - methodBuilder.addStatement("$N($L)", cancelProducersMethod, MAY_INTERRUPT_IF_RUNNING); - componentImplementation.addMethod(CANCELLATION_LISTENER_METHOD, cancelProducersMethod); - } - } - - cancelParentStatement().ifPresent(methodBuilder::addCode); - - componentImplementation.addMethod(CANCELLATION_LISTENER_METHOD, methodBuilder.build()); - } - - private ImmutableList<CodeBlock> cancellationStatements() { - // Reversing should order cancellations starting from entry points and going down to leaves - // rather than the other way around. This shouldn't really matter but seems *slightly* - // preferable because: - // When a future that another future depends on is cancelled, that cancellation will propagate - // up the future graph toward the entry point. Cancelling in reverse order should ensure that - // everything that depends on a particular node has already been cancelled when that node is - // cancelled, so there's no need to propagate. Otherwise, when we cancel a leaf node, it might - // propagate through most of the graph, making most of the cancel calls that follow in the - // onProducerFutureCancelled method do nothing. - ImmutableList<Key> cancellationKeys = - componentImplementation.getCancellableProducerKeys().reverse(); - - ImmutableList.Builder<CodeBlock> cancellationStatements = ImmutableList.builder(); - for (Key cancellationKey : cancellationKeys) { - cancellationStatements.add( - CodeBlock.of( - "$T.cancel($L, $N);", - Producers.class, - bindingExpressions - .getDependencyExpression( - bindingRequest(cancellationKey, FrameworkType.PRODUCER_NODE), - componentImplementation.name()) - .codeBlock(), - MAY_INTERRUPT_IF_RUNNING)); - } - return cancellationStatements.build(); - } - - private Optional<CodeBlock> cancelParentStatement() { - if (!shouldPropagateCancellationToParent()) { - return Optional.empty(); - } - return Optional.of( - CodeBlock.builder() - .addStatement( - "$T.this.$N($N)", - parent.get().componentImplementation.name(), - CANCELLATION_LISTENER_METHOD_NAME, - MAY_INTERRUPT_IF_RUNNING) - .build()); - } - - private boolean shouldPropagateCancellationToParent() { - return parent.isPresent() - && parent - .get() - .componentImplementation - .componentDescriptor() - .cancellationPolicy() - .map(policy -> policy.fromSubcomponents().equals(PROPAGATE)) - .orElse(false); - } - - private MethodSignature getMethodSignature(ComponentMethodDescriptor method) { - return MethodSignature.forComponentMethod( - method, MoreTypes.asDeclared(graph.componentTypeElement().asType()), types); - } - - private void addChildComponents() { - for (BindingGraph subgraph : graph.subgraphs()) { - componentImplementation.addType(SUBCOMPONENT, childComponent(subgraph)); - } - } - - private TypeSpec childComponent(BindingGraph childGraph) { - return topLevelImplementationComponent - .currentImplementationSubcomponentBuilder() - .componentImplementation(subcomponent(childGraph)) - .bindingGraph(childGraph) - .parentBuilder(Optional.of(this)) - .parentBindingExpressions(Optional.of(bindingExpressions)) - .parentRequirementExpressions(Optional.of(componentRequirementExpressions)) - .build() - .componentImplementationBuilder() - .build() - .generate() - .build(); - } - - /** Creates an inner subcomponent implementation. */ - private ComponentImplementation subcomponent(BindingGraph childGraph) { - return componentImplementation.childComponentImplementation(childGraph); - } - - /** Creates and adds the constructor and methods needed for initializing the component. */ - private void addConstructorAndInitializationMethods() { - MethodSpec.Builder constructor = constructorBuilder().addModifiers(PRIVATE); - implementInitializationMethod(constructor, initializationParameters()); - componentImplementation.addMethod(CONSTRUCTOR, constructor.build()); - } - - /** Adds parameters and code to the given {@code initializationMethod}. */ - private void implementInitializationMethod( - MethodSpec.Builder initializationMethod, - ImmutableMap<ComponentRequirement, ParameterSpec> initializationParameters) { - initializationMethod.addParameters(initializationParameters.values()); - initializationMethod.addCode( - CodeBlocks.concat(componentImplementation.getComponentRequirementInitializations())); - addInitializeMethods(initializationMethod, initializationParameters.values().asList()); - } - - /** - * Adds any necessary {@code initialize} methods to the component and adds calls to them to the - * given {@code callingMethod}. - */ - private void addInitializeMethods( - MethodSpec.Builder callingMethod, ImmutableList<ParameterSpec> parameters) { - // TODO(cgdecker): It's not the case that each initialize() method has need for all of the - // given parameters. In some cases, those parameters may have already been assigned to fields - // which could be referenced instead. In other cases, an initialize method may just not need - // some of the parameters because the set of initializations in that partition does not - // include any reference to them. Right now, the Dagger code has no way of getting that - // information because, among other things, componentImplementation.getImplementations() just - // returns a bunch of CodeBlocks with no semantic information. Additionally, we may not know - // yet whether a field will end up needing to be created for a specific requirement, and we - // don't want to create a field that ends up only being used during initialization. - CodeBlock args = parameterNames(parameters); - ImmutableList<MethodSpec> methods = - createPartitionedMethods( - "initialize", - makeFinal(parameters), - componentImplementation.getInitializations(), - methodName -> - methodBuilder(methodName) - .addModifiers(PRIVATE) - /* TODO(gak): Strictly speaking, we only need the suppression here if we are - * also initializing a raw field in this method, but the structure of this - * code makes it awkward to pass that bit through. This will be cleaned up - * when we no longer separate fields and initialization as we do now. */ - .addAnnotation(AnnotationSpecs.suppressWarnings(UNCHECKED))); - for (MethodSpec method : methods) { - callingMethod.addStatement("$N($L)", method, args); - componentImplementation.addMethod(INITIALIZE_METHOD, method); - } - } - - /** - * Creates one or more methods, all taking the given {@code parameters}, which partition the given - * list of {@code statements} among themselves such that no method has more than {@code - * STATEMENTS_PER_METHOD} statements in it and such that the returned methods, if called in order, - * will execute the {@code statements} in the given order. - */ - private ImmutableList<MethodSpec> createPartitionedMethods( - String methodName, - Iterable<ParameterSpec> parameters, - List<CodeBlock> statements, - Function<String, MethodSpec.Builder> methodBuilderCreator) { - return Lists.partition(statements, STATEMENTS_PER_METHOD).stream() - .map( - partition -> - methodBuilderCreator - .apply(componentImplementation.getUniqueMethodName(methodName)) - .addParameters(parameters) - .addCode(CodeBlocks.concat(partition)) - .build()) - .collect(toImmutableList()); - } - - /** Returns the given parameters with a final modifier added. */ - private final ImmutableList<ParameterSpec> makeFinal(Collection<ParameterSpec> parameters) { - return parameters.stream() - .map(param -> param.toBuilder().addModifiers(FINAL).build()) - .collect(toImmutableList()); - } - - /** - * Returns the parameters for the constructor as a map from the requirement the parameter fulfills - * to the spec for the parameter. - */ - private final ImmutableMap<ComponentRequirement, ParameterSpec> initializationParameters() { - Map<ComponentRequirement, ParameterSpec> parameters; - if (componentImplementation.componentDescriptor().hasCreator()) { - parameters = Maps.toMap(graph.componentRequirements(), ComponentRequirement::toParameterSpec); - } else if (graph.factoryMethod().isPresent()) { - parameters = getFactoryMethodParameters(graph); - } else { - throw new AssertionError( - "Expected either a component creator or factory method but found neither."); - } - - return renameParameters(parameters); - } - - /** - * Renames the given parameters to guarantee their names do not conflict with fields in the - * component to ensure that a parameter is never referenced where a reference to a field was - * intended. - */ - // TODO(cgdecker): This is a bit kludgy; it would be preferable to either qualify the field - // references with "this." or "super." when needed to disambiguate between field and parameter, - // but that would require more context than is currently available when the code referencing a - // field is generated. - private ImmutableMap<ComponentRequirement, ParameterSpec> renameParameters( - Map<ComponentRequirement, ParameterSpec> parameters) { - return ImmutableMap.copyOf( - Maps.transformEntries( - parameters, - (requirement, parameter) -> - renameParameter( - parameter, - componentImplementation.getParameterName(requirement, parameter.name)))); - } - - private ParameterSpec renameParameter(ParameterSpec parameter, String newName) { - return ParameterSpec.builder(parameter.type, newName) - .addAnnotations(parameter.annotations) - .addModifiers(parameter.modifiers) - .build(); - } - - private void createRootComponentFactoryMethod() { - checkState(!parent.isPresent()); - // Top-level components have a static method that returns a builder or factory for the - // component. If the user defined a @Component.Builder or @Component.Factory, an - // implementation of their type is returned. Otherwise, an autogenerated Builder type is - // returned. - // TODO(cgdecker): Replace this abomination with a small class? - // Better yet, change things so that an autogenerated builder type has a descriptor of sorts - // just like a user-defined creator type. - ComponentCreatorKind creatorKind; - ClassName creatorType; - String factoryMethodName; - boolean noArgFactoryMethod; - Optional<ComponentCreatorDescriptor> creatorDescriptor = - graph.componentDescriptor().creatorDescriptor(); - if (creatorDescriptor.isPresent()) { - ComponentCreatorDescriptor descriptor = creatorDescriptor.get(); - creatorKind = descriptor.kind(); - creatorType = ClassName.get(descriptor.typeElement()); - factoryMethodName = descriptor.factoryMethod().getSimpleName().toString(); - noArgFactoryMethod = descriptor.factoryParameters().isEmpty(); - } else { - creatorKind = BUILDER; - creatorType = componentImplementation.getCreatorName(); - factoryMethodName = "build"; - noArgFactoryMethod = true; - } - - MethodSpec creatorFactoryMethod = - methodBuilder(creatorKind.methodName()) - .addModifiers(PUBLIC, STATIC) - .returns(creatorType) - .addStatement("return new $T()", componentImplementation.getCreatorName()) - .build(); - componentImplementation.addMethod(BUILDER_METHOD, creatorFactoryMethod); - if (noArgFactoryMethod && canInstantiateAllRequirements()) { - componentImplementation.addMethod( - BUILDER_METHOD, - methodBuilder("create") - .returns(ClassName.get(graph.componentTypeElement())) - .addModifiers(PUBLIC, STATIC) - .addStatement("return new $L().$L()", creatorKind.typeName(), factoryMethodName) - .build()); - } - } - - /** {@code true} if all of the graph's required dependencies can be automatically constructed */ - private boolean canInstantiateAllRequirements() { - return !Iterables.any( - graph.componentRequirements(), - dependency -> dependency.requiresAPassedInstance(elements, metadataUtil)); - } - - private void createSubcomponentFactoryMethod(ExecutableElement factoryMethod) { - checkState(parent.isPresent()); - Collection<ParameterSpec> params = getFactoryMethodParameters(graph).values(); - MethodSpec.Builder method = MethodSpec.overriding(factoryMethod, parentType(), types); - params.forEach( - param -> method.addStatement("$T.checkNotNull($N)", Preconditions.class, param)); - method.addStatement( - "return new $T($L)", componentImplementation.name(), parameterNames(params)); - - parent.get().componentImplementation.addMethod(COMPONENT_METHOD, method.build()); - } - - private DeclaredType parentType() { - return asDeclared(parent.get().graph.componentTypeElement().asType()); - } - /** - * Returns the map of {@link ComponentRequirement}s to {@link ParameterSpec}s for the given - * graph's factory method. - */ - private static Map<ComponentRequirement, ParameterSpec> getFactoryMethodParameters( - BindingGraph graph) { - return Maps.transformValues(graph.factoryMethodParameters(), ParameterSpec::get); - } -} diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java b/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java deleted file mode 100644 index 0d29b8693..000000000 --- a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2015 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.componentgenerator; - -import static dagger.internal.codegen.componentgenerator.ComponentGenerator.componentName; - -import dagger.internal.codegen.binding.BindingGraph; -import dagger.internal.codegen.binding.KeyFactory; -import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.internal.codegen.writing.ComponentImplementation; -import dagger.internal.codegen.writing.SubcomponentNames; -import java.util.Optional; -import javax.inject.Inject; -import javax.inject.Singleton; - -/** Factory for {@link ComponentImplementation}s. */ -@Singleton -final class ComponentImplementationFactory { - private final KeyFactory keyFactory; - private final CompilerOptions compilerOptions; - private final TopLevelImplementationComponent.Builder topLevelImplementationComponentBuilder; - - @Inject - ComponentImplementationFactory( - KeyFactory keyFactory, - CompilerOptions compilerOptions, - TopLevelImplementationComponent.Builder topLevelImplementationComponentBuilder) { - this.keyFactory = keyFactory; - this.compilerOptions = compilerOptions; - this.topLevelImplementationComponentBuilder = topLevelImplementationComponentBuilder; - } - - /** - * Returns a top-level (non-nested) component implementation for a binding graph. - */ - ComponentImplementation createComponentImplementation(BindingGraph bindingGraph) { - ComponentImplementation componentImplementation = - ComponentImplementation.topLevelComponentImplementation( - bindingGraph, - componentName(bindingGraph.componentTypeElement()), - new SubcomponentNames(bindingGraph, keyFactory), - compilerOptions); - - // TODO(dpb): explore using optional bindings for the "parent" bindings - return topLevelImplementationComponentBuilder - .topLevelComponent(componentImplementation) - .build() - .currentImplementationSubcomponentBuilder() - .componentImplementation(componentImplementation) - .bindingGraph(bindingGraph) - .parentBuilder(Optional.empty()) - .parentBindingExpressions(Optional.empty()) - .parentRequirementExpressions(Optional.empty()) - .build() - .componentImplementationBuilder() - .build(); - } -} diff --git a/java/dagger/internal/codegen/componentgenerator/CurrentImplementationSubcomponent.java b/java/dagger/internal/codegen/componentgenerator/CurrentImplementationSubcomponent.java index 558437239..4a193ce41 100644 --- a/java/dagger/internal/codegen/componentgenerator/CurrentImplementationSubcomponent.java +++ b/java/dagger/internal/codegen/componentgenerator/CurrentImplementationSubcomponent.java @@ -17,41 +17,64 @@ package dagger.internal.codegen.componentgenerator; import dagger.BindsInstance; +import dagger.Module; +import dagger.Provides; import dagger.Subcomponent; import dagger.internal.codegen.binding.BindingGraph; -import dagger.internal.codegen.writing.ComponentBindingExpressions; import dagger.internal.codegen.writing.ComponentImplementation; +import dagger.internal.codegen.writing.ComponentImplementation.ChildComponentImplementationFactory; +import dagger.internal.codegen.writing.ComponentRequestRepresentations; import dagger.internal.codegen.writing.ComponentRequirementExpressions; import dagger.internal.codegen.writing.ParentComponent; import dagger.internal.codegen.writing.PerComponentImplementation; import java.util.Optional; +import javax.inject.Provider; /** * A subcomponent that injects all objects that are responsible for creating a single {@link * ComponentImplementation} instance. Each child {@link ComponentImplementation} will have its own * instance of {@link CurrentImplementationSubcomponent}. */ -@Subcomponent +@Subcomponent( + modules = CurrentImplementationSubcomponent.ChildComponentImplementationFactoryModule.class) @PerComponentImplementation // This only needs to be public because the type is referenced by generated component. public interface CurrentImplementationSubcomponent { - ComponentImplementationBuilder componentImplementationBuilder(); + ComponentImplementation componentImplementation(); + + /** A module to bind the {@link ChildComponentImplementationFactory}. */ + @Module + interface ChildComponentImplementationFactoryModule { + @Provides + static ChildComponentImplementationFactory provideChildComponentImplementationFactory( + CurrentImplementationSubcomponent.Builder currentImplementationSubcomponentBuilder, + Provider<ComponentImplementation> componentImplementation, + Provider<ComponentRequestRepresentations> componentRequestRepresentations, + Provider<ComponentRequirementExpressions> componentRequirementExpressions) { + return childGraph -> + currentImplementationSubcomponentBuilder + .bindingGraph(childGraph) + .parentImplementation(Optional.of(componentImplementation.get())) + .parentRequestRepresentations(Optional.of(componentRequestRepresentations.get())) + .parentRequirementExpressions(Optional.of(componentRequirementExpressions.get())) + .build() + .componentImplementation(); + } + } /** Returns the builder for {@link CurrentImplementationSubcomponent}. */ @Subcomponent.Builder interface Builder { @BindsInstance - Builder componentImplementation(ComponentImplementation componentImplementation); - - @BindsInstance Builder bindingGraph(BindingGraph bindingGraph); @BindsInstance - Builder parentBuilder(@ParentComponent Optional<ComponentImplementationBuilder> parentBuilder); + Builder parentImplementation( + @ParentComponent Optional<ComponentImplementation> parentImplementation); @BindsInstance - Builder parentBindingExpressions( - @ParentComponent Optional<ComponentBindingExpressions> parentBindingExpressions); + Builder parentRequestRepresentations( + @ParentComponent Optional<ComponentRequestRepresentations> parentRequestRepresentations); @BindsInstance Builder parentRequirementExpressions( diff --git a/java/dagger/internal/codegen/componentgenerator/MethodSignature.java b/java/dagger/internal/codegen/componentgenerator/MethodSignature.java deleted file mode 100644 index 99b05a44a..000000000 --- a/java/dagger/internal/codegen/componentgenerator/MethodSignature.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2014 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.componentgenerator; - -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; - -import com.google.auto.common.MoreTypes; -import com.google.auto.value.AutoValue; -import com.google.common.base.Equivalence; -import com.google.common.collect.ImmutableList; -import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; -import dagger.internal.codegen.langmodel.DaggerTypes; -import java.util.List; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.TypeMirror; - -/** A class that defines proper {@code equals} and {@code hashcode} for a method signature. */ -@AutoValue -abstract class MethodSignature { - - abstract String name(); - - abstract ImmutableList<? extends Equivalence.Wrapper<? extends TypeMirror>> parameterTypes(); - - abstract ImmutableList<? extends Equivalence.Wrapper<? extends TypeMirror>> thrownTypes(); - - static MethodSignature forComponentMethod( - ComponentMethodDescriptor componentMethod, DeclaredType componentType, DaggerTypes types) { - ExecutableType methodType = - MoreTypes.asExecutable(types.asMemberOf(componentType, componentMethod.methodElement())); - return new AutoValue_MethodSignature( - componentMethod.methodElement().getSimpleName().toString(), - wrapInEquivalence(methodType.getParameterTypes()), - wrapInEquivalence(methodType.getThrownTypes())); - } - - private static ImmutableList<? extends Equivalence.Wrapper<? extends TypeMirror>> - wrapInEquivalence(List<? extends TypeMirror> types) { - return types.stream().map(MoreTypes.equivalence()::wrap).collect(toImmutableList()); - } -} diff --git a/java/dagger/internal/codegen/componentgenerator/TopLevelImplementationComponent.java b/java/dagger/internal/codegen/componentgenerator/TopLevelImplementationComponent.java index 7919e471d..79952c9fa 100644 --- a/java/dagger/internal/codegen/componentgenerator/TopLevelImplementationComponent.java +++ b/java/dagger/internal/codegen/componentgenerator/TopLevelImplementationComponent.java @@ -18,6 +18,7 @@ package dagger.internal.codegen.componentgenerator; import dagger.BindsInstance; import dagger.Subcomponent; +import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.writing.ComponentImplementation; import dagger.internal.codegen.writing.PerGeneratedFile; import dagger.internal.codegen.writing.TopLevel; @@ -33,10 +34,8 @@ public interface TopLevelImplementationComponent { CurrentImplementationSubcomponent.Builder currentImplementationSubcomponentBuilder(); /** Returns the builder for {@link TopLevelImplementationComponent}. */ - @Subcomponent.Builder - interface Builder { - @BindsInstance - Builder topLevelComponent(@TopLevel ComponentImplementation topLevelImplementation); - TopLevelImplementationComponent build(); + @Subcomponent.Factory + interface Factory { + TopLevelImplementationComponent create(@BindsInstance @TopLevel BindingGraph bindingGraph); } } diff --git a/java/dagger/internal/codegen/extension/BUILD b/java/dagger/internal/codegen/extension/BUILD index 468a68583..6bcb605de 100644 --- a/java/dagger/internal/codegen/extension/BUILD +++ b/java/dagger/internal/codegen/extension/BUILD @@ -25,9 +25,9 @@ java_library( srcs = glob(["*.java"]), tags = ["maven:merged"], deps = [ - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:graph", - "@google_bazel_common//third_party/java/jsr305_annotations", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/graph", + "//third_party/java/jsr305_annotations", ], ) diff --git a/java/dagger/internal/codegen/javac/BUILD b/java/dagger/internal/codegen/javac/BUILD index b8cb37c8a..ad2f91be9 100644 --- a/java/dagger/internal/codegen/javac/BUILD +++ b/java/dagger/internal/codegen/javac/BUILD @@ -32,6 +32,8 @@ java_library( "//java/dagger/internal/codegen/binding", "//java/dagger/internal/codegen/compileroption", "//java/dagger/internal/codegen/langmodel", + "//java/dagger/internal/codegen/xprocessing", + "//third_party/java/guava/collect", ], ) diff --git a/java/dagger/internal/codegen/compileroption/JavacPluginCompilerOptions.java b/java/dagger/internal/codegen/javac/JavacPluginCompilerOptions.java index a86cc1b7c..c12d86568 100644 --- a/java/dagger/internal/codegen/compileroption/JavacPluginCompilerOptions.java +++ b/java/dagger/internal/codegen/javac/JavacPluginCompilerOptions.java @@ -14,17 +14,19 @@ * limitations under the License. */ -package dagger.internal.codegen.compileroption; +package dagger.internal.codegen.javac; import static dagger.internal.codegen.compileroption.ValidationType.NONE; import static javax.tools.Diagnostic.Kind.NOTE; +import androidx.room.compiler.processing.XTypeElement; +import dagger.internal.codegen.compileroption.CompilerOptions; +import dagger.internal.codegen.compileroption.ValidationType; import javax.inject.Inject; -import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; /** {@link CompilerOptions} for Javac plugins (e.g. for Dagger statistics or Kythe). */ -public final class JavacPluginCompilerOptions extends CompilerOptions { +final class JavacPluginCompilerOptions extends CompilerOptions { @Inject JavacPluginCompilerOptions() {} @@ -35,7 +37,12 @@ public final class JavacPluginCompilerOptions extends CompilerOptions { } @Override - public boolean fastInit(TypeElement element) { + public boolean experimentalMergedMode(XTypeElement element) { + return false; + } + + @Override + public boolean fastInit(XTypeElement element) { return false; } @@ -65,6 +72,11 @@ public final class JavacPluginCompilerOptions extends CompilerOptions { } @Override + public boolean includeStacktraceWithDeferredErrorMessages() { + return false; + } + + @Override public boolean ignorePrivateAndStaticInjectionForComponent() { return false; } @@ -95,7 +107,7 @@ public final class JavacPluginCompilerOptions extends CompilerOptions { } @Override - public boolean pluginsVisitFullBindingGraphs(TypeElement element) { + public boolean pluginsVisitFullBindingGraphs(XTypeElement element) { return false; } @@ -118,4 +130,9 @@ public final class JavacPluginCompilerOptions extends CompilerOptions { public boolean strictMultibindingValidation() { return false; } + + @Override + public boolean strictSuperficialValidation() { + return true; + } } diff --git a/java/dagger/internal/codegen/javac/JavacPluginModule.java b/java/dagger/internal/codegen/javac/JavacPluginModule.java index 214191a50..aca9f238c 100644 --- a/java/dagger/internal/codegen/javac/JavacPluginModule.java +++ b/java/dagger/internal/codegen/javac/JavacPluginModule.java @@ -16,6 +16,9 @@ package dagger.internal.codegen.javac; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.compat.XConverters; import com.sun.tools.javac.model.JavacElements; import com.sun.tools.javac.model.JavacTypes; import com.sun.tools.javac.util.Context; @@ -25,68 +28,54 @@ import dagger.Provides; import dagger.internal.codegen.binding.BindingGraphFactory; import dagger.internal.codegen.binding.ComponentDescriptorFactory; import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.internal.codegen.compileroption.JavacPluginCompilerOptions; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import javax.annotation.processing.Messager; -import javax.inject.Inject; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.util.Elements; // ALLOW_TYPES_ELEMENTS +import javax.lang.model.util.Types; // ALLOW_TYPES_ELEMENTS /** * A module that provides a {@link BindingGraphFactory} and {@link ComponentDescriptorFactory} for * use in {@code javac} plugins. Requires a binding for the {@code javac} {@link Context}. */ -@Module -public abstract class JavacPluginModule { - @Binds - abstract CompilerOptions compilerOptions(JavacPluginCompilerOptions compilerOptions); - - @Binds - abstract Messager messager(NullMessager nullMessager); - - static final class NullMessager implements Messager { - - @Inject - NullMessager() {} - - @Override - public void printMessage(Diagnostic.Kind kind, CharSequence charSequence) {} +@Module(includes = JavacPluginModule.BindsModule.class) +public final class JavacPluginModule { + @Module + interface BindsModule { + @Binds + CompilerOptions compilerOptions(JavacPluginCompilerOptions compilerOptions); + } - @Override - public void printMessage(Diagnostic.Kind kind, CharSequence charSequence, Element element) {} + private final XProcessingEnv processingEnv; - @Override - public void printMessage( - Diagnostic.Kind kind, - CharSequence charSequence, - Element element, - AnnotationMirror annotationMirror) {} + public JavacPluginModule(Context context) { + this(JavacElements.instance(context), JavacTypes.instance(context)); + } - @Override - public void printMessage( - Diagnostic.Kind kind, - CharSequence charSequence, - Element element, - AnnotationMirror annotationMirror, - AnnotationValue annotationValue) {} + public JavacPluginModule(Elements elements, Types types) { + this.processingEnv = + XProcessingEnv.create(new JavacPluginProcessingEnvironment(elements, types)); } @Provides - static DaggerElements daggerElements(Context javaContext) { - return new DaggerElements( - JavacElements.instance(javaContext), JavacTypes.instance(javaContext)); + XMessager messager() { + return processingEnv.getMessager(); } @Provides - static DaggerTypes daggerTypes(Context javaContext, DaggerElements elements) { - return new DaggerTypes(JavacTypes.instance(javaContext), elements); + DaggerElements daggerElements() { + ProcessingEnvironment env = XConverters.toJavac(processingEnv); + return new DaggerElements(env.getElementUtils(), env.getTypeUtils()); // ALLOW_TYPES_ELEMENTS } - @Binds abstract Types types(DaggerTypes daggerTypes); + @Provides + DaggerTypes daggerTypes(DaggerElements elements) { + ProcessingEnvironment env = XConverters.toJavac(processingEnv); + return new DaggerTypes(env.getTypeUtils(), elements); // ALLOW_TYPES_ELEMENTS + } - private JavacPluginModule() {} + @Provides + XProcessingEnv xProcessingEnv() { + return processingEnv; + } } diff --git a/java/dagger/internal/codegen/javac/JavacPluginProcessingEnvironment.java b/java/dagger/internal/codegen/javac/JavacPluginProcessingEnvironment.java new file mode 100644 index 000000000..3f6a575fb --- /dev/null +++ b/java/dagger/internal/codegen/javac/JavacPluginProcessingEnvironment.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2021 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.javac; + +import com.google.common.collect.ImmutableMap; +import java.util.Locale; +import javax.annotation.processing.Filer; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.util.Elements; // ALLOW_TYPES_ELEMENTS since in interface API +import javax.lang.model.util.Types; // ALLOW_TYPES_ELEMENTS since in interface API +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.JavaFileManager.Location; +import javax.tools.JavaFileObject; + +/** + * An implementation of {@link ProcessingEnvironment} that runs in a javac plugin environment. + * + * <p>This environment runs after the classes are already compiled, so parts of the {@link + * ProcessingEnvironment} API like {@link Filer}, {@link Messager} don't make sense in this + * environment, so they've been replaced with throwing and no-op implementations respectively. + */ +final class JavacPluginProcessingEnvironment implements ProcessingEnvironment { + private final Elements elements; + private final Types types; + private final Filer filer = new ThrowingFiler(); + private final Messager messager = new NoopMessager(); + + JavacPluginProcessingEnvironment(Elements elements, Types types) { + this.elements = elements; + this.types = types; + } + + @Override + public Elements getElementUtils() { + return elements; + } + + @Override + public Types getTypeUtils() { + return types; + } + + @Override + public Filer getFiler() { + return filer; + } + + @Override + public Locale getLocale() { + // Null means there's no locale in effect + return null; + } + + @Override + public Messager getMessager() { + return messager; + } + + @Override + public ImmutableMap<String, String> getOptions() { + // TODO(erichang): You can technically parse options out of the context, but it is internal + // implementation and unclear that any of the tools will ever be passing an option. + return ImmutableMap.of(); + } + + @Override + public SourceVersion getSourceVersion() { + // This source version doesn't really matter because it is saying what version generated code + // should adhere to, which there shouldn't be any because the Filer doesn't work. + return SourceVersion.latestSupported(); + } + + private static final class ThrowingFiler implements Filer { + @Override + public JavaFileObject createClassFile(CharSequence name, Element... originatingElements) { + throw new UnsupportedOperationException("Cannot use a Filer in this context"); + } + + @Override + public FileObject createResource( + Location location, + CharSequence pkg, + CharSequence relativeName, + Element... originatingElements) { + throw new UnsupportedOperationException("Cannot use a Filer in this context"); + } + + @Override + public JavaFileObject createSourceFile(CharSequence name, Element... originatingElements) { + throw new UnsupportedOperationException("Cannot use a Filer in this context"); + } + + @Override + public FileObject getResource(Location location, CharSequence pkg, CharSequence relativeName) { + throw new UnsupportedOperationException("Cannot use a Filer in this context"); + } + } + + private static final class NoopMessager implements Messager { + @Override + public void printMessage(Diagnostic.Kind kind, CharSequence charSequence) {} + + @Override + public void printMessage(Diagnostic.Kind kind, CharSequence charSequence, Element element) {} + + @Override + public void printMessage( + Diagnostic.Kind kind, + CharSequence charSequence, + Element element, + AnnotationMirror annotationMirror) {} + + @Override + public void printMessage( + Diagnostic.Kind kind, + CharSequence charSequence, + Element element, + AnnotationMirror annotationMirror, + AnnotationValue annotationValue) {} + } +} diff --git a/java/dagger/internal/codegen/javapoet/BUILD b/java/dagger/internal/codegen/javapoet/BUILD index ddb9f8802..4dc44857f 100644 --- a/java/dagger/internal/codegen/javapoet/BUILD +++ b/java/dagger/internal/codegen/javapoet/BUILD @@ -25,12 +25,12 @@ java_library( plugins = ["//java/dagger/internal/codegen/bootstrap"], tags = ["maven:merged"], deps = [ - "//java/dagger:core", "//java/dagger/internal/codegen/langmodel", - "//java/dagger/internal/guava:collect", - "//java/dagger/producers", - "@google_bazel_common//third_party/java/error_prone:annotations", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//java/dagger/internal/codegen/xprocessing", + "//third_party/java/auto:common", + "//third_party/java/error_prone:annotations", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/internal/codegen/javapoet/CodeBlocks.java b/java/dagger/internal/codegen/javapoet/CodeBlocks.java index 3e9f75d8a..c0d7a7b1c 100644 --- a/java/dagger/internal/codegen/javapoet/CodeBlocks.java +++ b/java/dagger/internal/codegen/javapoet/CodeBlocks.java @@ -112,6 +112,11 @@ public final class CodeBlocks { } /** Returns {@code expression} cast to a type. */ + public static CodeBlock cast(CodeBlock expression, ClassName castTo) { + return CodeBlock.of("($T) $L", castTo, expression); + } + + /** Returns {@code expression} cast to a type. */ public static CodeBlock cast(CodeBlock expression, Class<?> castTo) { return CodeBlock.of("($T) $L", castTo, expression); } diff --git a/java/dagger/internal/codegen/javapoet/Expression.java b/java/dagger/internal/codegen/javapoet/Expression.java index b79c55c01..f7bfbb881 100644 --- a/java/dagger/internal/codegen/javapoet/Expression.java +++ b/java/dagger/internal/codegen/javapoet/Expression.java @@ -16,6 +16,9 @@ package dagger.internal.codegen.javapoet; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; + +import androidx.room.compiler.processing.XType; import com.google.auto.common.MoreTypes; import com.squareup.javapoet.CodeBlock; import dagger.internal.codegen.langmodel.DaggerTypes; @@ -45,6 +48,11 @@ public final class Expression { } /** Creates a new {@link Expression} with a {@link TypeMirror} and {@link CodeBlock}. */ + public static Expression create(XType type, CodeBlock expression) { + return create(toJavac(type), expression); + } + + /** Creates a new {@link Expression} with a {@link TypeMirror} and {@link CodeBlock}. */ public static Expression create(TypeMirror type, CodeBlock expression) { return new Expression(type, expression); } @@ -53,6 +61,14 @@ public final class Expression { * Creates a new {@link Expression} with a {@link TypeMirror}, {@linkplain CodeBlock#of(String, * Object[]) format, and arguments}. */ + public static Expression create(XType type, String format, Object... args) { + return create(toJavac(type), format, args); + } + + /** + * Creates a new {@link Expression} with a {@link TypeMirror}, {@linkplain CodeBlock#of(String, + * Object[]) format, and arguments}. + */ public static Expression create(TypeMirror type, String format, Object... args) { return create(type, CodeBlock.of(format, args)); } @@ -60,6 +76,13 @@ public final class Expression { /** Returns a new expression that casts the current expression to {@code newType}. */ // TODO(ronshapiro): consider overloads that take a Types and Elements and only cast if necessary, // or just embedding a Types/Elements instance in an Expression. + public Expression castTo(XType newType) { + return castTo(toJavac(newType)); + } + + /** Returns a new expression that casts the current expression to {@code newType}. */ + // TODO(ronshapiro): consider overloads that take a Types and Elements and only cast if necessary, + // or just embedding a Types/Elements instance in an Expression. public Expression castTo(TypeMirror newType) { return create(newType, "($T) $L", newType, codeBlock); } diff --git a/java/dagger/internal/codegen/javapoet/TypeNames.java b/java/dagger/internal/codegen/javapoet/TypeNames.java index 71f03f0e4..ae4145d66 100644 --- a/java/dagger/internal/codegen/javapoet/TypeNames.java +++ b/java/dagger/internal/codegen/javapoet/TypeNames.java @@ -16,79 +16,152 @@ package dagger.internal.codegen.javapoet; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; + import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; -import dagger.Lazy; -import dagger.MembersInjector; -import dagger.internal.DoubleCheck; -import dagger.internal.Factory; -import dagger.internal.InjectedFieldSignature; -import dagger.internal.InstanceFactory; -import dagger.internal.MapFactory; -import dagger.internal.MapProviderFactory; -import dagger.internal.MembersInjectors; -import dagger.internal.ProviderOfLazy; -import dagger.internal.SetFactory; -import dagger.internal.SingleCheck; -import dagger.producers.Produced; -import dagger.producers.Producer; -import dagger.producers.ProducerModule; -import dagger.producers.internal.AbstractProducer; -import dagger.producers.internal.DependencyMethodProducer; -import dagger.producers.internal.MapOfProducedProducer; -import dagger.producers.internal.MapOfProducerProducer; -import dagger.producers.internal.MapProducer; -import dagger.producers.internal.Producers; -import dagger.producers.internal.SetOfProducedProducer; -import dagger.producers.internal.SetProducer; -import dagger.producers.monitoring.ProducerToken; -import dagger.producers.monitoring.ProductionComponentMonitor; -import java.util.List; -import java.util.Set; -import javax.inject.Provider; +import javax.lang.model.type.TypeMirror; /** Common names and convenience methods for JavaPoet {@link TypeName} usage. */ public final class TypeNames { - public static final ClassName ABSTRACT_PRODUCER = ClassName.get(AbstractProducer.class); - public static final ClassName DEPENDENCY_METHOD_PRODUCER = - ClassName.get(DependencyMethodProducer.class); - public static final ClassName DOUBLE_CHECK = ClassName.get(DoubleCheck.class); - public static final ClassName FACTORY = ClassName.get(Factory.class); - public static final ClassName FUTURES = ClassName.get(Futures.class); + // Dagger Core classnames + public static final ClassName ASSISTED = ClassName.get("dagger.assisted", "Assisted"); + public static final ClassName ASSISTED_FACTORY = + ClassName.get("dagger.assisted", "AssistedFactory"); + public static final ClassName ASSISTED_INJECT = + ClassName.get("dagger.assisted", "AssistedInject"); + public static final ClassName BINDS = ClassName.get("dagger", "Binds"); + public static final ClassName BINDS_INSTANCE = ClassName.get("dagger", "BindsInstance"); + public static final ClassName BINDS_OPTIONAL_OF = ClassName.get("dagger", "BindsOptionalOf"); + public static final ClassName COMPONENT = ClassName.get("dagger", "Component"); + public static final ClassName COMPONENT_BUILDER = COMPONENT.nestedClass("Builder"); + public static final ClassName COMPONENT_FACTORY = COMPONENT.nestedClass("Factory"); + public static final ClassName DAGGER_PROCESSING_OPTIONS = + ClassName.get("dagger", "DaggerProcessingOptions"); + public static final ClassName ELEMENTS_INTO_SET = + ClassName.get("dagger.multibindings", "ElementsIntoSet"); + public static final ClassName INTO_MAP = ClassName.get("dagger.multibindings", "IntoMap"); + public static final ClassName INTO_SET = ClassName.get("dagger.multibindings", "IntoSet"); + public static final ClassName MAP_KEY = ClassName.get("dagger", "MapKey"); + public static final ClassName MODULE = ClassName.get("dagger", "Module"); + public static final ClassName MULTIBINDS = ClassName.get("dagger.multibindings", "Multibinds"); + public static final ClassName PROVIDES = ClassName.get("dagger", "Provides"); + public static final ClassName REUSABLE = ClassName.get("dagger", "Reusable"); + public static final ClassName SUBCOMPONENT = ClassName.get("dagger", "Subcomponent"); + public static final ClassName SUBCOMPONENT_BUILDER = SUBCOMPONENT.nestedClass("Builder"); + public static final ClassName SUBCOMPONENT_FACTORY = SUBCOMPONENT.nestedClass("Factory"); + + // Dagger Internal classnames + public static final ClassName DELEGATE_FACTORY = + ClassName.get("dagger.internal", "DelegateFactory"); + public static final ClassName DOUBLE_CHECK = ClassName.get("dagger.internal", "DoubleCheck"); + public static final ClassName FACTORY = ClassName.get("dagger.internal", "Factory"); public static final ClassName INJECTED_FIELD_SIGNATURE = - ClassName.get(InjectedFieldSignature.class); - public static final ClassName INSTANCE_FACTORY = ClassName.get(InstanceFactory.class); - public static final ClassName LAZY = ClassName.get(Lazy.class); - public static final ClassName LIST = ClassName.get(List.class); - public static final ClassName LISTENABLE_FUTURE = ClassName.get(ListenableFuture.class); - public static final ClassName MAP_FACTORY = ClassName.get(MapFactory.class); + ClassName.get("dagger.internal", "InjectedFieldSignature"); + public static final ClassName INSTANCE_FACTORY = + ClassName.get("dagger.internal", "InstanceFactory"); + public static final ClassName MAP_FACTORY = ClassName.get("dagger.internal", "MapFactory"); + public static final ClassName MAP_PROVIDER_FACTORY = + ClassName.get("dagger.internal", "MapProviderFactory"); + public static final ClassName MEMBERS_INJECTOR = ClassName.get("dagger", "MembersInjector"); + public static final ClassName MEMBERS_INJECTORS = + ClassName.get("dagger.internal", "MembersInjectors"); + public static final ClassName PROVIDER = ClassName.get("javax.inject", "Provider"); + public static final ClassName PROVIDER_OF_LAZY = + ClassName.get("dagger.internal", "ProviderOfLazy"); + public static final ClassName SCOPE_METADATA = ClassName.get("dagger.internal", "ScopeMetadata"); + public static final ClassName QUALIFIER_METADATA = + ClassName.get("dagger.internal", "QualifierMetadata"); + public static final ClassName SET_FACTORY = ClassName.get("dagger.internal", "SetFactory"); + public static final ClassName SINGLE_CHECK = ClassName.get("dagger.internal", "SingleCheck"); + public static final ClassName LAZY = ClassName.get("dagger", "Lazy"); + + // Dagger Producers classnames + public static final ClassName ABSTRACT_PRODUCER = + ClassName.get("dagger.producers.internal", "AbstractProducer"); + public static final ClassName CANCELLATION_LISTENER = + ClassName.get("dagger.producers.internal", "CancellationListener"); + public static final ClassName CANCELLATION_POLICY = + ClassName.get("dagger.producers", "CancellationPolicy"); + public static final ClassName DELEGATE_PRODUCER = + ClassName.get("dagger.producers.internal", "DelegateProducer"); + public static final ClassName DEPENDENCY_METHOD_PRODUCER = + ClassName.get("dagger.producers.internal", "DependencyMethodProducer"); public static final ClassName MAP_OF_PRODUCED_PRODUCER = - ClassName.get(MapOfProducedProducer.class); + ClassName.get("dagger.producers.internal", "MapOfProducedProducer"); public static final ClassName MAP_OF_PRODUCER_PRODUCER = - ClassName.get(MapOfProducerProducer.class); - public static final ClassName MAP_PRODUCER = ClassName.get(MapProducer.class); - public static final ClassName MAP_PROVIDER_FACTORY = ClassName.get(MapProviderFactory.class); - public static final ClassName MEMBERS_INJECTOR = ClassName.get(MembersInjector.class); - public static final ClassName MEMBERS_INJECTORS = ClassName.get(MembersInjectors.class); - public static final ClassName PRODUCER_TOKEN = ClassName.get(ProducerToken.class); - public static final ClassName PRODUCED = ClassName.get(Produced.class); - public static final ClassName PRODUCER = ClassName.get(Producer.class); - public static final ClassName PRODUCERS = ClassName.get(Producers.class); - public static final ClassName PRODUCER_MODULE = ClassName.get(ProducerModule.class); + ClassName.get("dagger.producers.internal", "MapOfProducerProducer"); + public static final ClassName MAP_PRODUCER = + ClassName.get("dagger.producers.internal", "MapProducer"); + public static final ClassName MONITORS = + ClassName.get("dagger.producers.monitoring.internal", "Monitors"); + public static final ClassName PRODUCED = ClassName.get("dagger.producers", "Produced"); + public static final ClassName PRODUCER = ClassName.get("dagger.producers", "Producer"); + public static final ClassName PRODUCERS = ClassName.get("dagger.producers.internal", "Producers"); + public static final ClassName PRODUCER_MODULE = + ClassName.get("dagger.producers", "ProducerModule"); + public static final ClassName PRODUCES = ClassName.get("dagger.producers", "Produces"); + public static final ClassName PRODUCTION = ClassName.get("dagger.producers", "Production"); + public static final ClassName PRODUCTION_COMPONENT = + ClassName.get("dagger.producers", "ProductionComponent"); + public static final ClassName PRODUCTION_COMPONENT_BUILDER = + PRODUCTION_COMPONENT.nestedClass("Builder"); + public static final ClassName PRODUCTION_COMPONENT_FACTORY = + PRODUCTION_COMPONENT.nestedClass("Factory"); + public static final ClassName PRODUCTION_EXECTUTOR_MODULE = + ClassName.get("dagger.producers.internal", "ProductionExecutorModule"); + public static final ClassName PRODUCTION_IMPLEMENTATION = + ClassName.get("dagger.producers.internal", "ProductionImplementation"); + public static final ClassName PRODUCTION_SUBCOMPONENT = + ClassName.get("dagger.producers", "ProductionSubcomponent"); + public static final ClassName PRODUCTION_SUBCOMPONENT_BUILDER = + PRODUCTION_SUBCOMPONENT.nestedClass("Builder"); + public static final ClassName PRODUCTION_SUBCOMPONENT_FACTORY = + PRODUCTION_SUBCOMPONENT.nestedClass("Factory"); + public static final ClassName PRODUCER_TOKEN = + ClassName.get("dagger.producers.monitoring", "ProducerToken"); + public static final ClassName PRODUCTION_COMPONENT_MONITOR = + ClassName.get("dagger.producers.monitoring", "ProductionComponentMonitor"); public static final ClassName PRODUCTION_COMPONENT_MONITOR_FACTORY = - ClassName.get(ProductionComponentMonitor.Factory.class); - public static final ClassName PROVIDER = ClassName.get(Provider.class); - public static final ClassName PROVIDER_OF_LAZY = ClassName.get(ProviderOfLazy.class); - public static final ClassName SET = ClassName.get(Set.class); - public static final ClassName SET_FACTORY = ClassName.get(SetFactory.class); + ClassName.get("dagger.producers.monitoring", "ProductionComponentMonitor", "Factory"); public static final ClassName SET_OF_PRODUCED_PRODUCER = - ClassName.get(SetOfProducedProducer.class); - public static final ClassName SET_PRODUCER = ClassName.get(SetProducer.class); - public static final ClassName SINGLE_CHECK = ClassName.get(SingleCheck.class); + ClassName.get("dagger.producers.internal", "SetOfProducedProducer"); + public static final ClassName SET_PRODUCER = + ClassName.get("dagger.producers.internal", "SetProducer"); + public static final ClassName PRODUCTION_SCOPE = + ClassName.get("dagger.producers", "ProductionScope"); + + // Other classnames + public static final ClassName EXECUTOR = ClassName.get("java.util.concurrent", "Executor"); + public static final ClassName ERROR = ClassName.get("java.lang", "Error"); + public static final ClassName EXCEPTION = ClassName.get("java.lang", "Exception"); + public static final ClassName RUNTIME_EXCEPTION = ClassName.get("java.lang", "RuntimeException"); + public static final ClassName MAP = ClassName.get("java.util", "Map"); + public static final ClassName IMMUTABLE_MAP = + ClassName.get("com.google.common.collect", "ImmutableMap"); + public static final ClassName SINGLETON = ClassName.get("jakarta.inject", "Singleton"); + public static final ClassName SINGLETON_JAVAX = ClassName.get("javax.inject", "Singleton"); + public static final ClassName SCOPE = ClassName.get("jakarta.inject", "Scope"); + public static final ClassName SCOPE_JAVAX = ClassName.get("javax.inject", "Scope"); + public static final ClassName INJECT = ClassName.get("jakarta.inject", "Inject"); + public static final ClassName INJECT_JAVAX = ClassName.get("javax.inject", "Inject"); + public static final ClassName QUALIFIER = ClassName.get("jakarta.inject", "Qualifier"); + public static final ClassName QUALIFIER_JAVAX = ClassName.get("javax.inject", "Qualifier"); + public static final ClassName COLLECTION = ClassName.get("java.util", "Collection"); + public static final ClassName LIST = ClassName.get("java.util", "List"); + public static final ClassName SET = ClassName.get("java.util", "Set"); + public static final ClassName IMMUTABLE_SET = + ClassName.get("com.google.common.collect", "ImmutableSet"); + public static final ClassName FUTURES = + ClassName.get("com.google.common.util.concurrent", "Futures"); + public static final ClassName LISTENABLE_FUTURE = + ClassName.get("com.google.common.util.concurrent", "ListenableFuture"); + public static final ClassName GUAVA_OPTIONAL = + ClassName.get("com.google.common.base", "Optional"); + public static final ClassName JDK_OPTIONAL = ClassName.get("java.util", "Optional"); + public static final ClassName OVERRIDE = ClassName.get("java.lang", "Override"); + public static final ClassName JVM_STATIC = ClassName.get("kotlin.jvm", "JvmStatic"); /** * {@link TypeName#VOID} is lowercase-v {@code void} whereas this represents the class, {@link @@ -141,8 +214,8 @@ public final class TypeNames { } /** - * Returns the {@link TypeName} for the raw type of the given type name. If the argument isn't a - * parameterized type, it returns the argument unchanged. + * Returns the {@link TypeName} for the raw type of the given {@link TypeName}. If the argument + * isn't a parameterized type, it returns the argument unchanged. */ public static TypeName rawTypeName(TypeName typeName) { return (typeName instanceof ParameterizedTypeName) @@ -150,5 +223,13 @@ public final class TypeNames { : typeName; } + /** + * Returns the {@link TypeName} for the raw type of the given {@link TypeMirror}. If the argument + * isn't a parameterized type, it returns the argument unchanged. + */ + public static TypeName rawTypeName(TypeMirror type) { + return rawTypeName(TypeName.get(type)); + } + private TypeNames() {} } diff --git a/java/dagger/internal/codegen/javapoet/TypeSpecs.java b/java/dagger/internal/codegen/javapoet/TypeSpecs.java index 8ec8747dc..570f7022f 100644 --- a/java/dagger/internal/codegen/javapoet/TypeSpecs.java +++ b/java/dagger/internal/codegen/javapoet/TypeSpecs.java @@ -16,6 +16,9 @@ package dagger.internal.codegen.javapoet; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; + +import androidx.room.compiler.processing.XTypeElement; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeSpec; @@ -31,12 +34,28 @@ public final class TypeSpecs { * @return {@code typeBuilder} */ @CanIgnoreReturnValue + public static TypeSpec.Builder addSupertype( + TypeSpec.Builder typeBuilder, XTypeElement supertype) { + return addSupertype(typeBuilder, toJavac(supertype)); + } + + /** + * If {@code supertype} is a class, adds it as a superclass for {@code typeBuilder}; if it is an + * interface, adds it as a superinterface. + * + * @return {@code typeBuilder} + */ + @CanIgnoreReturnValue public static TypeSpec.Builder addSupertype(TypeSpec.Builder typeBuilder, TypeElement supertype) { switch (supertype.getKind()) { case CLASS: - return typeBuilder.superclass(ClassName.get(supertype)); + return typeBuilder + .superclass(ClassName.get(supertype)) + .avoidClashesWithNestedClasses(supertype); case INTERFACE: - return typeBuilder.addSuperinterface(ClassName.get(supertype)); + return typeBuilder + .addSuperinterface(ClassName.get(supertype)) + .avoidClashesWithNestedClasses(supertype); default: throw new AssertionError(supertype + " is neither a class nor an interface."); } diff --git a/java/dagger/internal/codegen/kotlin/BUILD b/java/dagger/internal/codegen/kotlin/BUILD index d1c5458a1..c2f5f0f40 100644 --- a/java/dagger/internal/codegen/kotlin/BUILD +++ b/java/dagger/internal/codegen/kotlin/BUILD @@ -29,12 +29,13 @@ java_library( "//java/dagger/internal/codegen/base:shared", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/langmodel", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/jsr305_annotations", - "@google_bazel_common//third_party/java/jsr330_inject", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", + "//third_party/java/jsr305_annotations", + "//third_party/java/jsr330_inject", "@maven//:org_jetbrains_kotlin_kotlin_stdlib", "@maven//:org_jetbrains_kotlinx_kotlinx_metadata_jvm", ], diff --git a/java/dagger/internal/codegen/kotlin/KotlinMetadata.java b/java/dagger/internal/codegen/kotlin/KotlinMetadata.java index 5fb49f005..d38eba043 100644 --- a/java/dagger/internal/codegen/kotlin/KotlinMetadata.java +++ b/java/dagger/internal/codegen/kotlin/KotlinMetadata.java @@ -34,6 +34,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.internal.codegen.extension.DaggerCollectors; import dagger.internal.codegen.langmodel.DaggerElements; import java.util.HashMap; @@ -188,13 +189,12 @@ abstract class KotlinMetadata { private static KotlinClassMetadata.Class metadataOf(TypeElement typeElement) { Optional<AnnotationMirror> metadataAnnotation = - getAnnotationMirror(typeElement, Metadata.class); + getAnnotationMirror(typeElement, ClassName.get(Metadata.class)); Preconditions.checkState(metadataAnnotation.isPresent()); KotlinClassHeader header = new KotlinClassHeader( getIntValue(metadataAnnotation.get(), "k"), getIntArrayValue(metadataAnnotation.get(), "mv"), - getIntArrayValue(metadataAnnotation.get(), "bv"), getStringArrayValue(metadataAnnotation.get(), "d1"), getStringArrayValue(metadataAnnotation.get(), "d2"), getStringValue(metadataAnnotation.get(), "xs"), diff --git a/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java b/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java index 980ff3438..f9ffe4447 100644 --- a/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java +++ b/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java @@ -16,9 +16,12 @@ package dagger.internal.codegen.kotlin; -import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations; + import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static com.google.common.base.Preconditions.checkState; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.langmodel.DaggerElements.closestEnclosingTypeElement; +import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotatedAnnotations; import static kotlinx.metadata.Flag.Class.IS_COMPANION_OBJECT; import static kotlinx.metadata.Flag.Class.IS_DATA; import static kotlinx.metadata.Flag.Class.IS_OBJECT; @@ -26,8 +29,10 @@ import static kotlinx.metadata.Flag.IS_PRIVATE; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.squareup.javapoet.ClassName; import dagger.internal.codegen.extension.DaggerCollectors; -import java.lang.annotation.Annotation; +import dagger.internal.codegen.kotlin.KotlinMetadata.FunctionMetadata; import java.util.Optional; import javax.inject.Inject; import javax.lang.model.element.AnnotationMirror; @@ -37,7 +42,6 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.ElementFilter; import kotlin.Metadata; -import kotlin.jvm.JvmStatic; import kotlinx.metadata.Flag; /** Utility class for interacting with Kotlin Metadata. */ @@ -65,11 +69,12 @@ public final class KotlinMetadataUtil { * method, if any, of a Kotlin property and not for annotations in its backing field. */ public ImmutableCollection<? extends AnnotationMirror> getSyntheticPropertyAnnotations( - VariableElement fieldElement, Class<? extends Annotation> annotationType) { + VariableElement fieldElement, ClassName annotationType) { return metadataFactory .create(fieldElement) .getSyntheticAnnotationMethod(fieldElement) - .map(methodElement -> getAnnotatedAnnotations(methodElement, annotationType).asList()) + .map(methodElement -> + getAnnotatedAnnotations(methodElement, annotationType).asList()) .orElse(ImmutableList.of()); } @@ -163,9 +168,13 @@ public final class KotlinMetadataUtil { } /** - * Returns {@code true} if the <code>@JvmStatic</code> annotation is present in the given element. + * Returns a map mapping all method signatures within the given class element, including methods + * that it inherits from its ancestors, to their method names. */ - public static boolean isJvmStaticPresent(ExecutableElement element) { - return isAnnotationPresent(element, JvmStatic.class); + public ImmutableMap<String, String> getAllMethodNamesBySignature(TypeElement element) { + checkState( + hasMetadata(element), "Can not call getAllMethodNamesBySignature for non-Kotlin class"); + return metadataFactory.create(element).classMetadata().functionsBySignature().values().stream() + .collect(toImmutableMap(FunctionMetadata::signature, FunctionMetadata::name)); } } diff --git a/java/dagger/internal/codegen/kythe/BUILD b/java/dagger/internal/codegen/kythe/BUILD index 9e8dea127..0d77841e6 100644 --- a/java/dagger/internal/codegen/kythe/BUILD +++ b/java/dagger/internal/codegen/kythe/BUILD @@ -28,13 +28,13 @@ java_library( "//java/dagger:core", "//java/dagger/internal/codegen/binding", "//java/dagger/internal/codegen/javac", + "//java/dagger/internal/codegen/javapoet", "//java/dagger/internal/codegen/langmodel", "//java/dagger/internal/codegen/validation", - "//java/dagger/internal/guava:collect", - "//java/dagger/producers", + "//java/dagger/internal/codegen/xprocessing", "//java/dagger/spi", - "@google_bazel_common//third_party/java/auto:service", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:service", + "//third_party/java/guava/collect", ], ) diff --git a/java/dagger/internal/codegen/kythe/DaggerKythePlugin.java b/java/dagger/internal/codegen/kythe/DaggerKythePlugin.java index 4c6f85e21..a2bc0c23f 100644 --- a/java/dagger/internal/codegen/kythe/DaggerKythePlugin.java +++ b/java/dagger/internal/codegen/kythe/DaggerKythePlugin.java @@ -19,8 +19,12 @@ // the regular kythe/java tree. package dagger.internal.codegen.kythe; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.compat.XConverters; import com.google.auto.service.AutoService; import com.google.common.collect.Iterables; import com.google.devtools.kythe.analyzers.base.EntrySet; @@ -31,8 +35,6 @@ import com.google.devtools.kythe.proto.Storage.VName; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; -import com.sun.tools.javac.util.Context; -import dagger.BindsInstance; import dagger.Component; import dagger.internal.codegen.binding.Binding; import dagger.internal.codegen.binding.BindingDeclaration; @@ -41,13 +43,13 @@ import dagger.internal.codegen.binding.BindingNode; import dagger.internal.codegen.binding.ComponentDescriptorFactory; import dagger.internal.codegen.binding.ModuleDescriptor; import dagger.internal.codegen.javac.JavacPluginModule; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.validation.InjectBindingRegistryModule; -import dagger.model.BindingGraph; -import dagger.model.BindingGraph.DependencyEdge; -import dagger.model.BindingGraph.Edge; -import dagger.model.BindingGraph.Node; -import dagger.model.DependencyRequest; -import dagger.producers.ProductionComponent; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraph.DependencyEdge; +import dagger.spi.model.BindingGraph.Edge; +import dagger.spi.model.BindingGraph.Node; +import dagger.spi.model.DependencyRequest; import java.util.Optional; import java.util.logging.Logger; import javax.inject.Inject; @@ -65,14 +67,17 @@ public class DaggerKythePlugin extends Plugin.Scanner<Void, Void> { private FactEmitter emitter; @Inject ComponentDescriptorFactory componentDescriptorFactory; @Inject BindingGraphFactory bindingGraphFactory; + @Inject XProcessingEnv xProcessingEnv; @Override public Void visitClassDef(JCClassDecl tree, Void p) { if (tree.sym != null - && isAnyAnnotationPresent(tree.sym, Component.class, ProductionComponent.class)) { + && isAnyAnnotationPresent(tree.sym, TypeNames.COMPONENT, TypeNames.PRODUCTION_COMPONENT)) { addNodesForGraph( bindingGraphFactory.create( - componentDescriptorFactory.rootComponentDescriptor(tree.sym), false)); + componentDescriptorFactory.rootComponentDescriptor( + XConverters.toXProcessing(tree.sym, xProcessingEnv)), + false)); } return super.visitClassDef(tree, p); } @@ -128,8 +133,8 @@ public class DaggerKythePlugin extends Plugin.Scanner<Void, Void> { private void addDependencyEdge( DependencyRequest dependency, BindingDeclaration bindingDeclaration) { - Element requestElement = dependency.requestElement().get(); - Element bindingElement = bindingDeclaration.bindingElement().get(); + Element requestElement = dependency.requestElement().get().java(); + Element bindingElement = toJavac(bindingDeclaration.bindingElement().get()); Optional<VName> requestElementNode = jvmNode(requestElement, "request element"); Optional<VName> bindingElementNode = jvmNode(bindingElement, "binding element"); emitEdge(requestElementNode, "/inject/satisfiedby", bindingElementNode); @@ -155,6 +160,10 @@ public class DaggerKythePlugin extends Plugin.Scanner<Void, Void> { graph.subgraphs().forEach(this::addChildComponentEdges); } + private Optional<VName> jvmNode(XElement element, String name) { + return jvmNode(toJavac(element), name); + } + private Optional<VName> jvmNode(Element element, String name) { Optional<VName> jvmNode = kytheGraph.getJvmNode((Symbol) element).map(KytheNode::getVName); if (!jvmNode.isPresent()) { @@ -174,7 +183,7 @@ public class DaggerKythePlugin extends Plugin.Scanner<Void, Void> { if (bindingGraphFactory == null) { emitter = entrySets.getEmitter(); DaggerDaggerKythePlugin_PluginComponent.builder() - .context(kytheGraph.getJavaContext()) + .javacPluginModule(new JavacPluginModule(kytheGraph.getJavaContext())) .build() .inject(this); } @@ -185,13 +194,5 @@ public class DaggerKythePlugin extends Plugin.Scanner<Void, Void> { @Component(modules = {InjectBindingRegistryModule.class, JavacPluginModule.class}) interface PluginComponent { void inject(DaggerKythePlugin plugin); - - @Component.Builder - interface Builder { - @BindsInstance - Builder context(Context context); - - PluginComponent build(); - } } } diff --git a/java/dagger/internal/codegen/langmodel/Accessibility.java b/java/dagger/internal/codegen/langmodel/Accessibility.java index 62c1fbdbd..a90e6fd28 100644 --- a/java/dagger/internal/codegen/langmodel/Accessibility.java +++ b/java/dagger/internal/codegen/langmodel/Accessibility.java @@ -16,12 +16,17 @@ package dagger.internal.codegen.langmodel; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.auto.common.MoreElements.getPackage; +import static com.google.auto.common.MoreTypes.asElement; import static com.google.common.base.Preconditions.checkArgument; import static javax.lang.model.element.Modifier.PRIVATE; +import static javax.lang.model.element.Modifier.PROTECTED; import static javax.lang.model.element.Modifier.PUBLIC; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import java.util.Optional; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -35,12 +40,11 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.NoType; import javax.lang.model.type.NullType; import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.TypeVisitor; import javax.lang.model.type.WildcardType; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.SimpleElementVisitor8; import javax.lang.model.util.SimpleTypeVisitor8; /** @@ -61,28 +65,45 @@ import javax.lang.model.util.SimpleTypeVisitor8; public final class Accessibility { /** Returns true if the given type can be referenced from any package. */ public static boolean isTypePubliclyAccessible(TypeMirror type) { - return type.accept(new TypeAccessibilityVisitor(), null); + return type.accept(new TypeAccessibilityVisitor(Optional.empty()), null); } /** Returns true if the given type can be referenced from code in the given package. */ - public static boolean isTypeAccessibleFrom(TypeMirror type, String packageName) { - return type.accept(new TypeAccessibilityVisitor(packageName), null); + public static boolean isTypeAccessibleFrom(XType type, String packageName) { + return isTypeAccessibleFrom(toJavac(type), packageName); } - private static boolean isTypeAccessibleFrom(TypeMirror type, Optional<String> packageName) { - return type.accept(new TypeAccessibilityVisitor(packageName), null); + /** Returns true if the given type can be referenced from code in the given package. */ + public static boolean isTypeAccessibleFrom(TypeMirror type, String packageName) { + return type.accept(new TypeAccessibilityVisitor(Optional.of(packageName)), null); } - private static final class TypeAccessibilityVisitor extends SimpleTypeVisitor6<Boolean, Void> { - final Optional<String> packageName; + /** + * Returns true if the given type is protected and can be referenced from the given requesting + * element. + */ + public static boolean isProtectedMemberOf(DeclaredType type, XTypeElement requestingElement) { + return isProtectedAccessibleFromElement(type.asElement(), requestingElement); + } - TypeAccessibilityVisitor() { - this(Optional.empty()); + private static Boolean isProtectedAccessibleFromElement( + Element element, XTypeElement requestingElement) { + if (!element.getModifiers().contains(PROTECTED)) { + return false; } - - TypeAccessibilityVisitor(String packageName) { - this(Optional.of(packageName)); + if (element.getEnclosingElement().equals(toJavac(requestingElement))) { + return true; + } + // Check if the element is protected member of the requesting element's super class. + if (requestingElement.getSuperType() != null) { + return isProtectedAccessibleFromElement( + element, requestingElement.getSuperType().getTypeElement()); } + return false; + } + + private static final class TypeAccessibilityVisitor extends SimpleTypeVisitor8<Boolean, Void> { + private final Optional<String> packageName; TypeAccessibilityVisitor(Optional<String> packageName) { this.packageName = packageName; @@ -103,7 +124,7 @@ public final class Accessibility { // TODO(gak): investigate this check. see comment in Binding return false; } - if (!isElementAccessibleFrom(type.asElement(), packageName)) { + if (!type.asElement().accept(new ElementAccessibilityVisitor(packageName), null)) { return false; } for (TypeMirror typeArgument : type.getTypeArguments()) { @@ -156,36 +177,35 @@ public final class Accessibility { /** Returns true if the given element can be referenced from any package. */ public static boolean isElementPubliclyAccessible(Element element) { - return element.accept(new ElementAccessibilityVisitor(), null); + return element.accept(new ElementAccessibilityVisitor(Optional.empty()), null); + } + + /** Returns true if the given element can be referenced from code in the given package. */ + // TODO(gak): account for protected + // TODO(bcorso): account for kotlin srcs (package-private doesn't exist, internal does exist). + public static boolean isElementAccessibleFrom(XElement element, String packageName) { + return isElementAccessibleFrom(toJavac(element), packageName); } /** Returns true if the given element can be referenced from code in the given package. */ // TODO(gak): account for protected public static boolean isElementAccessibleFrom(Element element, String packageName) { - return element.accept(new ElementAccessibilityVisitor(packageName), null); + return element.accept(new ElementAccessibilityVisitor(Optional.of(packageName)), null); } - private static boolean isElementAccessibleFrom(Element element, Optional<String> packageName) { - return element.accept(new ElementAccessibilityVisitor(packageName), null); + /** Returns true if the given element can be referenced from other code in its own package. */ + public static boolean isElementAccessibleFromOwnPackage(XElement element) { + return isElementAccessibleFromOwnPackage(toJavac(element)); } /** Returns true if the given element can be referenced from other code in its own package. */ public static boolean isElementAccessibleFromOwnPackage(Element element) { - return isElementAccessibleFrom( - element, MoreElements.getPackage(element).getQualifiedName().toString()); + return isElementAccessibleFrom(element, getPackage(element).getQualifiedName().toString()); } private static final class ElementAccessibilityVisitor - extends SimpleElementVisitor6<Boolean, Void> { - final Optional<String> packageName; - - ElementAccessibilityVisitor() { - this(Optional.empty()); - } - - ElementAccessibilityVisitor(String packageName) { - this(Optional.of(packageName)); - } + extends SimpleElementVisitor8<Boolean, Void> { + private final Optional<String> packageName; ElementAccessibilityVisitor(Optional<String> packageName) { this.packageName = packageName; @@ -211,10 +231,7 @@ public final class Accessibility { } boolean accessibleMember(Element element) { - if (!element.getEnclosingElement().accept(this, null)) { - return false; - } - return accessibleModifiers(element); + return element.getEnclosingElement().accept(this, null) && accessibleModifiers(element); } boolean accessibleModifiers(Element element) { @@ -222,12 +239,9 @@ public final class Accessibility { return true; } else if (element.getModifiers().contains(PRIVATE)) { return false; - } else if (packageName.isPresent() - && getPackage(element).getQualifiedName().contentEquals(packageName.get())) { - return true; - } else { - return false; } + return packageName.isPresent() + && getPackage(element).getQualifiedName().contentEquals(packageName.get()); } @Override @@ -249,27 +263,18 @@ public final class Accessibility { } } - private static final TypeVisitor<Boolean, Optional<String>> RAW_TYPE_ACCESSIBILITY_VISITOR = - new SimpleTypeVisitor8<Boolean, Optional<String>>() { - @Override - protected Boolean defaultAction(TypeMirror e, Optional<String> requestingPackage) { - return isTypeAccessibleFrom(e, requestingPackage); - } - - @Override - public Boolean visitDeclared(DeclaredType t, Optional<String> requestingPackage) { - return isElementAccessibleFrom(t.asElement(), requestingPackage); - } - }; - /** Returns true if the raw type of {@code type} is accessible from the given package. */ public static boolean isRawTypeAccessible(TypeMirror type, String requestingPackage) { - return type.accept(RAW_TYPE_ACCESSIBILITY_VISITOR, Optional.of(requestingPackage)); + return type.getKind() == TypeKind.DECLARED + ? isElementAccessibleFrom(asElement(type), requestingPackage) + : isTypeAccessibleFrom(type, requestingPackage); } /** Returns true if the raw type of {@code type} is accessible from any package. */ public static boolean isRawTypePubliclyAccessible(TypeMirror type) { - return type.accept(RAW_TYPE_ACCESSIBILITY_VISITOR, Optional.empty()); + return type.getKind() == TypeKind.DECLARED + ? isElementPubliclyAccessible(asElement(type)) + : isTypePubliclyAccessible(type); } private Accessibility() {} diff --git a/java/dagger/internal/codegen/langmodel/BUILD b/java/dagger/internal/codegen/langmodel/BUILD index 25f1e6210..5e961a12d 100644 --- a/java/dagger/internal/codegen/langmodel/BUILD +++ b/java/dagger/internal/codegen/langmodel/BUILD @@ -27,11 +27,13 @@ java_library( deps = [ "//java/dagger:core", "//java/dagger/internal/codegen/base:shared", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", - "//java/dagger/internal/guava:graph", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", + "//java/dagger/internal/codegen/extension", + "//java/dagger/internal/codegen/xprocessing", + "//third_party/java/auto:common", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/graph", + "//third_party/java/guava/util/concurrent", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/internal/codegen/langmodel/DaggerElements.java b/java/dagger/internal/codegen/langmodel/DaggerElements.java index 51c2a605a..4d5501e75 100644 --- a/java/dagger/internal/codegen/langmodel/DaggerElements.java +++ b/java/dagger/internal/codegen/langmodel/DaggerElements.java @@ -16,41 +16,32 @@ package dagger.internal.codegen.langmodel; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.auto.common.MoreElements.asExecutable; -import static com.google.auto.common.MoreElements.hasModifiers; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Lists.asList; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static java.util.Comparator.comparing; -import static java.util.stream.Collectors.toSet; -import static javax.lang.model.element.Modifier.ABSTRACT; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMethodElement; import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.graph.Traverser; import com.squareup.javapoet.ClassName; import dagger.Reusable; import dagger.internal.codegen.base.ClearableCache; import java.io.Writer; -import java.lang.annotation.Annotation; -import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.function.Predicate; import java.util.stream.Collectors; -import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ElementVisitor; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; @@ -63,15 +54,12 @@ import javax.lang.model.type.ErrorType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.IntersectionType; import javax.lang.model.type.NoType; -import javax.lang.model.type.NullType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.UnionType; import javax.lang.model.type.WildcardType; -import javax.lang.model.util.AbstractTypeVisitor8; import javax.lang.model.util.Elements; -import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; /** Extension of {@link Elements} that adds Dagger-specific methods. */ @@ -87,37 +75,32 @@ public final class DaggerElements implements Elements, ClearableCache { this.types = checkNotNull(types); } - public DaggerElements(ProcessingEnvironment processingEnv) { - this(processingEnv.getElementUtils(), processingEnv.getTypeUtils()); + /** + * Returns {@code true} if {@code encloser} is equal to or recursively encloses {@code enclosed}. + */ + public static boolean transitivelyEncloses(XElement encloser, XElement enclosed) { + return transitivelyEncloses(toJavac(encloser), toJavac(enclosed)); } /** - * Returns {@code true} if {@code encloser} is equal to {@code enclosed} or recursively encloses - * it. + * Returns {@code true} if {@code encloser} is equal to or recursively encloses {@code enclosed}. */ - public static boolean elementEncloses(TypeElement encloser, Element enclosed) { - return Iterables.contains(GET_ENCLOSED_ELEMENTS.breadthFirst(encloser), enclosed); + public static boolean transitivelyEncloses(Element encloser, Element enclosed) { + Element current = enclosed; + while (current != null) { + if (current.equals(encloser)) { + return true; + } + current = current.getEnclosingElement(); + } + return false; } - private static final Traverser<Element> GET_ENCLOSED_ELEMENTS = - Traverser.forTree(Element::getEnclosedElements); - public ImmutableSet<ExecutableElement> getLocalAndInheritedMethods(TypeElement type) { return getLocalAndInheritedMethodsCache.computeIfAbsent( type, k -> MoreElements.getLocalAndInheritedMethods(type, types, elements)); } - public ImmutableSet<ExecutableElement> getUnimplementedMethods(TypeElement type) { - return FluentIterable.from(getLocalAndInheritedMethods(type)) - .filter(hasModifiers(ABSTRACT)) - .toSet(); - } - - /** Returns the type element for a class. */ - public TypeElement getTypeElement(Class<?> clazz) { - return getTypeElement(clazz.getCanonicalName()); - } - @Override public TypeElement getTypeElement(CharSequence name) { return elements.getTypeElement(name); @@ -130,22 +113,16 @@ public final class DaggerElements implements Elements, ClearableCache { /** Returns the argument or the closest enclosing element that is a {@link TypeElement}. */ public static TypeElement closestEnclosingTypeElement(Element element) { - return element.accept(CLOSEST_ENCLOSING_TYPE_ELEMENT, null); + Element current = element; + while (current != null) { + if (MoreElements.isType(current)) { + return MoreElements.asType(current); + } + current = current.getEnclosingElement(); + } + throw new IllegalStateException("There is no enclosing TypeElement for: " + element); } - private static final ElementVisitor<TypeElement, Void> CLOSEST_ENCLOSING_TYPE_ELEMENT = - new SimpleElementVisitor8<TypeElement, Void>() { - @Override - protected TypeElement defaultAction(Element element, Void p) { - return element.getEnclosingElement().accept(this, null); - } - - @Override - public TypeElement visitType(TypeElement type, Void p) { - return type; - } - }; - /** * Compares elements according to their declaration order among siblings. Only valid to compare * elements enclosed by the same parent. @@ -167,9 +144,9 @@ public final class DaggerElements implements Elements, ClearableCache { * that of {@code annotationClasses}. */ public static boolean isAnyAnnotationPresent( - Element element, Iterable<? extends Class<? extends Annotation>> annotationClasses) { - for (Class<? extends Annotation> annotation : annotationClasses) { - if (MoreElements.isAnnotationPresent(element, annotation)) { + Element element, Iterable<ClassName> annotationClasses) { + for (ClassName annotation : annotationClasses) { + if (isAnnotationPresent(element, annotation)) { return true; } } @@ -178,53 +155,23 @@ public final class DaggerElements implements Elements, ClearableCache { @SafeVarargs public static boolean isAnyAnnotationPresent( - Element element, - Class<? extends Annotation> first, - Class<? extends Annotation>... otherAnnotations) { + Element element, ClassName first, ClassName... otherAnnotations) { return isAnyAnnotationPresent(element, asList(first, otherAnnotations)); } /** - * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain - * AnnotationMirror#getAnnotationType() annotation type} is equivalent to {@code annotationType}. - */ - public static boolean isAnnotationPresent(Element element, TypeMirror annotationType) { - return element.getAnnotationMirrors().stream() - .map(AnnotationMirror::getAnnotationType) - .anyMatch(candidate -> MoreTypes.equivalence().equivalent(candidate, annotationType)); - } - - /** - * Returns the annotation present on {@code element} whose type is {@code first} or within {@code - * rest}, checking each annotation type in order. - */ - @SafeVarargs - public static Optional<AnnotationMirror> getAnyAnnotation( - Element element, Class<? extends Annotation> first, Class<? extends Annotation>... rest) { - return getAnyAnnotation(element, asList(first, rest)); - } - - /** - * Returns the annotation present on {@code element} whose type is in {@code annotations}, - * checking each annotation type in order. + * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@link + * AnnotationMirror#getAnnotationType() annotation type} has the same canonical name as that of + * {@code annotationClass}. This method is a safer alternative to calling {@link + * Element#getAnnotation} and checking for {@code null} as it avoids any interaction with + * annotation proxies. */ - public static Optional<AnnotationMirror> getAnyAnnotation( - Element element, Collection<? extends Class<? extends Annotation>> annotations) { - return element.getAnnotationMirrors().stream() - .filter(hasAnnotationTypeIn(annotations)) - .map((AnnotationMirror a) -> a) // Avoid returning Optional<? extends AnnotationMirror>. - .findFirst(); - } - - /** Returns the annotations present on {@code element} of all types. */ - @SafeVarargs - public static ImmutableSet<AnnotationMirror> getAllAnnotations( - Element element, Class<? extends Annotation> first, Class<? extends Annotation>... rest) { - return ImmutableSet.copyOf( - Iterables.filter( - element.getAnnotationMirrors(), hasAnnotationTypeIn(asList(first, rest))::test)); + public static boolean isAnnotationPresent(Element element, ClassName annotationName) { + return getAnnotationMirror(element, annotationName).isPresent(); } + // Note: This is similar to auto-common's MoreElements except using ClassName rather than Class. + // TODO(bcorso): Contribute a String version to auto-common's MoreElements? /** * Returns an {@link AnnotationMirror} for the annotation of type {@code annotationClass} on * {@code element}, or {@link Optional#empty()} if no such annotation exists. This method is a @@ -232,25 +179,23 @@ public final class DaggerElements implements Elements, ClearableCache { * annotation proxies. */ public static Optional<AnnotationMirror> getAnnotationMirror( - Element element, Class<? extends Annotation> annotationClass) { - return Optional.ofNullable(MoreElements.getAnnotationMirror(element, annotationClass).orNull()); - } - - private static Predicate<AnnotationMirror> hasAnnotationTypeIn( - Collection<? extends Class<? extends Annotation>> annotations) { - Set<String> annotationClassNames = - annotations.stream().map(Class::getCanonicalName).collect(toSet()); - return annotation -> - annotationClassNames.contains( - MoreTypes.asTypeElement(annotation.getAnnotationType()).getQualifiedName().toString()); + Element element, ClassName annotationName) { + String annotationClassName = annotationName.canonicalName(); + for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { + TypeElement annotationTypeElement = + MoreElements.asType(annotationMirror.getAnnotationType().asElement()); + if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName)) { + return Optional.of(annotationMirror); + } + } + return Optional.empty(); } - public static ImmutableSet<String> suppressedWarnings(Element element) { - SuppressWarnings suppressedWarnings = element.getAnnotation(SuppressWarnings.class); - if (suppressedWarnings == null) { - return ImmutableSet.of(); - } - return ImmutableSet.copyOf(suppressedWarnings.value()); + public static ImmutableSet<? extends AnnotationMirror> getAnnotatedAnnotations( + Element element, ClassName annotationName) { + return element.getAnnotationMirrors().stream() + .filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotationName)) + .collect(toImmutableSet()); } /** @@ -275,6 +220,20 @@ public final class DaggerElements implements Elements, ClearableCache { * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3">JVM * specification, section 4.3.3</a>. */ + // TODO(bcorso): Expose getMethodDescriptor() method in XProcessing instead. + public static String getMethodDescriptor(XMethodElement element) { + return getMethodDescriptor(toJavac(element)); + } + + /** + * Returns the method descriptor of the given {@code element}. + * + * <p>This is useful for matching Kotlin Metadata JVM Signatures with elements from the AST. + * + * <p>For reference, see the <a + * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3">JVM + * specification, section 4.3.3</a>. + */ public static String getMethodDescriptor(ExecutableElement element) { return element.getSimpleName() + getDescriptor(element.asType()); } @@ -283,8 +242,8 @@ public final class DaggerElements implements Elements, ClearableCache { return t.accept(JVM_DESCRIPTOR_TYPE_VISITOR, null); } - private static final AbstractTypeVisitor8<String, Void> JVM_DESCRIPTOR_TYPE_VISITOR = - new AbstractTypeVisitor8<String, Void>() { + private static final SimpleTypeVisitor8<String, Void> JVM_DESCRIPTOR_TYPE_VISITOR = + new SimpleTypeVisitor8<String, Void>() { @Override public String visitArray(ArrayType arrayType, Void v) { @@ -326,11 +285,6 @@ public final class DaggerElements implements Elements, ClearableCache { } @Override - public String visitNull(NullType nullType, Void v) { - return visitUnknown(nullType, null); - } - - @Override public String visitPrimitive(PrimitiveType primitiveType, Void v) { switch (primitiveType.getKind()) { case BOOLEAN: @@ -361,12 +315,7 @@ public final class DaggerElements implements Elements, ClearableCache { } @Override - public String visitUnion(UnionType unionType, Void v) { - return visitUnknown(unionType, null); - } - - @Override - public String visitUnknown(TypeMirror typeMirror, Void v) { + public String defaultAction(TypeMirror typeMirror, Void v) { throw new IllegalArgumentException("Unsupported type: " + typeMirror); } @@ -408,18 +357,6 @@ public final class DaggerElements implements Elements, ClearableCache { } }; - /** - * Invokes {@link Elements#getTypeElement(CharSequence)}, throwing {@link TypeNotPresentException} - * if it is not accessible in the current compilation. - */ - public TypeElement checkTypePresent(String typeName) { - TypeElement type = elements.getTypeElement(typeName); - if (type == null) { - throw new TypeNotPresentException(typeName, null); - } - return type; - } - @Override public PackageElement getPackageElement(CharSequence name) { return elements.getPackageElement(name); @@ -491,8 +428,8 @@ public final class DaggerElements implements Elements, ClearableCache { } @Override - public Name getName(CharSequence cs) { - return elements.getName(cs); + public Name getName(CharSequence cs) { // SUPPRESS_GET_NAME_CHECK: This is not xprocessing usage. + return elements.getName(cs); // SUPPRESS_GET_NAME_CHECK: This is not xprocessing usage. } @Override diff --git a/java/dagger/internal/codegen/langmodel/DaggerTypes.java b/java/dagger/internal/codegen/langmodel/DaggerTypes.java index fb291dbdb..855e739e4 100644 --- a/java/dagger/internal/codegen/langmodel/DaggerTypes.java +++ b/java/dagger/internal/codegen/langmodel/DaggerTypes.java @@ -16,23 +16,28 @@ package dagger.internal.codegen.langmodel; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static com.google.auto.common.MoreTypes.asDeclared; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableSet; import com.google.common.graph.Traverser; import com.google.common.util.concurrent.FluentFuture; import com.google.common.util.concurrent.ListenableFuture; +import com.squareup.javapoet.ArrayTypeName; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; import java.util.List; import java.util.Optional; import java.util.function.Predicate; -import javax.inject.Inject; import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; @@ -43,7 +48,6 @@ import javax.lang.model.type.NullType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; @@ -53,12 +57,94 @@ public final class DaggerTypes implements Types { private final Types types; private final DaggerElements elements; - @Inject public DaggerTypes(Types types, DaggerElements elements) { this.types = checkNotNull(types); this.elements = checkNotNull(elements); } + // Note: This is similar to auto-common's MoreTypes except using ClassName rather than Class. + // TODO(bcorso): Contribute a String version to auto-common's MoreTypes? + /** + * Returns true if the raw type underlying the given {@link TypeMirror} represents the same raw + * type as the given {@link Class} and throws an IllegalArgumentException if the {@link + * TypeMirror} does not represent a type that can be referenced by a {@link Class} + */ + public static boolean isTypeOf(final TypeName typeName, TypeMirror type) { + checkNotNull(typeName); + return type.accept(new IsTypeOf(typeName), null); + } + + private static final class IsTypeOf extends SimpleTypeVisitor8<Boolean, Void> { + private final TypeName typeName; + + IsTypeOf(TypeName typeName) { + this.typeName = typeName; + } + + @Override + protected Boolean defaultAction(TypeMirror type, Void ignored) { + throw new IllegalArgumentException(type + " cannot be represented as a Class<?>."); + } + + @Override + public Boolean visitNoType(NoType noType, Void p) { + if (noType.getKind().equals(TypeKind.VOID)) { + return typeName.equals(TypeName.VOID); + } + throw new IllegalArgumentException(noType + " cannot be represented as a Class<?>."); + } + + @Override + public Boolean visitError(ErrorType errorType, Void p) { + return false; + } + + @Override + public Boolean visitPrimitive(PrimitiveType type, Void p) { + switch (type.getKind()) { + case BOOLEAN: + return typeName.equals(TypeName.BOOLEAN); + case BYTE: + return typeName.equals(TypeName.BYTE); + case CHAR: + return typeName.equals(TypeName.CHAR); + case DOUBLE: + return typeName.equals(TypeName.DOUBLE); + case FLOAT: + return typeName.equals(TypeName.FLOAT); + case INT: + return typeName.equals(TypeName.INT); + case LONG: + return typeName.equals(TypeName.LONG); + case SHORT: + return typeName.equals(TypeName.SHORT); + default: + throw new IllegalArgumentException(type + " cannot be represented as a Class<?>."); + } + } + + @Override + public Boolean visitArray(ArrayType array, Void p) { + return (typeName instanceof ArrayTypeName) + && isTypeOf(((ArrayTypeName) typeName).componentType, array.getComponentType()); + } + + @Override + public Boolean visitDeclared(DeclaredType type, Void ignored) { + TypeElement typeElement = MoreElements.asType(type.asElement()); + return (typeName instanceof ClassName) + && typeElement.getQualifiedName().contentEquals(((ClassName) typeName).canonicalName()); + } + } + + /** + * Returns the non-{@link Object} superclass of the type with the proper type parameters. An empty + * {@link Optional} is returned if there is no non-{@link Object} superclass. + */ + public Optional<DeclaredType> nonObjectSuperclass(XType type) { + return isDeclared(type) ? nonObjectSuperclass(asDeclared(toJavac(type))) : Optional.empty(); + } + /** * Returns the non-{@link Object} superclass of the type with the proper type parameters. An empty * {@link Optional} is returned if there is no non-{@link Object} superclass. @@ -83,22 +169,24 @@ public final class DaggerTypes implements Types { * @throws IllegalArgumentException if {@code type} is not a declared type or has zero or more * than one type arguments. */ - public static TypeMirror unwrapType(TypeMirror type) { - TypeMirror unwrapped = unwrapTypeOrDefault(type, null); + public static XType unwrapType(XType type) { + XType unwrapped = unwrapTypeOrDefault(type, null); checkArgument(unwrapped != null, "%s is a raw type", type); return unwrapped; } /** - * Returns {@code type}'s single type argument, if one exists, or {@link Object} if not. + * Returns {@code type}'s single type argument. * * <p>For example, if {@code type} is {@code List<Number>} this will return {@code Number}. * - * @throws IllegalArgumentException if {@code type} is not a declared type or has more than one - * type argument. + * @throws IllegalArgumentException if {@code type} is not a declared type or has zero or more + * than one type arguments. */ - public TypeMirror unwrapTypeOrObject(TypeMirror type) { - return unwrapTypeOrDefault(type, elements.getTypeElement(Object.class).asType()); + public static TypeMirror unwrapType(TypeMirror type) { + TypeMirror unwrapped = unwrapTypeOrDefault(type, null); + checkArgument(unwrapped != null, "%s is a raw type", type); + return unwrapped; } private static TypeMirror unwrapTypeOrDefault(TypeMirror type, TypeMirror defaultType) { @@ -111,14 +199,48 @@ public final class DaggerTypes implements Types { return getOnlyElement(declaredType.getTypeArguments(), defaultType); } + private static XType unwrapTypeOrDefault(XType type, XType defaultType) { + // Check the type parameters of the element's XType since the input XType could be raw. + checkArgument(isDeclared(type)); + XTypeElement typeElement = type.getTypeElement(); + checkArgument( + typeElement.getType().getTypeArguments().size() == 1, + "%s does not have exactly 1 type parameter. Found: %s", + typeElement.getQualifiedName(), + typeElement.getType().getTypeArguments()); + return getOnlyElement(type.getTypeArguments(), defaultType); + } + + /** + * Returns {@code type}'s single type argument, if one exists, or {@link Object} if not. + * + * <p>For example, if {@code type} is {@code List<Number>} this will return {@code Number}. + * + * @throws IllegalArgumentException if {@code type} is not a declared type or has more than one + * type argument. + */ + public TypeMirror unwrapTypeOrObject(TypeMirror type) { + return unwrapTypeOrDefault(type, elements.getTypeElement(TypeName.OBJECT).asType()); + } + + /** + * Returns {@code type} wrapped in {@code wrappingClass}. + * + * <p>For example, if {@code type} is {@code List<Number>} and {@code wrappingClass} is {@code + * Set.class}, this will return {@code Set<List<Number>>}. + */ + public DeclaredType wrapType(XType type, ClassName wrappingClassName) { + return wrapType(toJavac(type), wrappingClassName); + } + /** * Returns {@code type} wrapped in {@code wrappingClass}. * * <p>For example, if {@code type} is {@code List<Number>} and {@code wrappingClass} is {@code * Set.class}, this will return {@code Set<List<Number>>}. */ - public DeclaredType wrapType(TypeMirror type, Class<?> wrappingClass) { - return types.getDeclaredType(elements.getTypeElement(wrappingClass), type); + public DeclaredType wrapType(TypeMirror type, ClassName wrappingClassName) { + return types.getDeclaredType(elements.getTypeElement(wrappingClassName.canonicalName()), type); } /** @@ -132,9 +254,9 @@ public final class DaggerTypes implements Types { * * @throws IllegalArgumentException if {@code} has more than one type argument. */ - public DeclaredType rewrapType(TypeMirror type, Class<?> wrappingClass) { + public DeclaredType rewrapType(TypeMirror type, ClassName wrappingClassName) { List<? extends TypeMirror> typeArguments = MoreTypes.asDeclared(type).getTypeArguments(); - TypeElement wrappingType = elements.getTypeElement(wrappingClass); + TypeElement wrappingType = elements.getTypeElement(wrappingClassName.canonicalName()); switch (typeArguments.size()) { case 0: return getDeclaredType(wrappingType); @@ -146,17 +268,16 @@ public final class DaggerTypes implements Types { } /** - * Returns a publicly accessible type based on {@code type}: + * Returns an accessible type in {@code requestingClass}'s package based on {@code type}: * * <ul> - * <li>If {@code type} is publicly accessible, returns it. - * <li>If not, but {@code type}'s raw type is publicly accessible, returns the raw type. + * <li>If {@code type} is accessible from the package, returns it. + * <li>If not, but {@code type}'s raw type is accessible from the package, returns the raw type. * <li>Otherwise returns {@link Object}. * </ul> */ - public TypeMirror publiclyAccessibleType(TypeMirror type) { - return accessibleType( - type, Accessibility::isTypePubliclyAccessible, Accessibility::isRawTypePubliclyAccessible); + public TypeMirror accessibleType(XType type, ClassName requestingClass) { + return accessibleType(toJavac(type), requestingClass); } /** @@ -185,7 +306,7 @@ public final class DaggerTypes implements Types { && rawTypeAccessibilityPredicate.test(type)) { return getDeclaredType(MoreTypes.asTypeElement(type)); } else { - return elements.getTypeElement(Object.class).asType(); + return elements.getTypeElement(TypeName.OBJECT).asType(); } } @@ -220,42 +341,12 @@ public final class DaggerTypes implements Types { private static final ImmutableSet<Class<?>> FUTURE_TYPES = ImmutableSet.of(ListenableFuture.class, FluentFuture.class); - public static boolean isFutureType(TypeMirror type) { - return FUTURE_TYPES.stream().anyMatch(t -> MoreTypes.isTypeOf(t, type)); - } - - public static boolean hasTypeVariable(TypeMirror type) { - return type.accept( - new SimpleTypeVisitor8<Boolean, Void>() { - @Override - public Boolean visitArray(ArrayType arrayType, Void p) { - return arrayType.getComponentType().accept(this, p); - } - - @Override - public Boolean visitDeclared(DeclaredType declaredType, Void p) { - return declaredType.getTypeArguments().stream().anyMatch(type -> type.accept(this, p)); - } - - @Override - public Boolean visitTypeVariable(TypeVariable t, Void aVoid) { - return true; - } - - @Override - protected Boolean defaultAction(TypeMirror e, Void aVoid) { - return false; - } - }, - null); + public static boolean isFutureType(XType type) { + return isFutureType(toJavac(type)); } - /** - * Resolves the type of the given executable element as a member of the given type. This may - * resolve type variables to concrete types, etc. - */ - public ExecutableType resolveExecutableType(ExecutableElement element, TypeMirror containerType) { - return MoreTypes.asExecutable(asMemberOf(MoreTypes.asDeclared(containerType), element)); + public static boolean isFutureType(TypeMirror type) { + return FUTURE_TYPES.stream().anyMatch(t -> MoreTypes.isTypeOf(t, type)); } // Implementation of Types methods, delegating to types. @@ -270,6 +361,10 @@ public final class DaggerTypes implements Types { return types.isSameType(t1, t2); } + public boolean isSubtype(XType t1, XType t2) { + return isSubtype(toJavac(t1), toJavac(t2)); + } + @Override public boolean isSubtype(TypeMirror t1, TypeMirror t2) { return types.isSubtype(t1, t2); diff --git a/java/dagger/internal/codegen/validation/AnyBindingMethodValidator.java b/java/dagger/internal/codegen/validation/AnyBindingMethodValidator.java index 140afd215..896ba1598 100644 --- a/java/dagger/internal/codegen/validation/AnyBindingMethodValidator.java +++ b/java/dagger/internal/codegen/validation/AnyBindingMethodValidator.java @@ -16,33 +16,32 @@ package dagger.internal.codegen.validation; -import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation; import static java.util.stream.Collectors.joining; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XMethodElement; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.internal.codegen.base.ClearableCache; -import java.lang.annotation.Annotation; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; -import javax.lang.model.element.ExecutableElement; /** Validates any binding method. */ @Singleton public final class AnyBindingMethodValidator implements ClearableCache { - private final ImmutableMap<Class<? extends Annotation>, BindingMethodValidator> validators; - private final Map<ExecutableElement, ValidationReport<ExecutableElement>> reports = - new HashMap<>(); + private final ImmutableMap<ClassName, BindingMethodValidator> validators; + private final Map<XMethodElement, ValidationReport> reports = new HashMap<>(); @Inject - AnyBindingMethodValidator( - ImmutableMap<Class<? extends Annotation>, BindingMethodValidator> validators) { + AnyBindingMethodValidator(ImmutableMap<ClassName, BindingMethodValidator> validators) { this.validators = validators; } @@ -52,7 +51,7 @@ public final class AnyBindingMethodValidator implements ClearableCache { } /** Returns the binding method annotations considered by this validator. */ - ImmutableSet<Class<? extends Annotation>> methodAnnotations() { + ImmutableSet<ClassName> methodAnnotations() { return validators.keySet(); } @@ -60,8 +59,8 @@ public final class AnyBindingMethodValidator implements ClearableCache { * Returns {@code true} if {@code method} is annotated with at least one of {@link * #methodAnnotations()}. */ - boolean isBindingMethod(ExecutableElement method) { - return isAnyAnnotationPresent(method, methodAnnotations()); + boolean isBindingMethod(XExecutableElement method) { + return hasAnyAnnotation(method, methodAnnotations()); } /** @@ -77,25 +76,22 @@ public final class AnyBindingMethodValidator implements ClearableCache { * @throws IllegalArgumentException if {@code method} is not annotated by any {@linkplain * #methodAnnotations() binding method annotation} */ - ValidationReport<ExecutableElement> validate(ExecutableElement method) { + ValidationReport validate(XMethodElement method) { return reentrantComputeIfAbsent(reports, method, this::validateUncached); } /** - * Returns {@code true} if {@code method} was already {@linkplain #validate(ExecutableElement) + * Returns {@code true} if {@code method} was already {@linkplain #validate(XMethodElement) * validated}. */ - boolean wasAlreadyValidated(ExecutableElement method) { + boolean wasAlreadyValidated(XMethodElement method) { return reports.containsKey(method); } - private ValidationReport<ExecutableElement> validateUncached(ExecutableElement method) { - ValidationReport.Builder<ExecutableElement> report = ValidationReport.about(method); - ImmutableSet<? extends Class<? extends Annotation>> bindingMethodAnnotations = - methodAnnotations() - .stream() - .filter(annotation -> isAnnotationPresent(method, annotation)) - .collect(toImmutableSet()); + private ValidationReport validateUncached(XMethodElement method) { + ValidationReport.Builder report = ValidationReport.about(method); + ImmutableSet<ClassName> bindingMethodAnnotations = + methodAnnotations().stream().filter(method::hasAnnotation).collect(toImmutableSet()); switch (bindingMethodAnnotations.size()) { case 0: throw new IllegalArgumentException( @@ -110,8 +106,8 @@ public final class AnyBindingMethodValidator implements ClearableCache { report.addError( String.format( "%s is annotated with more than one of (%s)", - method.getSimpleName(), - methodAnnotations().stream().map(Class::getCanonicalName).collect(joining(", "))), + getSimpleName(method), + methodAnnotations().stream().map(ClassName::canonicalName).collect(joining(", "))), method); break; } diff --git a/java/dagger/internal/codegen/validation/BUILD b/java/dagger/internal/codegen/validation/BUILD index 602157b2f..d41215a9b 100644 --- a/java/dagger/internal/codegen/validation/BUILD +++ b/java/dagger/internal/codegen/validation/BUILD @@ -33,18 +33,19 @@ java_library( "//java/dagger/internal/codegen/javapoet", "//java/dagger/internal/codegen/kotlin", "//java/dagger/internal/codegen/langmodel", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:cache", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", - "//java/dagger/internal/guava:graph", - "//java/dagger/producers", + "//java/dagger/internal/codegen/xprocessing", "//java/dagger/spi", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/checker_framework_annotations", - "@google_bazel_common//third_party/java/error_prone:annotations", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/jsr330_inject", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/checker_framework_annotations", + "//third_party/java/error_prone:annotations", + "//third_party/java/guava/base", + "//third_party/java/guava/cache", + "//third_party/java/guava/collect", + "//third_party/java/guava/graph", + "//third_party/java/guava/util/concurrent", + "//third_party/java/javapoet", + "//third_party/java/jsr330_inject", + "@maven//:org_jetbrains_kotlin_kotlin_stdlib_jdk8", ], ) diff --git a/java/dagger/internal/codegen/validation/BindingElementValidator.java b/java/dagger/internal/codegen/validation/BindingElementValidator.java index b8f9912b4..d4b405ecd 100644 --- a/java/dagger/internal/codegen/validation/BindingElementValidator.java +++ b/java/dagger/internal/codegen/validation/BindingElementValidator.java @@ -16,77 +16,73 @@ package dagger.internal.codegen.validation; -import static com.google.auto.common.MoreTypes.asTypeElement; +import static androidx.room.compiler.processing.XTypeKt.isArray; +import static androidx.room.compiler.processing.XTypeKt.isVoid; import static com.google.common.base.Verify.verifyNotNull; -import static dagger.internal.codegen.base.Scopes.scopesOf; +import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedInjectionType; import static dagger.internal.codegen.binding.MapKeys.getMapKeys; -import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror; -import static javax.lang.model.type.TypeKind.ARRAY; -import static javax.lang.model.type.TypeKind.DECLARED; -import static javax.lang.model.type.TypeKind.TYPEVAR; -import static javax.lang.model.type.TypeKind.VOID; - -import com.google.common.collect.ImmutableCollection; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; +import static dagger.internal.codegen.xprocessing.XTypes.isTypeVariable; + +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.FormatMethod; -import dagger.MapKey; -import dagger.Provides; +import com.squareup.javapoet.ClassName; import dagger.internal.codegen.base.ContributionType; import dagger.internal.codegen.base.FrameworkTypes; -import dagger.internal.codegen.base.MultibindingAnnotations; import dagger.internal.codegen.base.SetType; import dagger.internal.codegen.binding.InjectionAnnotations; -import dagger.model.Key; -import dagger.model.Scope; -import dagger.multibindings.ElementsIntoSet; -import dagger.multibindings.IntoMap; -import dagger.producers.Produces; -import java.lang.annotation.Annotation; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.internal.codegen.xprocessing.XElements; +import dagger.spi.model.Key; +import dagger.spi.model.Scope; import java.util.Formatter; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import javax.inject.Inject; import javax.inject.Qualifier; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; /** A validator for elements that represent binding declarations. */ -public abstract class BindingElementValidator<E extends Element> { - private final Class<? extends Annotation> bindingAnnotation; +public abstract class BindingElementValidator<E extends XElement> { + private static final ImmutableSet<ClassName> MULTIBINDING_ANNOTATIONS = + ImmutableSet.of(TypeNames.INTO_SET, TypeNames.ELEMENTS_INTO_SET, TypeNames.INTO_MAP); + + // TODO(bcorso): Inject this directly into InjectionAnnotations instead of using field injection. + @Inject XProcessingEnv processingEnv; + private final AllowsMultibindings allowsMultibindings; private final AllowsScoping allowsScoping; - private final Map<E, ValidationReport<E>> cache = new HashMap<>(); + private final Map<E, ValidationReport> cache = new HashMap<>(); private final InjectionAnnotations injectionAnnotations; - /** - * Creates a validator object. - * - * @param bindingAnnotation the annotation on an element that identifies it as a binding element - */ + /** Creates a validator object. */ + // TODO(bcorso): Consider reworking BindingElementValidator and all subclasses to use composition + // rather than inheritance. The web of inheritance makes it difficult to track what implementation + // of a method is actually being used. protected BindingElementValidator( - Class<? extends Annotation> bindingAnnotation, AllowsMultibindings allowsMultibindings, AllowsScoping allowsScoping, InjectionAnnotations injectionAnnotations) { - this.bindingAnnotation = bindingAnnotation; this.allowsMultibindings = allowsMultibindings; this.allowsScoping = allowsScoping; this.injectionAnnotations = injectionAnnotations; } /** Returns a {@link ValidationReport} for {@code element}. */ - final ValidationReport<E> validate(E element) { + final ValidationReport validate(E element) { return reentrantComputeIfAbsent(cache, element, this::validateUncached); } - private ValidationReport<E> validateUncached(E element) { + private ValidationReport validateUncached(E element) { return elementValidator(element).validate(); } @@ -118,7 +114,7 @@ public abstract class BindingElementValidator<E extends Element> { /** * The error message when a the type for a binding element with {@link - * ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is a not set type. + * dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is a not set type. */ protected String elementsIntoSetNotASetMessage() { return bindingElements( @@ -127,7 +123,7 @@ public abstract class BindingElementValidator<E extends Element> { /** * The error message when a the type for a binding element with {@link - * ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is a raw set. + * dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is a raw set. */ protected String elementsIntoSetRawSetMessage() { return bindingElements( @@ -139,9 +135,9 @@ public abstract class BindingElementValidator<E extends Element> { /** Validator for a single binding element. */ protected abstract class ElementValidator { - protected final E element; - protected final ValidationReport.Builder<E> report; - private final ImmutableCollection<? extends AnnotationMirror> qualifiers; + private final E element; + protected final ValidationReport.Builder report; + private final ImmutableSet<XAnnotation> qualifiers; protected ElementValidator(E element) { this.element = element; @@ -150,7 +146,7 @@ public abstract class BindingElementValidator<E extends Element> { } /** Checks the element for validity. */ - private ValidationReport<E> validate() { + private ValidationReport validate() { checkType(); checkQualifiers(); checkMapKeys(); @@ -169,8 +165,8 @@ public abstract class BindingElementValidator<E extends Element> { * that the contributed type is ambiguous or missing, i.e. a {@code @BindsInstance} method with * zero or many parameters. */ - // TODO(dpb): should this be an ImmutableList<TypeMirror>, with this class checking the size? - protected abstract Optional<TypeMirror> bindingElementType(); + // TODO(dpb): should this be an ImmutableList<XType>, with this class checking the size? + protected abstract Optional<XType> bindingElementType(); /** * Adds an error if the {@link #bindingElementType() binding element type} is not appropriate. @@ -180,8 +176,8 @@ public abstract class BindingElementValidator<E extends Element> { * <p>If the binding is not a multibinding contribution, adds an error if the type is a * framework type. * - * <p>If the element has {@link ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES}, adds an - * error if the type is not a {@code Set<T>} for some {@code T} + * <p>If the element has {@link dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code + * SET_VALUES}, adds an error if the type is not a {@code Set<T>} for some {@code T} */ protected void checkType() { switch (ContributionType.fromBindingElement(element)) { @@ -199,7 +195,7 @@ public abstract class BindingElementValidator<E extends Element> { case SET: case MAP: - bindingElementType().ifPresent(type -> checkKeyType(type)); + bindingElementType().ifPresent(this::checkKeyType); break; case SET_VALUES: @@ -210,14 +206,13 @@ public abstract class BindingElementValidator<E extends Element> { /** * Adds an error if {@code keyType} is not a primitive, declared type, array, or type variable. */ - protected void checkKeyType(TypeMirror keyType) { - TypeKind kind = keyType.getKind(); - if (kind.equals(VOID)) { + protected void checkKeyType(XType keyType) { + if (isVoid(keyType)) { report.addError(bindingElements("must %s a value (not void)", bindingElementTypeVerb())); - } else if (!(kind.isPrimitive() - || kind.equals(DECLARED) - || kind.equals(ARRAY) - || kind.equals(TYPEVAR))) { + } else if (!(isPrimitive(keyType) + || isDeclared(keyType) + || isArray(keyType) + || isTypeVariable(keyType))) { report.addError(badTypeMessage()); } } @@ -225,9 +220,9 @@ public abstract class BindingElementValidator<E extends Element> { /** Adds errors for unqualified assisted types. */ private void checkAssistedType() { if (qualifiers.isEmpty() - && bindingElementType().isPresent() - && bindingElementType().get().getKind() == DECLARED) { - TypeElement keyElement = asTypeElement(bindingElementType().get()); + && bindingElementType().isPresent() + && isDeclared(bindingElementType().get())) { + XTypeElement keyElement = bindingElementType().get().getTypeElement(); if (isAssistedInjectionType(keyElement)) { report.addError("Dagger does not support providing @AssistedInject types.", keyElement); } @@ -238,16 +233,17 @@ public abstract class BindingElementValidator<E extends Element> { } /** - * Adds an error if the type for an element with {@link ElementsIntoSet @ElementsIntoSet} or - * {@code SET_VALUES} is not a a {@code Set<T>} for a reasonable {@code T}. + * Adds an error if the type for an element with {@link + * dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} is not a a + * {@code Set<T>} for a reasonable {@code T}. */ // TODO(gak): should we allow "covariant return" for set values? protected void checkSetValuesType() { - bindingElementType().ifPresent(keyType -> checkSetValuesType(keyType)); + bindingElementType().ifPresent(this::checkSetValuesType); } /** Adds an error if {@code type} is not a {@code Set<T>} for a reasonable {@code T}. */ - protected final void checkSetValuesType(TypeMirror type) { + protected final void checkSetValuesType(XType type) { if (!SetType.isSet(type)) { report.addError(elementsIntoSetNotASetMessage()); } else { @@ -255,7 +251,10 @@ public abstract class BindingElementValidator<E extends Element> { if (setType.isRawType()) { report.addError(elementsIntoSetRawSetMessage()); } else { - checkKeyType(setType.elementType()); + // TODO(bcorso): Use setType.elementType() once setType is fully converted to XProcessing. + // However, currently SetType returns TypeMirror instead of XType and we have no + // conversion from TypeMirror to XType, so we just get the type ourselves. + checkKeyType(getOnlyElement(type.getTypeArguments())); } } } @@ -265,7 +264,7 @@ public abstract class BindingElementValidator<E extends Element> { */ private void checkQualifiers() { if (qualifiers.size() > 1) { - for (AnnotationMirror qualifier : qualifiers) { + for (XAnnotation qualifier : qualifiers) { report.addError( bindingElements("may not use more than one @Qualifier"), element, @@ -275,14 +274,15 @@ public abstract class BindingElementValidator<E extends Element> { } /** - * Adds an error if an {@link IntoMap @IntoMap} element doesn't have exactly one {@link - * MapKey @MapKey} annotation, or if an element that is {@link IntoMap @IntoMap} has any. + * Adds an error if an {@link dagger.multibindings.IntoMap @IntoMap} element doesn't have + * exactly one {@link dagger.MapKey @MapKey} annotation, or if an element that is {@link + * dagger.multibindings.IntoMap @IntoMap} has any. */ private void checkMapKeys() { if (!allowsMultibindings.allowsMultibindings()) { return; } - ImmutableSet<? extends AnnotationMirror> mapKeys = getMapKeys(element); + ImmutableSet<XAnnotation> mapKeys = getMapKeys(element); if (ContributionType.fromBindingElement(element).equals(ContributionType.MAP)) { switch (mapKeys.size()) { case 0: @@ -306,17 +306,17 @@ public abstract class BindingElementValidator<E extends Element> { * <li>the element doesn't allow {@linkplain MultibindingAnnotations multibinding annotations} * and has any * <li>the element does allow them but has more than one - * <li>the element has a multibinding annotation and its {@link Provides} or {@link Produces} - * annotation has a {@code type} parameter. + * <li>the element has a multibinding annotation and its {@link dagger.Provides} or {@link + * dagger.producers.Produces} annotation has a {@code type} parameter. * </ul> */ private void checkMultibindings() { - ImmutableSet<AnnotationMirror> multibindingAnnotations = - MultibindingAnnotations.forElement(element); + ImmutableSet<XAnnotation> multibindingAnnotations = + XElements.getAllAnnotations(element, MULTIBINDING_ANNOTATIONS); switch (allowsMultibindings) { case NO_MULTIBINDINGS: - for (AnnotationMirror annotation : multibindingAnnotations) { + for (XAnnotation annotation : multibindingAnnotations) { report.addError( bindingElements("cannot have multibinding annotations"), element, @@ -326,7 +326,7 @@ public abstract class BindingElementValidator<E extends Element> { case ALLOWS_MULTIBINDINGS: if (multibindingAnnotations.size() > 1) { - for (AnnotationMirror annotation : multibindingAnnotations) { + for (XAnnotation annotation : multibindingAnnotations) { report.addError( bindingElements("cannot have more than one multibinding annotation"), element, @@ -335,20 +335,6 @@ public abstract class BindingElementValidator<E extends Element> { } break; } - - // TODO(ronshapiro): move this into ProvidesMethodValidator - if (bindingAnnotation.equals(Provides.class)) { - AnnotationMirror bindingAnnotationMirror = - getAnnotationMirror(element, bindingAnnotation).get(); - boolean usesProvidesType = false; - for (ExecutableElement member : bindingAnnotationMirror.getElementValues().keySet()) { - usesProvidesType |= member.getSimpleName().contentEquals("type"); - } - if (usesProvidesType && !multibindingAnnotations.isEmpty()) { - report.addError( - "@Provides.type cannot be used with multibinding annotations", element); - } - } } /** @@ -356,7 +342,7 @@ public abstract class BindingElementValidator<E extends Element> { * one {@linkplain Scope scope} annotation. */ private void checkScopes() { - ImmutableSet<Scope> scopes = scopesOf(element); + ImmutableSet<Scope> scopes = injectionAnnotations.getScopes(element); String error = null; switch (allowsScoping) { case ALLOWS_SCOPING: @@ -371,7 +357,7 @@ public abstract class BindingElementValidator<E extends Element> { } verifyNotNull(error); for (Scope scope : scopes) { - report.addError(error, element, scope.scopeAnnotation()); + report.addError(error, element, scope.scopeAnnotation().xprocessing()); } } diff --git a/java/dagger/internal/codegen/validation/BindingGraphValidator.java b/java/dagger/internal/codegen/validation/BindingGraphValidator.java index 99e86e762..09477d848 100644 --- a/java/dagger/internal/codegen/validation/BindingGraphValidator.java +++ b/java/dagger/internal/codegen/validation/BindingGraphValidator.java @@ -16,41 +16,32 @@ package dagger.internal.codegen.validation; -import static com.google.common.base.Preconditions.checkNotNull; -import static javax.tools.Diagnostic.Kind.ERROR; - -import com.google.common.collect.ImmutableSet; +import androidx.room.compiler.processing.XTypeElement; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.compileroption.ValidationType; -import dagger.internal.codegen.validation.DiagnosticReporterFactory.DiagnosticReporterImpl; -import dagger.model.BindingGraph; -import dagger.spi.BindingGraphPlugin; +import dagger.spi.model.BindingGraph; import javax.inject.Inject; import javax.inject.Singleton; -import javax.lang.model.element.TypeElement; /** Validates a {@link BindingGraph}. */ @Singleton public final class BindingGraphValidator { - private final ImmutableSet<BindingGraphPlugin> validationPlugins; - private final ImmutableSet<BindingGraphPlugin> externalPlugins; - private final DiagnosticReporterFactory diagnosticReporterFactory; + private final ValidationBindingGraphPlugins validationPlugins; + private final ExternalBindingGraphPlugins externalPlugins; private final CompilerOptions compilerOptions; @Inject BindingGraphValidator( - @Validation ImmutableSet<BindingGraphPlugin> validationPlugins, - ImmutableSet<BindingGraphPlugin> externalPlugins, - DiagnosticReporterFactory diagnosticReporterFactory, + ValidationBindingGraphPlugins validationPlugins, + ExternalBindingGraphPlugins externalPlugins, CompilerOptions compilerOptions) { this.validationPlugins = validationPlugins; this.externalPlugins = externalPlugins; - this.diagnosticReporterFactory = checkNotNull(diagnosticReporterFactory); this.compilerOptions = compilerOptions; } /** Returns {@code true} if validation or analysis is required on the full binding graph. */ - public boolean shouldDoFullBindingGraphValidation(TypeElement component) { + public boolean shouldDoFullBindingGraphValidation(XTypeElement component) { return requiresFullBindingGraphValidation() || compilerOptions.pluginsVisitFullBindingGraphs(component); } @@ -61,47 +52,29 @@ public final class BindingGraphValidator { /** Returns {@code true} if no errors are reported for {@code graph}. */ public boolean isValid(BindingGraph graph) { - return validate(graph) && visitPlugins(graph); + return visitValidationPlugins(graph) && visitExternalPlugins(graph); } /** Returns {@code true} if validation plugins report no errors. */ - private boolean validate(BindingGraph graph) { + private boolean visitValidationPlugins(BindingGraph graph) { if (graph.isFullBindingGraph() && !requiresFullBindingGraphValidation()) { return true; } - boolean errorsAsWarnings = - graph.isFullBindingGraph() - && compilerOptions.fullBindingGraphValidationType().equals(ValidationType.WARNING); - - return runPlugins(validationPlugins, graph, errorsAsWarnings); + return validationPlugins.visit(graph); } /** Returns {@code true} if external plugins report no errors. */ - private boolean visitPlugins(BindingGraph graph) { - TypeElement component = graph.rootComponentNode().componentPath().currentComponent(); + private boolean visitExternalPlugins(BindingGraph graph) { if (graph.isFullBindingGraph() // TODO(b/135938915): Consider not visiting plugins if only // fullBindingGraphValidation is enabled. && !requiresFullBindingGraphValidation() - && !compilerOptions.pluginsVisitFullBindingGraphs(component)) { + && !compilerOptions.pluginsVisitFullBindingGraphs( + graph.rootComponentNode().componentPath().currentComponent().xprocessing())) { return true; } - return runPlugins(externalPlugins, graph, /*errorsAsWarnings=*/ false); - } - /** Returns {@code false} if any of the plugins reported an error. */ - private boolean runPlugins( - ImmutableSet<BindingGraphPlugin> plugins, BindingGraph graph, boolean errorsAsWarnings) { - boolean isClean = true; - for (BindingGraphPlugin plugin : plugins) { - DiagnosticReporterImpl reporter = - diagnosticReporterFactory.reporter(graph, plugin, errorsAsWarnings); - plugin.visitGraph(graph, reporter); - if (reporter.reportedDiagnosticKinds().contains(ERROR)) { - isClean = false; - } - } - return isClean; + return externalPlugins.visit(graph); } } diff --git a/java/dagger/internal/codegen/validation/BindingMethodProcessingStep.java b/java/dagger/internal/codegen/validation/BindingMethodProcessingStep.java index 10aec063f..03aff8a85 100644 --- a/java/dagger/internal/codegen/validation/BindingMethodProcessingStep.java +++ b/java/dagger/internal/codegen/validation/BindingMethodProcessingStep.java @@ -18,37 +18,32 @@ package dagger.internal.codegen.validation; import static com.google.common.base.Preconditions.checkArgument; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XMethodElement; import com.google.common.collect.ImmutableSet; -import java.lang.annotation.Annotation; -import java.util.Set; -import javax.annotation.processing.Messager; +import com.squareup.javapoet.ClassName; import javax.inject.Inject; -import javax.lang.model.element.ExecutableElement; /** A step that validates all binding methods that were not validated while processing modules. */ -public final class BindingMethodProcessingStep - extends TypeCheckingProcessingStep<ExecutableElement> { +public final class BindingMethodProcessingStep extends TypeCheckingProcessingStep<XMethodElement> { - private final Messager messager; + private final XMessager messager; private final AnyBindingMethodValidator anyBindingMethodValidator; @Inject BindingMethodProcessingStep( - Messager messager, AnyBindingMethodValidator anyBindingMethodValidator) { - super(MoreElements::asExecutable); + XMessager messager, AnyBindingMethodValidator anyBindingMethodValidator) { this.messager = messager; this.anyBindingMethodValidator = anyBindingMethodValidator; } @Override - public Set<? extends Class<? extends Annotation>> annotations() { + public ImmutableSet<ClassName> annotationClassNames() { return anyBindingMethodValidator.methodAnnotations(); } @Override - protected void process( - ExecutableElement method, ImmutableSet<Class<? extends Annotation>> annotations) { + protected void process(XMethodElement method, ImmutableSet<ClassName> annotations) { checkArgument( anyBindingMethodValidator.isBindingMethod(method), "%s is not annotated with any of %s", diff --git a/java/dagger/internal/codegen/validation/BindingMethodValidator.java b/java/dagger/internal/codegen/validation/BindingMethodValidator.java index 81349b9fb..1542b458f 100644 --- a/java/dagger/internal/codegen/validation/BindingMethodValidator.java +++ b/java/dagger/internal/codegen/validation/BindingMethodValidator.java @@ -16,34 +16,31 @@ package dagger.internal.codegen.validation; -import static com.google.auto.common.MoreElements.asType; -import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent; +import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation; +import static dagger.internal.codegen.xprocessing.XMethodElements.getEnclosingTypeElement; +import static dagger.internal.codegen.xprocessing.XMethodElements.hasTypeParameters; import static java.util.stream.Collectors.joining; -import static javax.lang.model.element.Modifier.ABSTRACT; -import static javax.lang.model.element.Modifier.PRIVATE; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.XVariableElement; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.FormatMethod; +import com.squareup.javapoet.ClassName; import dagger.internal.codegen.binding.InjectionAnnotations; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerTypes; -import java.lang.annotation.Annotation; import java.util.Optional; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; /** A validator for methods that represent binding declarations. */ -abstract class BindingMethodValidator extends BindingElementValidator<ExecutableElement> { +abstract class BindingMethodValidator extends BindingElementValidator<XMethodElement> { - private final DaggerElements elements; private final DaggerTypes types; - private final KotlinMetadataUtil metadataUtil; private final DependencyRequestValidator dependencyRequestValidator; - private final Class<? extends Annotation> methodAnnotation; - private final ImmutableSet<? extends Class<? extends Annotation>> enclosingElementAnnotations; + private final ClassName methodAnnotation; + private final ImmutableSet<ClassName> enclosingElementAnnotations; private final Abstractness abstractness; private final ExceptionSuperclass exceptionSuperclass; @@ -55,21 +52,17 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable * with this annotation */ protected BindingMethodValidator( - DaggerElements elements, DaggerTypes types, - KotlinMetadataUtil metadataUtil, DependencyRequestValidator dependencyRequestValidator, - Class<? extends Annotation> methodAnnotation, - Class<? extends Annotation> enclosingElementAnnotation, + ClassName methodAnnotation, + ClassName enclosingElementAnnotation, Abstractness abstractness, ExceptionSuperclass exceptionSuperclass, AllowsMultibindings allowsMultibindings, AllowsScoping allowsScoping, InjectionAnnotations injectionAnnotations) { this( - elements, types, - metadataUtil, methodAnnotation, ImmutableSet.of(enclosingElementAnnotation), dependencyRequestValidator, @@ -88,21 +81,17 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable * annotated with one of these annotations */ protected BindingMethodValidator( - DaggerElements elements, DaggerTypes types, - KotlinMetadataUtil metadataUtil, - Class<? extends Annotation> methodAnnotation, - Iterable<? extends Class<? extends Annotation>> enclosingElementAnnotations, + ClassName methodAnnotation, + Iterable<ClassName> enclosingElementAnnotations, DependencyRequestValidator dependencyRequestValidator, Abstractness abstractness, ExceptionSuperclass exceptionSuperclass, AllowsMultibindings allowsMultibindings, AllowsScoping allowsScoping, InjectionAnnotations injectionAnnotations) { - super(methodAnnotation, allowsMultibindings, allowsScoping, injectionAnnotations); - this.elements = elements; + super(allowsMultibindings, allowsScoping, injectionAnnotations); this.types = types; - this.metadataUtil = metadataUtil; this.methodAnnotation = methodAnnotation; this.enclosingElementAnnotations = ImmutableSet.copyOf(enclosingElementAnnotations); this.dependencyRequestValidator = dependencyRequestValidator; @@ -111,7 +100,7 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable } /** The annotation that identifies binding methods validated by this object. */ - final Class<? extends Annotation> methodAnnotation() { + final ClassName methodAnnotation() { return methodAnnotation; } @@ -127,7 +116,7 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable @Override protected final String bindingElements() { - return String.format("@%s methods", methodAnnotation.getSimpleName()); + return String.format("@%s methods", methodAnnotation.simpleName()); } @Override @@ -137,13 +126,16 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable /** Abstract validator for individual binding method elements. */ protected abstract class MethodValidator extends ElementValidator { - protected MethodValidator(ExecutableElement element) { - super(element); + private final XMethodElement method; + + protected MethodValidator(XMethodElement method) { + super(method); + this.method = method; } @Override - protected final Optional<TypeMirror> bindingElementType() { - return Optional.of(element.getReturnType()); + protected final Optional<XType> bindingElementType() { + return Optional.of(method.getReturnType()); } @Override @@ -165,38 +157,38 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable * {@link #enclosingElementAnnotations}. */ private void checkEnclosingElement() { - TypeElement enclosingElement = asType(element.getEnclosingElement()); - if (metadataUtil.isCompanionObjectClass(enclosingElement)) { + XTypeElement enclosingTypeElement = getEnclosingTypeElement(method); + if (enclosingTypeElement.isCompanionObject()) { // Binding method is in companion object, use companion object's enclosing class instead. - enclosingElement = asType(enclosingElement.getEnclosingElement()); + enclosingTypeElement = enclosingTypeElement.getEnclosingTypeElement(); } - if (!isAnyAnnotationPresent(enclosingElement, enclosingElementAnnotations)) { + if (!hasAnyAnnotation(enclosingTypeElement, enclosingElementAnnotations)) { report.addError( bindingMethods( "can only be present within a @%s", enclosingElementAnnotations.stream() - .map(Class::getSimpleName) + .map(ClassName::simpleName) .collect(joining(" or @")))); } } /** Adds an error if the method is generic. */ private void checkTypeParameters() { - if (!element.getTypeParameters().isEmpty()) { + if (hasTypeParameters(method)) { report.addError(bindingMethods("may not have type parameters")); } } /** Adds an error if the method is private. */ private void checkNotPrivate() { - if (element.getModifiers().contains(PRIVATE)) { + if (method.isPrivate()) { report.addError(bindingMethods("cannot be private")); } } /** Adds an error if the method is abstract but must not be, or is not and must be. */ private void checkAbstractness() { - boolean isAbstract = element.getModifiers().contains(ABSTRACT); + boolean isAbstract = method.isAbstract(); switch (abstractness) { case MUST_BE_ABSTRACT: if (!isAbstract) { @@ -216,12 +208,12 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable * subtype of {@link Exception}. */ private void checkThrows() { - exceptionSuperclass.checkThrows(BindingMethodValidator.this, element, report); + exceptionSuperclass.checkThrows(BindingMethodValidator.this, method, report); } /** Adds errors for the method parameters. */ protected void checkParameters() { - for (VariableElement parameter : element.getParameters()) { + for (XVariableElement parameter : method.getParameters()) { checkParameter(parameter); } } @@ -230,8 +222,8 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable * Adds errors for a method parameter. This implementation reports an error if the parameter has * more than one qualifier. */ - protected void checkParameter(VariableElement parameter) { - dependencyRequestValidator.validateDependencyRequest(report, parameter, parameter.asType()); + protected void checkParameter(XVariableElement parameter) { + dependencyRequestValidator.validateDependencyRequest(report, parameter, parameter.getType()); } } @@ -256,8 +248,8 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable @Override protected void checkThrows( BindingMethodValidator validator, - ExecutableElement element, - ValidationReport.Builder<ExecutableElement> report) { + XExecutableElement element, + ValidationReport.Builder report) { if (!element.getThrownTypes().isEmpty()) { report.addError(validator.bindingMethods("may not throw")); return; @@ -266,7 +258,7 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable }, /** Methods may throw checked or unchecked exceptions or errors. */ - EXCEPTION(Exception.class) { + EXCEPTION(TypeNames.EXCEPTION) { @Override protected String errorMessage(BindingMethodValidator validator) { return validator.bindingMethods( @@ -275,7 +267,7 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable }, /** Methods may throw unchecked exceptions or errors. */ - RUNTIME_EXCEPTION(RuntimeException.class) { + RUNTIME_EXCEPTION(TypeNames.RUNTIME_EXCEPTION) { @Override protected String errorMessage(BindingMethodValidator validator) { return validator.bindingMethods("may only throw unchecked exceptions"); @@ -283,13 +275,14 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable }, ; - private final Class<? extends Exception> superclass; + @SuppressWarnings("Immutable") + private final ClassName superclass; ExceptionSuperclass() { this(null); } - ExceptionSuperclass(Class<? extends Exception> superclass) { + ExceptionSuperclass(ClassName superclass) { this.superclass = superclass; } @@ -301,11 +294,11 @@ abstract class BindingMethodValidator extends BindingElementValidator<Executable */ protected void checkThrows( BindingMethodValidator validator, - ExecutableElement element, - ValidationReport.Builder<ExecutableElement> report) { - TypeMirror exceptionSupertype = validator.elements.getTypeElement(superclass).asType(); - TypeMirror errorType = validator.elements.getTypeElement(Error.class).asType(); - for (TypeMirror thrownType : element.getThrownTypes()) { + XExecutableElement element, + ValidationReport.Builder report) { + XType exceptionSupertype = validator.processingEnv.findType(superclass); + XType errorType = validator.processingEnv.findType(TypeNames.ERROR); + for (XType thrownType : element.getThrownTypes()) { if (!validator.types.isSubtype(thrownType, exceptionSupertype) && !validator.types.isSubtype(thrownType, errorType)) { report.addError(errorMessage(validator)); diff --git a/java/dagger/internal/codegen/validation/BindingMethodValidatorsModule.java b/java/dagger/internal/codegen/validation/BindingMethodValidatorsModule.java index 08afbc81f..3b196a620 100644 --- a/java/dagger/internal/codegen/validation/BindingMethodValidatorsModule.java +++ b/java/dagger/internal/codegen/validation/BindingMethodValidatorsModule.java @@ -19,11 +19,11 @@ package dagger.internal.codegen.validation; import static com.google.common.collect.Maps.uniqueIndex; import com.google.common.collect.ImmutableMap; +import com.squareup.javapoet.ClassName; import dagger.Binds; import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoSet; -import java.lang.annotation.Annotation; import java.util.Set; /** @@ -33,7 +33,7 @@ import java.util.Set; @Module public interface BindingMethodValidatorsModule { @Provides - static ImmutableMap<Class<? extends Annotation>, BindingMethodValidator> indexValidators( + static ImmutableMap<ClassName, BindingMethodValidator> indexValidators( Set<BindingMethodValidator> validators) { return uniqueIndex(validators, BindingMethodValidator::methodAnnotation); } diff --git a/java/dagger/internal/codegen/validation/BindsInstanceElementValidator.java b/java/dagger/internal/codegen/validation/BindsInstanceElementValidator.java index 283af7d0b..c22f8db83 100644 --- a/java/dagger/internal/codegen/validation/BindsInstanceElementValidator.java +++ b/java/dagger/internal/codegen/validation/BindsInstanceElementValidator.java @@ -16,14 +16,13 @@ package dagger.internal.codegen.validation; -import dagger.BindsInstance; +import androidx.room.compiler.processing.XElement; import dagger.internal.codegen.binding.InjectionAnnotations; -import javax.lang.model.element.Element; -abstract class BindsInstanceElementValidator<E extends Element> extends BindingElementValidator<E> { +abstract class BindsInstanceElementValidator<E extends XElement> + extends BindingElementValidator<E> { BindsInstanceElementValidator(InjectionAnnotations injectionAnnotations) { super( - BindsInstance.class, AllowsMultibindings.NO_MULTIBINDINGS, AllowsScoping.NO_SCOPING, injectionAnnotations); diff --git a/java/dagger/internal/codegen/validation/BindsInstanceMethodValidator.java b/java/dagger/internal/codegen/validation/BindsInstanceMethodValidator.java index 6d144bd28..234a55305 100644 --- a/java/dagger/internal/codegen/validation/BindsInstanceMethodValidator.java +++ b/java/dagger/internal/codegen/validation/BindsInstanceMethodValidator.java @@ -19,48 +19,56 @@ package dagger.internal.codegen.validation; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.ComponentAnnotation.anyComponentAnnotation; import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation; -import static javax.lang.model.element.Modifier.ABSTRACT; +import static dagger.internal.codegen.xprocessing.XMethodElements.getEnclosingTypeElement; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.XVariableElement; +import dagger.internal.codegen.base.DaggerSuperficialValidation; import dagger.internal.codegen.base.ModuleAnnotation; import dagger.internal.codegen.binding.InjectionAnnotations; import java.util.List; import java.util.Optional; import javax.inject.Inject; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; -final class BindsInstanceMethodValidator extends BindsInstanceElementValidator<ExecutableElement> { +final class BindsInstanceMethodValidator extends BindsInstanceElementValidator<XMethodElement> { + private final DaggerSuperficialValidation superficialValidation; + @Inject - BindsInstanceMethodValidator(InjectionAnnotations injectionAnnotations) { + BindsInstanceMethodValidator( + InjectionAnnotations injectionAnnotations, + DaggerSuperficialValidation superficialValidation) { super(injectionAnnotations); + this.superficialValidation = superficialValidation; } @Override - protected ElementValidator elementValidator(ExecutableElement element) { - return new Validator(element); + protected ElementValidator elementValidator(XMethodElement method) { + return new Validator(method); } private class Validator extends ElementValidator { - Validator(ExecutableElement element) { - super(element); + private final XMethodElement method; + + Validator(XMethodElement method) { + super(method); + this.method = method; } @Override protected void checkAdditionalProperties() { - if (!element.getModifiers().contains(ABSTRACT)) { + if (!method.isAbstract()) { report.addError("@BindsInstance methods must be abstract"); } - if (element.getParameters().size() != 1) { + if (method.getParameters().size() != 1) { report.addError( "@BindsInstance methods should have exactly one parameter for the bound type"); } - TypeElement enclosingType = MoreElements.asType(element.getEnclosingElement()); - moduleAnnotation(enclosingType) + XTypeElement enclosingTypeElement = getEnclosingTypeElement(method); + moduleAnnotation(enclosingTypeElement, superficialValidation) .ifPresent(moduleAnnotation -> report.addError(didYouMeanBinds(moduleAnnotation))); - anyComponentAnnotation(enclosingType) + anyComponentAnnotation(enclosingTypeElement, superficialValidation) .ifPresent( componentAnnotation -> report.addError( @@ -71,11 +79,10 @@ final class BindsInstanceMethodValidator extends BindsInstanceElementValidator<E } @Override - protected Optional<TypeMirror> bindingElementType() { - List<? extends VariableElement> parameters = - MoreElements.asExecutable(element).getParameters(); + protected Optional<XType> bindingElementType() { + List<? extends XVariableElement> parameters = method.getParameters(); return parameters.size() == 1 - ? Optional.of(getOnlyElement(parameters).asType()) + ? Optional.of(getOnlyElement(parameters).getType()) : Optional.empty(); } } @@ -83,6 +90,6 @@ final class BindsInstanceMethodValidator extends BindsInstanceElementValidator<E private static String didYouMeanBinds(ModuleAnnotation moduleAnnotation) { return String.format( "@BindsInstance methods should not be included in @%ss. Did you mean @Binds?", - moduleAnnotation.annotationName()); + moduleAnnotation.simpleName()); } } diff --git a/java/dagger/internal/codegen/validation/BindsInstanceParameterValidator.java b/java/dagger/internal/codegen/validation/BindsInstanceParameterValidator.java index 24d65a979..b071aa7a9 100644 --- a/java/dagger/internal/codegen/validation/BindsInstanceParameterValidator.java +++ b/java/dagger/internal/codegen/validation/BindsInstanceParameterValidator.java @@ -16,53 +16,46 @@ package dagger.internal.codegen.validation; -import static javax.lang.model.element.ElementKind.METHOD; -import static javax.lang.model.element.Modifier.ABSTRACT; -import static javax.lang.model.type.TypeKind.DECLARED; -import static javax.lang.model.type.TypeKind.TYPEVAR; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isTypeVariable; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; import dagger.internal.codegen.binding.InjectionAnnotations; import java.util.Optional; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -final class BindsInstanceParameterValidator extends BindsInstanceElementValidator<VariableElement> { +final class BindsInstanceParameterValidator + extends BindsInstanceElementValidator<XExecutableParameterElement> { @Inject BindsInstanceParameterValidator(InjectionAnnotations injectionAnnotations) { super(injectionAnnotations); } @Override - protected ElementValidator elementValidator(VariableElement element) { - return new Validator(element); + protected ElementValidator elementValidator(XExecutableParameterElement parameter) { + return new Validator(parameter); } private class Validator extends ElementValidator { - Validator(VariableElement element) { - super(element); + private final XExecutableParameterElement parameter; + + Validator(XExecutableParameterElement parameter) { + super(parameter); + this.parameter = parameter; } @Override protected void checkAdditionalProperties() { - Element enclosing = element.getEnclosingElement(); - if (!enclosing.getKind().equals(METHOD)) { - report.addError( - "@BindsInstance should only be applied to methods or parameters of methods"); - return; - } - - ExecutableElement method = MoreElements.asExecutable(enclosing); - if (!method.getModifiers().contains(ABSTRACT)) { + if (!parameter.getEnclosingMethodElement().isAbstract()) { report.addError("@BindsInstance parameters may only be used in abstract methods"); } - TypeKind returnKind = method.getReturnType().getKind(); - if (!(returnKind.equals(DECLARED) || returnKind.equals(TYPEVAR))) { + // The above check should rule out constructors since constructors cannot be abstract, so we + // know the XExecutableElement enclosing the parameter has to be an XMethodElement. + XMethodElement method = (XMethodElement) parameter.getEnclosingMethodElement(); + if (!(isDeclared(method.getReturnType()) || isTypeVariable(method.getReturnType()))) { report.addError( "@BindsInstance parameters may not be used in methods with a void, array or primitive " + "return type"); @@ -70,8 +63,8 @@ final class BindsInstanceParameterValidator extends BindsInstanceElementValidato } @Override - protected Optional<TypeMirror> bindingElementType() { - return Optional.of(element.asType()); + protected Optional<XType> bindingElementType() { + return Optional.of(parameter.getType()); } } } diff --git a/java/dagger/internal/codegen/validation/BindsInstanceProcessingStep.java b/java/dagger/internal/codegen/validation/BindsInstanceProcessingStep.java index 0e79b910f..b4132df67 100644 --- a/java/dagger/internal/codegen/validation/BindsInstanceProcessingStep.java +++ b/java/dagger/internal/codegen/validation/BindsInstanceProcessingStep.java @@ -16,51 +16,50 @@ package dagger.internal.codegen.validation; -import com.google.auto.common.MoreElements; +import static androidx.room.compiler.processing.XElementKt.isMethod; +import static androidx.room.compiler.processing.XElementKt.isMethodParameter; + +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XMethodElement; import com.google.common.collect.ImmutableSet; -import dagger.BindsInstance; -import java.lang.annotation.Annotation; -import java.util.Set; -import javax.annotation.processing.Messager; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.javapoet.TypeNames; import javax.inject.Inject; -import javax.lang.model.element.Element; /** * Processing step that validates that the {@code BindsInstance} annotation is applied to the * correct elements. */ -public final class BindsInstanceProcessingStep extends TypeCheckingProcessingStep<Element> { +public final class BindsInstanceProcessingStep extends TypeCheckingProcessingStep<XElement> { private final BindsInstanceMethodValidator methodValidator; private final BindsInstanceParameterValidator parameterValidator; - private final Messager messager; + private final XMessager messager; @Inject BindsInstanceProcessingStep( BindsInstanceMethodValidator methodValidator, BindsInstanceParameterValidator parameterValidator, - Messager messager) { - super(element -> element); + XMessager messager) { this.methodValidator = methodValidator; this.parameterValidator = parameterValidator; this.messager = messager; } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(BindsInstance.class); + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.BINDS_INSTANCE); } @Override - protected void process(Element element, ImmutableSet<Class<? extends Annotation>> annotations) { - switch (element.getKind()) { - case PARAMETER: - parameterValidator.validate(MoreElements.asVariable(element)).printMessagesTo(messager); - break; - case METHOD: - methodValidator.validate(MoreElements.asExecutable(element)).printMessagesTo(messager); - break; - default: - throw new AssertionError(element); + protected void process(XElement element, ImmutableSet<ClassName> annotations) { + if (isMethod(element)) { + methodValidator.validate((XMethodElement) element).printMessagesTo(messager); + } else if (isMethodParameter(element)) { + parameterValidator.validate((XExecutableParameterElement) element).printMessagesTo(messager); + } else { + throw new AssertionError(element); } } } diff --git a/java/dagger/internal/codegen/validation/BindsMethodValidator.java b/java/dagger/internal/codegen/validation/BindsMethodValidator.java index 6d2dd1878..20f9eb523 100644 --- a/java/dagger/internal/codegen/validation/BindsMethodValidator.java +++ b/java/dagger/internal/codegen/validation/BindsMethodValidator.java @@ -20,67 +20,63 @@ import static dagger.internal.codegen.validation.BindingElementValidator.AllowsM import static dagger.internal.codegen.validation.BindingElementValidator.AllowsScoping.ALLOWS_SCOPING; import static dagger.internal.codegen.validation.BindingMethodValidator.Abstractness.MUST_BE_ABSTRACT; import static dagger.internal.codegen.validation.BindingMethodValidator.ExceptionSuperclass.NO_EXCEPTIONS; -import static dagger.internal.codegen.validation.TypeHierarchyValidator.validateTypeHierarchy; +import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XVariableElement; import com.google.common.collect.ImmutableSet; -import dagger.Binds; -import dagger.Module; import dagger.internal.codegen.base.ContributionType; +import dagger.internal.codegen.base.DaggerSuperficialValidation; import dagger.internal.codegen.base.SetType; import dagger.internal.codegen.binding.BindsTypeChecker; import dagger.internal.codegen.binding.InjectionAnnotations; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.producers.ProducerModule; import javax.inject.Inject; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; -/** A validator for {@link Binds} methods. */ +/** A validator for {@link dagger.Binds} methods. */ final class BindsMethodValidator extends BindingMethodValidator { - private final DaggerTypes types; private final BindsTypeChecker bindsTypeChecker; + private final DaggerSuperficialValidation superficialValidation; @Inject BindsMethodValidator( - DaggerElements elements, DaggerTypes types, - KotlinMetadataUtil kotlinMetadataUtil, BindsTypeChecker bindsTypeChecker, DependencyRequestValidator dependencyRequestValidator, + DaggerSuperficialValidation superficialValidation, InjectionAnnotations injectionAnnotations) { super( - elements, types, - kotlinMetadataUtil, - Binds.class, - ImmutableSet.of(Module.class, ProducerModule.class), + TypeNames.BINDS, + ImmutableSet.of(TypeNames.MODULE, TypeNames.PRODUCER_MODULE), dependencyRequestValidator, MUST_BE_ABSTRACT, NO_EXCEPTIONS, ALLOWS_MULTIBINDINGS, ALLOWS_SCOPING, injectionAnnotations); - this.types = types; this.bindsTypeChecker = bindsTypeChecker; + this.superficialValidation = superficialValidation; } @Override - protected ElementValidator elementValidator(ExecutableElement element) { - return new Validator(element); + protected ElementValidator elementValidator(XMethodElement method) { + return new Validator(method); } private class Validator extends MethodValidator { - Validator(ExecutableElement element) { - super(element); + private final XMethodElement method; + + Validator(XMethodElement method) { + super(method); + this.method = method; } @Override protected void checkParameters() { - if (element.getParameters().size() != 1) { + if (method.getParameters().size() != 1) { report.addError( bindingMethods( "must have exactly one parameter, whose type is assignable to the return type")); @@ -90,24 +86,24 @@ final class BindsMethodValidator extends BindingMethodValidator { } @Override - protected void checkParameter(VariableElement parameter) { + protected void checkParameter(XVariableElement parameter) { super.checkParameter(parameter); - TypeMirror leftHandSide = boxIfNecessary(element.getReturnType()); - TypeMirror rightHandSide = parameter.asType(); - ContributionType contributionType = ContributionType.fromBindingElement(element); - if (contributionType.equals(ContributionType.SET_VALUES) && !SetType.isSet(leftHandSide)) { + XType returnType = boxIfNecessary(method.getReturnType()); + XType parameterType = parameter.getType(); + ContributionType contributionType = ContributionType.fromBindingElement(method); + if (contributionType.equals(ContributionType.SET_VALUES) && !SetType.isSet(returnType)) { report.addError( "@Binds @ElementsIntoSet methods must return a Set and take a Set parameter"); } - if (!bindsTypeChecker.isAssignable(rightHandSide, leftHandSide, contributionType)) { + if (!bindsTypeChecker.isAssignable(parameterType, returnType, contributionType)) { // Validate the type hierarchy of both sides to make sure they're both valid. // If one of the types isn't valid it means we need to delay validation to the next round. // Note: BasicAnnotationProcessor only performs superficial validation on the referenced // types within the module. Thus, we're guaranteed that the types in the @Binds method are // valid, but it says nothing about their supertypes, which are needed for isAssignable. - validateTypeHierarchy(leftHandSide, types); - validateTypeHierarchy(rightHandSide, types); + superficialValidation.validateTypeHierarchyOf("return type", method, returnType); + superficialValidation.validateTypeHierarchyOf("parameter", parameter, parameterType); // TODO(ronshapiro): clarify this error message for @ElementsIntoSet cases, where the // right-hand-side might not be assignable to the left-hand-side, but still compatible with // Set.addAll(Collection<? extends E>) @@ -115,11 +111,8 @@ final class BindsMethodValidator extends BindingMethodValidator { } } - private TypeMirror boxIfNecessary(TypeMirror maybePrimitive) { - if (maybePrimitive.getKind().isPrimitive()) { - return types.boxedClass(MoreTypes.asPrimitiveType(maybePrimitive)).asType(); - } - return maybePrimitive; + private XType boxIfNecessary(XType maybePrimitive) { + return isPrimitive(maybePrimitive) ? maybePrimitive.boxed() : maybePrimitive; } } } diff --git a/java/dagger/internal/codegen/validation/BindsOptionalOfMethodValidator.java b/java/dagger/internal/codegen/validation/BindsOptionalOfMethodValidator.java index cb776e196..fff851fcc 100644 --- a/java/dagger/internal/codegen/validation/BindsOptionalOfMethodValidator.java +++ b/java/dagger/internal/codegen/validation/BindsOptionalOfMethodValidator.java @@ -16,7 +16,6 @@ package dagger.internal.codegen.validation; -import static com.google.auto.common.MoreTypes.asTypeElement; import static dagger.internal.codegen.base.Keys.isValidImplicitProvisionKey; import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors; import static dagger.internal.codegen.validation.BindingElementValidator.AllowsMultibindings.NO_MULTIBINDINGS; @@ -24,64 +23,55 @@ import static dagger.internal.codegen.validation.BindingElementValidator.AllowsS import static dagger.internal.codegen.validation.BindingMethodValidator.Abstractness.MUST_BE_ABSTRACT; import static dagger.internal.codegen.validation.BindingMethodValidator.ExceptionSuperclass.NO_EXCEPTIONS; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; import com.google.common.collect.ImmutableSet; -import dagger.BindsOptionalOf; -import dagger.Module; import dagger.internal.codegen.binding.InjectionAnnotations; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.producers.ProducerModule; import javax.inject.Inject; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; -/** A validator for {@link BindsOptionalOf} methods. */ +/** A validator for {@link dagger.BindsOptionalOf} methods. */ final class BindsOptionalOfMethodValidator extends BindingMethodValidator { - - private final DaggerTypes types; private final InjectionAnnotations injectionAnnotations; @Inject BindsOptionalOfMethodValidator( - DaggerElements elements, DaggerTypes types, - KotlinMetadataUtil kotlinMetadataUtil, DependencyRequestValidator dependencyRequestValidator, InjectionAnnotations injectionAnnotations) { super( - elements, types, - kotlinMetadataUtil, - BindsOptionalOf.class, - ImmutableSet.of(Module.class, ProducerModule.class), + TypeNames.BINDS_OPTIONAL_OF, + ImmutableSet.of(TypeNames.MODULE, TypeNames.PRODUCER_MODULE), dependencyRequestValidator, MUST_BE_ABSTRACT, NO_EXCEPTIONS, NO_MULTIBINDINGS, NO_SCOPING, injectionAnnotations); - this.types = types; this.injectionAnnotations = injectionAnnotations; } - @Override - protected ElementValidator elementValidator(ExecutableElement element) { - return new Validator(element); + protected ElementValidator elementValidator(XMethodElement method) { + return new Validator(method); } private class Validator extends MethodValidator { - Validator(ExecutableElement element) { - super(element); + private final XMethodElement method; + + Validator(XMethodElement method) { + super(method); + this.method = method; } @Override - protected void checkKeyType(TypeMirror keyType) { + protected void checkKeyType(XType keyType) { super.checkKeyType(keyType); if (isValidImplicitProvisionKey( - injectionAnnotations.getQualifiers(element).stream().findFirst(), keyType, types) - && !injectedConstructors(asTypeElement(keyType)).isEmpty()) { + injectionAnnotations.getQualifiers(method).stream().findFirst(), keyType) + && !injectedConstructors(keyType.getTypeElement()).isEmpty()) { report.addError( "@BindsOptionalOf methods cannot return unqualified types that have an @Inject-" + "annotated constructor because those are always present"); @@ -90,7 +80,7 @@ final class BindsOptionalOfMethodValidator extends BindingMethodValidator { @Override protected void checkParameters() { - if (!element.getParameters().isEmpty()) { + if (!method.getParameters().isEmpty()) { report.addError("@BindsOptionalOf methods cannot have parameters"); } } diff --git a/java/dagger/internal/codegen/validation/ComponentCreatorValidator.java b/java/dagger/internal/codegen/validation/ComponentCreatorValidator.java index a6d9a3fb5..f1d043816 100644 --- a/java/dagger/internal/codegen/validation/ComponentCreatorValidator.java +++ b/java/dagger/internal/codegen/validation/ComponentCreatorValidator.java @@ -16,53 +16,50 @@ package dagger.internal.codegen.validation; -import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static androidx.room.compiler.processing.XTypeKt.isVoid; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.getCreatorAnnotations; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.getCreatorAnnotations; -import static javax.lang.model.element.Modifier.ABSTRACT; -import static javax.lang.model.element.Modifier.PRIVATE; -import static javax.lang.model.element.Modifier.STATIC; -import static javax.lang.model.util.ElementFilter.methodsIn; - -import com.google.auto.common.MoreElements; +import static dagger.internal.codegen.xprocessing.XMethodElements.hasTypeParameters; +import static dagger.internal.codegen.xprocessing.XTypeElements.getAllUnimplementedMethods; +import static dagger.internal.codegen.xprocessing.XTypeElements.hasTypeParameters; +import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; +import static javax.lang.model.SourceVersion.isKeyword; + +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XMethodElement; +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.google.common.collect.ObjectArrays; -import dagger.BindsInstance; import dagger.internal.codegen.base.ClearableCache; -import dagger.internal.codegen.binding.ComponentCreatorAnnotation; +import dagger.internal.codegen.base.ComponentCreatorAnnotation; import dagger.internal.codegen.binding.ErrorMessages; import dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages; -import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerTypes; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -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.ElementFilter; /** Validates types annotated with component creator annotations. */ @Singleton public final class ComponentCreatorValidator implements ClearableCache { - private final DaggerElements elements; + private final Map<XTypeElement, ValidationReport> reports = new HashMap<>(); private final DaggerTypes types; - private final Map<TypeElement, ValidationReport<TypeElement>> reports = new HashMap<>(); + private final KotlinMetadataUtil metadataUtil; @Inject - ComponentCreatorValidator(DaggerElements elements, DaggerTypes types) { - this.elements = elements; + ComponentCreatorValidator(DaggerTypes types, KotlinMetadataUtil metadataUtil) { this.types = types; + this.metadataUtil = metadataUtil; } @Override @@ -71,12 +68,12 @@ public final class ComponentCreatorValidator implements ClearableCache { } /** Validates that the given {@code type} is potentially a valid component creator type. */ - public ValidationReport<TypeElement> validate(TypeElement type) { + public ValidationReport validate(XTypeElement type) { return reentrantComputeIfAbsent(reports, type, this::validateUncached); } - private ValidationReport<TypeElement> validateUncached(TypeElement type) { - ValidationReport.Builder<TypeElement> report = ValidationReport.about(type); + private ValidationReport validateUncached(XTypeElement type) { + ValidationReport.Builder report = ValidationReport.about(type); ImmutableSet<ComponentCreatorAnnotation> creatorAnnotations = getCreatorAnnotations(type); if (!validateOnlyOneCreatorAnnotation(creatorAnnotations, report)) { @@ -93,7 +90,7 @@ public final class ComponentCreatorValidator implements ClearableCache { private boolean validateOnlyOneCreatorAnnotation( ImmutableSet<ComponentCreatorAnnotation> creatorAnnotations, - ValidationReport.Builder<?> report) { + ValidationReport.Builder report) { // creatorAnnotations should never be empty because this should only ever be called for // types that have been found to have some creator annotation if (creatorAnnotations.size() > 1) { @@ -109,30 +106,29 @@ public final class ComponentCreatorValidator implements ClearableCache { } /** - * Validator for a single {@link TypeElement} that is annotated with a {@code Builder} or {@code + * Validator for a single {@link XTypeElement} that is annotated with a {@code Builder} or {@code * Factory} annotation. */ private final class ElementValidator { - private final TypeElement type; - private final Element component; - private final ValidationReport.Builder<TypeElement> report; + private final XTypeElement creator; + private final ValidationReport.Builder report; private final ComponentCreatorAnnotation annotation; private final ComponentCreatorMessages messages; private ElementValidator( - TypeElement type, - ValidationReport.Builder<TypeElement> report, + XTypeElement creator, + ValidationReport.Builder report, ComponentCreatorAnnotation annotation) { - this.type = type; - this.component = type.getEnclosingElement(); + this.creator = creator; this.report = report; this.annotation = annotation; this.messages = ErrorMessages.creatorMessagesFor(annotation); } /** Validates the creator type. */ - final ValidationReport<TypeElement> validate() { - if (!isAnnotationPresent(component, annotation.componentAnnotation())) { + final ValidationReport validate() { + XTypeElement enclosingType = creator.getEnclosingTypeElement(); + if (enclosingType == null || !enclosingType.hasAnnotation(annotation.componentAnnotation())) { report.addError(messages.mustBeInComponent()); } @@ -156,29 +152,26 @@ public final class ComponentCreatorValidator implements ClearableCache { /** Validates that the type is a class or interface type and returns true if it is. */ private boolean validateIsClassOrInterface() { - switch (type.getKind()) { - case CLASS: - validateConstructor(); - return true; - case INTERFACE: - return true; - default: - report.addError(messages.mustBeClassOrInterface()); + if (creator.isClass()) { + validateConstructor(); + return true; + } + if (creator.isInterface()) { + return true; } + report.addError(messages.mustBeClassOrInterface()); return false; } private void validateConstructor() { - List<? extends Element> allElements = type.getEnclosedElements(); - List<ExecutableElement> constructors = ElementFilter.constructorsIn(allElements); + List<XConstructorElement> constructors = creator.getConstructors(); boolean valid = true; if (constructors.size() != 1) { valid = false; } else { - ExecutableElement constructor = getOnlyElement(constructors); - valid = - constructor.getParameters().isEmpty() && !constructor.getModifiers().contains(PRIVATE); + XConstructorElement constructor = getOnlyElement(constructors); + valid = constructor.getParameters().isEmpty() && !constructor.isPrivate(); } if (!valid) { @@ -188,26 +181,26 @@ public final class ComponentCreatorValidator implements ClearableCache { /** Validates basic requirements about the type that are common to both creator kinds. */ private void validateTypeRequirements() { - if (!type.getTypeParameters().isEmpty()) { + if (hasTypeParameters(creator)) { report.addError(messages.generics()); } - Set<Modifier> modifiers = type.getModifiers(); - if (modifiers.contains(PRIVATE)) { + if (creator.isPrivate()) { report.addError(messages.isPrivate()); } - if (!modifiers.contains(STATIC)) { + if (!creator.isStatic()) { report.addError(messages.mustBeStatic()); } // Note: Must be abstract, so no need to check for final. - if (!modifiers.contains(ABSTRACT)) { + if (!creator.isAbstract()) { report.addError(messages.mustBeAbstract()); } } private void validateBuilder() { - ExecutableElement buildMethod = null; - for (ExecutableElement method : elements.getUnimplementedMethods(type)) { + validateClassMethodName(); + XMethodElement buildMethod = null; + for (XMethodElement method : getAllUnimplementedMethods(creator)) { switch (method.getParameters().size()) { case 0: // If this is potentially a build() method, validate it returns the correct type. if (validateFactoryMethodReturnType(method)) { @@ -244,9 +237,24 @@ public final class ComponentCreatorValidator implements ClearableCache { } } - private void validateSetterMethod(ExecutableElement method) { - TypeMirror returnType = types.resolveExecutableType(method, type.asType()).getReturnType(); - if (returnType.getKind() != TypeKind.VOID && !types.isSubtype(type.asType(), returnType)) { + private void validateClassMethodName() { + // Only Kotlin class can have method name the same as a Java reserved keyword, so only check + // the method name if this class is a Kotlin class. + if (metadataUtil.hasMetadata(toJavac(creator))) { + metadataUtil + .getAllMethodNamesBySignature(toJavac(creator)) + .forEach( + (signature, name) -> { + if (isKeyword(name)) { + report.addError("Can not use a Java keyword as method name: " + signature); + } + }); + } + } + + private void validateSetterMethod(XMethodElement method) { + XType returnType = method.asMemberOf(creator.getType()).getReturnType(); + if (!isVoid(returnType) && !types.isSubtype(creator.getType(), returnType)) { error( method, messages.setterMethodsMustReturnVoidOrBuilder(), @@ -255,10 +263,10 @@ public final class ComponentCreatorValidator implements ClearableCache { validateNotGeneric(method); - VariableElement parameter = method.getParameters().get(0); + XExecutableParameterElement parameter = method.getParameters().get(0); - boolean methodIsBindsInstance = isAnnotationPresent(method, BindsInstance.class); - boolean parameterIsBindsInstance = isAnnotationPresent(parameter, BindsInstance.class); + boolean methodIsBindsInstance = method.hasAnnotation(TypeNames.BINDS_INSTANCE); + boolean parameterIsBindsInstance = parameter.hasAnnotation(TypeNames.BINDS_INSTANCE); boolean bindsInstance = methodIsBindsInstance || parameterIsBindsInstance; if (methodIsBindsInstance && parameterIsBindsInstance) { @@ -268,7 +276,7 @@ public final class ComponentCreatorValidator implements ClearableCache { messages.inheritedBindsInstanceNotAllowedOnBothSetterMethodAndParameter()); } - if (!bindsInstance && parameter.asType().getKind().isPrimitive()) { + if (!bindsInstance && isPrimitive(parameter.getType())) { error( method, messages.nonBindsInstanceParametersMayNotBePrimitives(), @@ -277,8 +285,7 @@ public final class ComponentCreatorValidator implements ClearableCache { } private void validateFactory() { - ImmutableList<ExecutableElement> abstractMethods = - elements.getUnimplementedMethods(type).asList(); + ImmutableList<XMethodElement> abstractMethods = getAllUnimplementedMethods(creator); switch (abstractMethods.size()) { case 0: report.addError(messages.missingFactoryMethod()); @@ -298,7 +305,7 @@ public final class ComponentCreatorValidator implements ClearableCache { } /** Validates that the given {@code method} is a valid component factory method. */ - private void validateFactoryMethod(ExecutableElement method) { + private void validateFactoryMethod(XMethodElement method) { validateNotGeneric(method); if (!validateFactoryMethodReturnType(method)) { @@ -307,9 +314,9 @@ public final class ComponentCreatorValidator implements ClearableCache { return; } - for (VariableElement parameter : method.getParameters()) { - if (!isAnnotationPresent(parameter, BindsInstance.class) - && parameter.asType().getKind().isPrimitive()) { + for (XExecutableParameterElement parameter : method.getParameters()) { + if (!parameter.hasAnnotation(TypeNames.BINDS_INSTANCE) + && isPrimitive(parameter.getType())) { error( method, messages.nonBindsInstanceParametersMayNotBePrimitives(), @@ -322,10 +329,10 @@ public final class ComponentCreatorValidator implements ClearableCache { * Validates that the factory method that actually returns a new component instance. Returns * true if the return type was valid. */ - private boolean validateFactoryMethodReturnType(ExecutableElement method) { - TypeMirror returnType = types.resolveExecutableType(method, type.asType()).getReturnType(); - - if (!types.isSubtype(component.asType(), returnType)) { + private boolean validateFactoryMethodReturnType(XMethodElement method) { + XTypeElement component = creator.getEnclosingTypeElement(); + XType returnType = method.asMemberOf(creator.getType()).getReturnType(); + if (!types.isSubtype(component.getType(), returnType)) { error( method, messages.factoryMethodMustReturnComponentType(), @@ -333,7 +340,7 @@ public final class ComponentCreatorValidator implements ClearableCache { return false; } - if (isAnnotationPresent(method, BindsInstance.class)) { + if (method.hasAnnotation(TypeNames.BINDS_INSTANCE)) { error( method, messages.factoryMethodMayNotBeAnnotatedWithBindsInstance(), @@ -341,14 +348,16 @@ public final class ComponentCreatorValidator implements ClearableCache { return false; } - TypeElement componentType = MoreElements.asType(component); - if (!types.isSameType(componentType.asType(), returnType)) { - ImmutableSet<ExecutableElement> methodsOnlyInComponent = - methodsOnlyInComponent(componentType); - if (!methodsOnlyInComponent.isEmpty()) { + if (!returnType.isSameType(component.getType())) { + // TODO(ronshapiro): Ideally this shouldn't return methods which are redeclared from a + // supertype, but do not change the return type. We don't have a good/simple way of checking + // that, and it doesn't seem likely, so the warning won't be too bad. + ImmutableSet<XMethodElement> declaredMethods = + ImmutableSet.copyOf(component.getDeclaredMethods()); + if (!declaredMethods.isEmpty()) { report.addWarning( messages.factoryMethodReturnsSupertypeWithMissingMethods( - componentType, type, returnType, method, methodsOnlyInComponent), + component, creator, returnType, method, declaredMethods), method); } } @@ -374,11 +383,8 @@ public final class ComponentCreatorValidator implements ClearableCache { * class was included in this compile run. But that's hard, and this is close enough. */ private void error( - ExecutableElement method, - String enclosedError, - String inheritedError, - Object... extraArgs) { - if (method.getEnclosingElement().equals(type)) { + XMethodElement method, String enclosedError, String inheritedError, Object... extraArgs) { + if (method.getEnclosingElement().equals(creator)) { report.addError(String.format(enclosedError, extraArgs), method); } else { report.addError(String.format(inheritedError, ObjectArrays.concat(extraArgs, method))); @@ -386,23 +392,13 @@ public final class ComponentCreatorValidator implements ClearableCache { } /** Validates that the given {@code method} is not generic. * */ - private void validateNotGeneric(ExecutableElement method) { - if (!method.getTypeParameters().isEmpty()) { + private void validateNotGeneric(XMethodElement method) { + if (hasTypeParameters(method)) { error( method, messages.methodsMayNotHaveTypeParameters(), messages.inheritedMethodsMayNotHaveTypeParameters()); } } - - /** - * Returns all methods defind in {@code componentType} which are not inherited from a supertype. - */ - private ImmutableSet<ExecutableElement> methodsOnlyInComponent(TypeElement componentType) { - // TODO(ronshapiro): Ideally this shouldn't return methods which are redeclared from a - // supertype, but do not change the return type. We don't have a good/simple way of checking - // that, and it doesn't seem likely, so the warning won't be too bad. - return ImmutableSet.copyOf(methodsIn(componentType.getEnclosedElements())); - } } } diff --git a/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java b/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java index 28b1c2bc8..dc7eac6a3 100644 --- a/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java +++ b/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java @@ -16,7 +16,9 @@ package dagger.internal.codegen.validation; -import static com.google.auto.common.MoreTypes.asDeclared; +import static androidx.room.compiler.processing.XElementKt.isMethod; +import static androidx.room.compiler.processing.XElementKt.isMethodParameter; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Predicates.in; import static com.google.common.collect.Collections2.transform; @@ -24,22 +26,30 @@ import static dagger.internal.codegen.base.ComponentAnnotation.rootComponentAnno import static dagger.internal.codegen.base.DiagnosticFormatting.stripCommonTypePrefixes; import static dagger.internal.codegen.base.Formatter.INDENT; import static dagger.internal.codegen.base.Scopes.getReadableSource; -import static dagger.internal.codegen.base.Scopes.scopesOf; -import static dagger.internal.codegen.base.Scopes.singletonScope; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; +import static dagger.internal.codegen.xprocessing.XElements.asMethodParameter; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static javax.tools.Diagnostic.Kind.ERROR; -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; -import com.google.common.base.Equivalence.Wrapper; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; +import dagger.internal.codegen.base.DaggerSuperficialValidation; import dagger.internal.codegen.binding.ComponentCreatorDescriptor; import dagger.internal.codegen.binding.ComponentDescriptor; import dagger.internal.codegen.binding.ComponentRequirement; @@ -47,14 +57,13 @@ import dagger.internal.codegen.binding.ComponentRequirement.NullPolicy; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.ErrorMessages; import dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages; +import dagger.internal.codegen.binding.InjectionAnnotations; import dagger.internal.codegen.binding.MethodSignatureFormatter; import dagger.internal.codegen.binding.ModuleDescriptor; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.compileroption.ValidationType; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Scope; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.Scope; import java.util.ArrayDeque; import java.util.Collection; import java.util.Deque; @@ -65,14 +74,6 @@ import java.util.Optional; import java.util.Set; import java.util.StringJoiner; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ExecutableType; -import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; /** @@ -88,30 +89,27 @@ import javax.tools.Diagnostic; // TODO(dpb): Combine with ComponentHierarchyValidator. public final class ComponentDescriptorValidator { - private final DaggerElements elements; - private final DaggerTypes types; private final CompilerOptions compilerOptions; private final MethodSignatureFormatter methodSignatureFormatter; private final ComponentHierarchyValidator componentHierarchyValidator; - private final KotlinMetadataUtil metadataUtil; + private final InjectionAnnotations injectionAnnotations; + private final DaggerSuperficialValidation superficialValidation; @Inject ComponentDescriptorValidator( - DaggerElements elements, - DaggerTypes types, CompilerOptions compilerOptions, MethodSignatureFormatter methodSignatureFormatter, ComponentHierarchyValidator componentHierarchyValidator, - KotlinMetadataUtil metadataUtil) { - this.elements = elements; - this.types = types; + InjectionAnnotations injectionAnnotations, + DaggerSuperficialValidation superficialValidation) { this.compilerOptions = compilerOptions; this.methodSignatureFormatter = methodSignatureFormatter; this.componentHierarchyValidator = componentHierarchyValidator; - this.metadataUtil = metadataUtil; + this.injectionAnnotations = injectionAnnotations; + this.superficialValidation = superficialValidation; } - public ValidationReport<TypeElement> validate(ComponentDescriptor component) { + public ValidationReport validate(ComponentDescriptor component) { ComponentValidation validation = new ComponentValidation(component); validation.visitComponent(component); validation.report(component).addSubreport(componentHierarchyValidator.validate(component)); @@ -120,23 +118,21 @@ public final class ComponentDescriptorValidator { private final class ComponentValidation { final ComponentDescriptor rootComponent; - final Map<ComponentDescriptor, ValidationReport.Builder<TypeElement>> reports = - new LinkedHashMap<>(); + final Map<ComponentDescriptor, ValidationReport.Builder> reports = new LinkedHashMap<>(); ComponentValidation(ComponentDescriptor rootComponent) { this.rootComponent = checkNotNull(rootComponent); } /** Returns a report that contains all validation messages found during traversal. */ - ValidationReport<TypeElement> buildReport() { - ValidationReport.Builder<TypeElement> report = - ValidationReport.about(rootComponent.typeElement()); + ValidationReport buildReport() { + ValidationReport.Builder report = ValidationReport.about(rootComponent.typeElement()); reports.values().forEach(subreport -> report.addSubreport(subreport.build())); return report.build(); } /** Returns the report builder for a (sub)component. */ - private ValidationReport.Builder<TypeElement> report(ComponentDescriptor component) { + private ValidationReport.Builder report(ComponentDescriptor component) { return reentrantComputeIfAbsent( reports, component, descriptor -> ValidationReport.about(descriptor.typeElement())); } @@ -166,7 +162,9 @@ public final class ComponentDescriptorValidator { /** Recursive method to validate that component dependencies do not form a cycle. */ private void validateComponentDependencyHierarchy( - ComponentDescriptor component, TypeElement dependency, Deque<TypeElement> dependencyStack) { + ComponentDescriptor component, + XTypeElement dependency, + Deque<XTypeElement> dependencyStack) { if (dependencyStack.contains(dependency)) { // Current component has already appeared in the component chain. StringBuilder message = new StringBuilder(); @@ -183,16 +181,14 @@ public final class ComponentDescriptorValidator { // Always validate direct component dependencies referenced by this component regardless // of the flag value || dependencyStack.isEmpty()) { - rootComponentAnnotation(dependency) + rootComponentAnnotation(dependency, superficialValidation) .ifPresent( componentAnnotation -> { dependencyStack.push(dependency); - - for (TypeElement nextDependency : componentAnnotation.dependencies()) { + for (XTypeElement nextDependency : componentAnnotation.dependencies()) { validateComponentDependencyHierarchy( component, nextDependency, dependencyStack); } - dependencyStack.pop(); }); } @@ -203,19 +199,18 @@ public final class ComponentDescriptorValidator { * singleton components have no scoped dependencies. */ private void validateDependencyScopes(ComponentDescriptor component) { - ImmutableSet<Scope> scopes = component.scopes(); - ImmutableSet<TypeElement> scopedDependencies = + ImmutableSet<ClassName> scopes = + component.scopes().stream().map(Scope::className).collect(toImmutableSet()); + ImmutableSet<XTypeElement> scopedDependencies = scopedTypesIn( - component - .dependencies() - .stream() + component.dependencies().stream() .map(ComponentRequirement::typeElement) .collect(toImmutableSet())); if (!scopes.isEmpty()) { - Scope singletonScope = singletonScope(elements); // Dagger 1.x scope compatibility requires this be suppress-able. if (compilerOptions.scopeCycleValidationType().diagnosticKind().isPresent() - && scopes.contains(singletonScope)) { + && (scopes.contains(TypeNames.SINGLETON) + || scopes.contains(TypeNames.SINGLETON_JAVAX))) { // Singleton is a special-case representing the longest lifetime, and therefore // @Singleton components may not depend on scoped components if (!scopedDependencies.isEmpty()) { @@ -249,7 +244,7 @@ public final class ComponentDescriptorValidator { private void validateModules(ComponentDescriptor component) { for (ModuleDescriptor module : component.modules()) { - if (module.moduleElement().getModifiers().contains(Modifier.ABSTRACT)) { + if (module.moduleElement().isAbstract()) { for (ContributionBinding binding : module.bindings()) { if (binding.requiresModuleInstance()) { report(component).addError(abstractModuleHasInstanceBindingMethodsError(module)); @@ -300,9 +295,9 @@ public final class ComponentDescriptorValidator { Sets.difference( creatorModuleAndDependencyRequirements, componentModuleAndDependencyRequirements); - DeclaredType container = asDeclared(creator.typeElement().asType()); + XType container = creator.typeElement().getType(); if (!inapplicableRequirementsOnCreator.isEmpty()) { - Collection<Element> excessElements = + Collection<XElement> excessElements = Multimaps.filterKeys( creator.unvalidatedRequirementElements(), in(inapplicableRequirementsOnCreator)) .values(); @@ -318,7 +313,7 @@ public final class ComponentDescriptorValidator { Set<ComponentRequirement> mustBePassed = Sets.filter( componentModuleAndDependencyRequirements, - input -> input.nullPolicy(elements, metadataUtil).equals(NullPolicy.THROW)); + input -> input.nullPolicy().equals(NullPolicy.THROW)); // Component requirements that the creator must be able to set, but can't Set<ComponentRequirement> missingRequirements = Sets.difference(mustBePassed, creatorModuleAndDependencyRequirements); @@ -333,19 +328,20 @@ public final class ComponentDescriptorValidator { } // Validate that declared creator requirements (modules, dependencies) have unique types. - ImmutableSetMultimap<Wrapper<TypeMirror>, Element> declaredRequirementsByType = + ImmutableSetMultimap<TypeName, XElement> declaredRequirementsByType = Multimaps.filterKeys( creator.unvalidatedRequirementElements(), creatorModuleAndDependencyRequirements::contains) - .entries().stream() + .entries() + .stream() .collect( - toImmutableSetMultimap(entry -> entry.getKey().wrappedType(), Entry::getValue)); + toImmutableSetMultimap( + entry -> entry.getKey().type().getTypeName(), Entry::getValue)); declaredRequirementsByType .asMap() .forEach( - (typeWrapper, elementsForType) -> { + (type, elementsForType) -> { if (elementsForType.size() > 1) { - TypeMirror type = typeWrapper.get(); // TODO(cgdecker): Attach this error message to the factory method rather than // the component type if the elements are factory method parameters AND the // factory method is defined by the factory type itself and not by a supertype. @@ -365,40 +361,39 @@ public final class ComponentDescriptorValidator { // for subcomponents. } - private String formatElement(Element element, DeclaredType container) { + private String formatElement(XElement element, XType container) { // TODO(cgdecker): Extract some or all of this to another class? // But note that it does different formatting for parameters than // DaggerElements.elementToString(Element). - switch (element.getKind()) { - case METHOD: - return methodSignatureFormatter.format( - MoreElements.asExecutable(element), Optional.of(container)); - case PARAMETER: - return formatParameter(MoreElements.asVariable(element), container); - default: - // This method shouldn't be called with any other type of element. - throw new AssertionError(); + if (isMethod(element)) { + return methodSignatureFormatter.format(asMethod(element), Optional.of(container)); + } else if (isMethodParameter(element)) { + return formatParameter(asMethodParameter(element), container); } + // This method shouldn't be called with any other type of element. + throw new AssertionError(); } - private String formatParameter(VariableElement parameter, DeclaredType container) { + private String formatParameter(XExecutableParameterElement parameter, XType container) { // TODO(cgdecker): Possibly leave the type (and annotations?) off of the parameters here and // just use their names, since the type will be redundant in the context of the error message. StringJoiner joiner = new StringJoiner(" "); - parameter.getAnnotationMirrors().stream().map(Object::toString).forEach(joiner::add); - TypeMirror parameterType = resolveParameterType(parameter, container); + parameter.getAllAnnotations().stream() + .map(XAnnotation::getQualifiedName) + .forEach(joiner::add); + XType parameterType = resolveParameterType(parameter, container); return joiner - .add(stripCommonTypePrefixes(parameterType.toString())) - .add(parameter.getSimpleName()) + .add(stripCommonTypePrefixes(parameterType.getTypeName().toString())) + .add(getSimpleName(parameter)) .toString(); } - private TypeMirror resolveParameterType(VariableElement parameter, DeclaredType container) { - ExecutableElement method = - MoreElements.asExecutable(parameter.getEnclosingElement()); + private XType resolveParameterType(XExecutableParameterElement parameter, XType container) { + checkArgument(isMethod(parameter.getEnclosingMethodElement())); + XMethodElement method = asMethod(parameter.getEnclosingMethodElement()); int parameterIndex = method.getParameters().indexOf(parameter); - ExecutableType methodType = MoreTypes.asExecutable(types.asMemberOf(container, method)); + XMethodType methodType = method.asMemberOf(container); return methodType.getParameterTypes().get(parameterIndex); } @@ -413,10 +408,10 @@ public final class ComponentDescriptorValidator { */ private void validateDependencyScopeHierarchy( ComponentDescriptor component, - TypeElement dependency, + XTypeElement dependency, Deque<ImmutableSet<Scope>> scopeStack, - Deque<TypeElement> scopedDependencyStack) { - ImmutableSet<Scope> scopes = scopesOf(dependency); + Deque<XTypeElement> scopedDependencyStack) { + ImmutableSet<Scope> scopes = injectionAnnotations.getScopes(dependency); if (stackOverlaps(scopeStack, scopes)) { scopedDependencyStack.push(dependency); // Current scope has already appeared in the component chain. @@ -436,22 +431,19 @@ public final class ComponentDescriptorValidator { // of the flag value || scopedDependencyStack.isEmpty()) { // TODO(beder): transitively check scopes of production components too. - rootComponentAnnotation(dependency) + rootComponentAnnotation(dependency, superficialValidation) .filter(componentAnnotation -> !componentAnnotation.isProduction()) .ifPresent( componentAnnotation -> { - ImmutableSet<TypeElement> scopedDependencies = + ImmutableSet<XTypeElement> scopedDependencies = scopedTypesIn(componentAnnotation.dependencies()); if (!scopedDependencies.isEmpty()) { // empty can be ignored (base-case) scopeStack.push(scopes); scopedDependencyStack.push(dependency); - for (TypeElement scopedDependency : scopedDependencies) { + for (XTypeElement scopedDependency : scopedDependencies) { validateDependencyScopeHierarchy( - component, - scopedDependency, - scopeStack, - scopedDependencyStack); + component, scopedDependency, scopeStack, scopedDependencyStack); } scopedDependencyStack.pop(); scopeStack.pop(); @@ -470,10 +462,10 @@ public final class ComponentDescriptorValidator { } /** Appends and formats a list of indented component types (with their scope annotations). */ - private void appendIndentedComponentsList(StringBuilder message, Iterable<TypeElement> types) { - for (TypeElement scopedComponent : types) { + private void appendIndentedComponentsList(StringBuilder message, Iterable<XTypeElement> types) { + for (XTypeElement scopedComponent : types) { message.append(INDENT); - for (Scope scope : scopesOf(scopedComponent)) { + for (Scope scope : injectionAnnotations.getScopes(scopedComponent)) { message.append(getReadableSource(scope)).append(' '); } message @@ -486,8 +478,10 @@ public final class ComponentDescriptorValidator { * Returns a set of type elements containing only those found in the input set that have a * scoping annotation. */ - private ImmutableSet<TypeElement> scopedTypesIn(Collection<TypeElement> types) { - return types.stream().filter(type -> !scopesOf(type).isEmpty()).collect(toImmutableSet()); + private ImmutableSet<XTypeElement> scopedTypesIn(Collection<XTypeElement> types) { + return types.stream() + .filter(type -> !injectionAnnotations.getScopes(type).isEmpty()) + .collect(toImmutableSet()); } } } diff --git a/java/dagger/internal/codegen/validation/ComponentHierarchyValidator.java b/java/dagger/internal/codegen/validation/ComponentHierarchyValidator.java index 1861636d3..f67c9e3c7 100644 --- a/java/dagger/internal/codegen/validation/ComponentHierarchyValidator.java +++ b/java/dagger/internal/codegen/validation/ComponentHierarchyValidator.java @@ -21,13 +21,13 @@ import static com.google.common.base.Predicates.and; import static com.google.common.base.Predicates.in; import static com.google.common.base.Predicates.not; import static dagger.internal.codegen.base.Scopes.getReadableSource; -import static dagger.internal.codegen.base.Scopes.uniqueScopeOf; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.base.Joiner; import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; @@ -37,32 +37,35 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; +import dagger.internal.codegen.base.ModuleKind; import dagger.internal.codegen.binding.ComponentDescriptor; import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; +import dagger.internal.codegen.binding.InjectionAnnotations; import dagger.internal.codegen.binding.ModuleDescriptor; -import dagger.internal.codegen.binding.ModuleKind; import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.model.Scope; +import dagger.spi.model.Scope; import java.util.Collection; import java.util.Formatter; import java.util.Map; +import java.util.Optional; import javax.inject.Inject; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; /** Validates the relationships between parent components and subcomponents. */ final class ComponentHierarchyValidator { private static final Joiner COMMA_SEPARATED_JOINER = Joiner.on(", "); + private final CompilerOptions compilerOptions; + private final InjectionAnnotations injectionAnnotations; @Inject - ComponentHierarchyValidator(CompilerOptions compilerOptions) { + ComponentHierarchyValidator( + CompilerOptions compilerOptions, InjectionAnnotations injectionAnnotations) { this.compilerOptions = compilerOptions; + this.injectionAnnotations = injectionAnnotations; } - ValidationReport<TypeElement> validate(ComponentDescriptor componentDescriptor) { - ValidationReport.Builder<TypeElement> report = - ValidationReport.about(componentDescriptor.typeElement()); + ValidationReport validate(ComponentDescriptor componentDescriptor) { + ValidationReport.Builder report = ValidationReport.about(componentDescriptor.typeElement()); validateSubcomponentMethods( report, componentDescriptor, @@ -78,9 +81,9 @@ final class ComponentHierarchyValidator { } private void validateSubcomponentMethods( - ValidationReport.Builder<?> report, + ValidationReport.Builder report, ComponentDescriptor componentDescriptor, - ImmutableMap<TypeElement, TypeElement> existingModuleToOwners) { + ImmutableMap<XTypeElement, XTypeElement> existingModuleToOwners) { componentDescriptor .childComponentsDeclaredByFactoryMethods() .forEach( @@ -97,7 +100,7 @@ final class ComponentHierarchyValidator { validateSubcomponentMethods( report, childComponent, - new ImmutableMap.Builder<TypeElement, TypeElement>() + new ImmutableMap.Builder<XTypeElement, XTypeElement>() .putAll(existingModuleToOwners) .putAll( Maps.toMap( @@ -109,21 +112,21 @@ final class ComponentHierarchyValidator { } private void validateFactoryMethodParameters( - ValidationReport.Builder<?> report, + ValidationReport.Builder report, ComponentMethodDescriptor subcomponentMethodDescriptor, - ImmutableMap<TypeElement, TypeElement> existingModuleToOwners) { - for (VariableElement factoryMethodParameter : + ImmutableMap<XTypeElement, XTypeElement> existingModuleToOwners) { + for (XExecutableParameterElement factoryMethodParameter : subcomponentMethodDescriptor.methodElement().getParameters()) { - TypeElement moduleType = MoreTypes.asTypeElement(factoryMethodParameter.asType()); - TypeElement originatingComponent = existingModuleToOwners.get(moduleType); - if (originatingComponent != null) { + XTypeElement moduleType = factoryMethodParameter.getType().getTypeElement(); + if (existingModuleToOwners.containsKey(moduleType)) { /* Factory method tries to pass a module that is already present in the parent. * This is an error. */ report.addError( String.format( "%s is present in %s. A subcomponent cannot use an instance of a " + "module that differs from its parent.", - moduleType.getSimpleName(), originatingComponent.getQualifiedName()), + getSimpleName(moduleType), + existingModuleToOwners.get(moduleType).getQualifiedName()), factoryMethodParameter); } } @@ -133,7 +136,7 @@ final class ComponentHierarchyValidator { * Checks that components do not have any scopes that are also applied on any of their ancestors. */ private void validateScopeHierarchy( - ValidationReport.Builder<TypeElement> report, + ValidationReport.Builder report, ComponentDescriptor subject, SetMultimap<ComponentDescriptor, Scope> scopesByComponent) { scopesByComponent.putAll(subject, subject.scopes()); @@ -172,7 +175,7 @@ final class ComponentHierarchyValidator { } private void validateProductionModuleUniqueness( - ValidationReport.Builder<TypeElement> report, + ValidationReport.Builder report, ComponentDescriptor componentDescriptor, SetMultimap<ComponentDescriptor, ModuleDescriptor> producerModulesByComponent) { ImmutableSet<ModuleDescriptor> producerModules = @@ -209,7 +212,7 @@ final class ComponentHierarchyValidator { } private void validateRepeatedScopedDeclarations( - ValidationReport.Builder<TypeElement> report, + ValidationReport.Builder report, ComponentDescriptor component, // TODO(ronshapiro): optimize ModuleDescriptor.hashCode()/equals. Otherwise this could be // quite costly @@ -264,10 +267,10 @@ final class ComponentHierarchyValidator { } private ImmutableSet<Scope> moduleScopes(ModuleDescriptor module) { - return FluentIterable.concat(module.allBindingDeclarations()) - .transform(declaration -> uniqueScopeOf(declaration.bindingElement().get())) + return module.allBindingDeclarations().stream() + .map(declaration -> injectionAnnotations.getScope(declaration.bindingElement().get())) .filter(scope -> scope.isPresent() && !scope.get().isReusable()) - .transform(scope -> scope.get()) - .toSet(); + .map(Optional::get) + .collect(toImmutableSet()); } } diff --git a/java/dagger/internal/codegen/validation/ComponentValidator.java b/java/dagger/internal/codegen/validation/ComponentValidator.java index 72a44f251..9f2119ac8 100644 --- a/java/dagger/internal/codegen/validation/ComponentValidator.java +++ b/java/dagger/internal/codegen/validation/ComponentValidator.java @@ -16,38 +16,43 @@ package dagger.internal.codegen.validation; -import static com.google.auto.common.MoreElements.asType; -import static com.google.auto.common.MoreElements.isAnnotationPresent; -import static com.google.auto.common.MoreTypes.asDeclared; -import static com.google.auto.common.MoreTypes.asExecutable; -import static com.google.auto.common.MoreTypes.asTypeElement; +import static androidx.room.compiler.processing.XTypeKt.isVoid; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; import static com.google.common.base.Verify.verify; +import static com.google.common.collect.Iterables.consumingIterable; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Multimaps.asMap; import static com.google.common.collect.Sets.intersection; import static dagger.internal.codegen.base.ComponentAnnotation.anyComponentAnnotation; +import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.creatorAnnotationsFor; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.productionCreatorAnnotations; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.subcomponentCreatorAnnotations; +import static dagger.internal.codegen.base.ComponentKind.annotationsFor; import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation; +import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotations; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.creatorAnnotationsFor; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.productionCreatorAnnotations; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.subcomponentCreatorAnnotations; -import static dagger.internal.codegen.binding.ComponentKind.annotationsFor; import static dagger.internal.codegen.binding.ConfigurationAnnotations.enclosedAnnotatedTypes; -import static dagger.internal.codegen.binding.ConfigurationAnnotations.getTransitiveModules; import static dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages.builderMethodRequiresNoArgs; import static dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages.moreThanOneRefToSubcomponent; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror; -import static dagger.internal.codegen.langmodel.DaggerElements.getAnyAnnotation; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.getAnyAnnotation; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XTypeElements.getAllUnimplementedMethods; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import static java.util.Comparator.comparing; -import static javax.lang.model.element.ElementKind.CLASS; -import static javax.lang.model.element.ElementKind.INTERFACE; -import static javax.lang.model.element.Modifier.ABSTRACT; -import static javax.lang.model.type.TypeKind.VOID; import static javax.lang.model.util.ElementFilter.methodsIn; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -55,76 +60,75 @@ import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Maps; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; import dagger.Component; -import dagger.Reusable; import dagger.internal.codegen.base.ClearableCache; import dagger.internal.codegen.base.ComponentAnnotation; -import dagger.internal.codegen.binding.ComponentKind; +import dagger.internal.codegen.base.ComponentKind; +import dagger.internal.codegen.base.DaggerSuperficialValidation; +import dagger.internal.codegen.base.ModuleKind; import dagger.internal.codegen.binding.DependencyRequestFactory; import dagger.internal.codegen.binding.ErrorMessages; import dagger.internal.codegen.binding.MethodSignatureFormatter; -import dagger.internal.codegen.binding.ModuleKind; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.DependencyRequest; -import dagger.model.Key; -import dagger.producers.CancellationPolicy; -import dagger.producers.ProductionComponent; -import java.lang.annotation.Annotation; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; +import java.util.ArrayDeque; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Queue; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -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.ExecutableType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVisitor; -import javax.lang.model.util.SimpleTypeVisitor8; +import javax.lang.model.SourceVersion; /** * Performs superficial validation of the contract of the {@link Component} and {@link - * ProductionComponent} annotations. + * dagger.producers.ProductionComponent} annotations. */ @Singleton public final class ComponentValidator implements ClearableCache { + private final XProcessingEnv processingEnv; private final DaggerElements elements; - private final DaggerTypes types; private final ModuleValidator moduleValidator; private final ComponentCreatorValidator creatorValidator; private final DependencyRequestValidator dependencyRequestValidator; private final MembersInjectionValidator membersInjectionValidator; private final MethodSignatureFormatter methodSignatureFormatter; private final DependencyRequestFactory dependencyRequestFactory; - private final Map<TypeElement, ValidationReport<TypeElement>> reports = new HashMap<>(); + private final DaggerSuperficialValidation superficialValidation; + private final Map<XTypeElement, ValidationReport> reports = new HashMap<>(); + private final KotlinMetadataUtil metadataUtil; @Inject ComponentValidator( + XProcessingEnv processingEnv, DaggerElements elements, - DaggerTypes types, ModuleValidator moduleValidator, ComponentCreatorValidator creatorValidator, DependencyRequestValidator dependencyRequestValidator, MembersInjectionValidator membersInjectionValidator, MethodSignatureFormatter methodSignatureFormatter, - DependencyRequestFactory dependencyRequestFactory) { + DependencyRequestFactory dependencyRequestFactory, + DaggerSuperficialValidation superficialValidation, + KotlinMetadataUtil metadataUtil) { + this.processingEnv = processingEnv; this.elements = elements; - this.types = types; this.moduleValidator = moduleValidator; this.creatorValidator = creatorValidator; this.dependencyRequestValidator = dependencyRequestValidator; this.membersInjectionValidator = membersInjectionValidator; this.methodSignatureFormatter = methodSignatureFormatter; this.dependencyRequestFactory = dependencyRequestFactory; + this.superficialValidation = superficialValidation; + this.metadataUtil = metadataUtil; } @Override @@ -133,24 +137,24 @@ public final class ComponentValidator implements ClearableCache { } /** Validates the given component. */ - public ValidationReport<TypeElement> validate(TypeElement component) { + public ValidationReport validate(XTypeElement component) { return reentrantComputeIfAbsent(reports, component, this::validateUncached); } - private ValidationReport<TypeElement> validateUncached(TypeElement component) { + private ValidationReport validateUncached(XTypeElement component) { return new ElementValidator(component).validateElement(); } private class ElementValidator { - private final TypeElement component; - private final ValidationReport.Builder<TypeElement> report; + private final XTypeElement component; + private final ValidationReport.Builder report; private final ImmutableSet<ComponentKind> componentKinds; // Populated by ComponentMethodValidators - private final SetMultimap<Element, ExecutableElement> referencedSubcomponents = + private final SetMultimap<XTypeElement, XMethodElement> referencedSubcomponents = LinkedHashMultimap.create(); - ElementValidator(TypeElement component) { + ElementValidator(XTypeElement component) { this.component = component; this.report = ValidationReport.about(component); this.componentKinds = ComponentKind.getComponentKinds(component); @@ -161,14 +165,10 @@ public final class ComponentValidator implements ClearableCache { } private ComponentAnnotation componentAnnotation() { - return anyComponentAnnotation(component).get(); + return anyComponentAnnotation(component, superficialValidation).get(); } - private DeclaredType componentType() { - return asDeclared(component.asType()); - } - - ValidationReport<TypeElement> validateElement() { + ValidationReport validateElement() { if (componentKinds.size() > 1) { return moreThanOneComponentAnnotation(); } @@ -187,7 +187,7 @@ public final class ComponentValidator implements ClearableCache { return report.build(); } - private ValidationReport<TypeElement> moreThanOneComponentAnnotation() { + private ValidationReport moreThanOneComponentAnnotation() { String error = "Components may not be annotated with more than one component annotation: found " + annotationsFor(componentKinds); @@ -196,8 +196,7 @@ public final class ComponentValidator implements ClearableCache { } private void validateUseOfCancellationPolicy() { - if (isAnnotationPresent(component, CancellationPolicy.class) - && !componentKind().isProducer()) { + if (component.hasAnnotation(TypeNames.CANCELLATION_POLICY) && !componentKind().isProducer()) { report.addError( "@CancellationPolicy may only be applied to production components and subcomponents", component); @@ -205,23 +204,19 @@ public final class ComponentValidator implements ClearableCache { } private void validateIsAbstractType() { - if (!component.getKind().equals(INTERFACE) - && !(component.getKind().equals(CLASS) && component.getModifiers().contains(ABSTRACT))) { + if (!component.isInterface() && !(component.isClass() && component.isAbstract())) { report.addError( String.format( "@%s may only be applied to an interface or abstract class", - componentKind().annotation().getSimpleName()), + componentKind().annotation().simpleName()), component); } } private void validateCreators() { - ImmutableList<DeclaredType> creators = - creatorAnnotationsFor(componentAnnotation()).stream() - .flatMap(annotation -> enclosedAnnotatedTypes(component, annotation).stream()) - .collect(toImmutableList()); - creators.forEach( - creator -> report.addSubreport(creatorValidator.validate(asTypeElement(creator)))); + ImmutableSet<XTypeElement> creators = + enclosedAnnotatedTypes(component, creatorAnnotationsFor(componentAnnotation())); + creators.forEach(creator -> report.addSubreport(creatorValidator.validate(creator))); if (creators.size() > 1) { report.addError( String.format( @@ -231,32 +226,44 @@ public final class ComponentValidator implements ClearableCache { } private void validateNoReusableAnnotation() { - Optional<AnnotationMirror> reusableAnnotation = - getAnnotationMirror(component, Reusable.class); - if (reusableAnnotation.isPresent()) { + if (component.hasAnnotation(TypeNames.REUSABLE)) { report.addError( "@Reusable cannot be applied to components or subcomponents", component, - reusableAnnotation.get()); + component.getAnnotation(TypeNames.REUSABLE)); } } private void validateComponentMethods() { - elements.getUnimplementedMethods(component).stream() + validateClassMethodName(); + getAllUnimplementedMethods(component).stream() .map(ComponentMethodValidator::new) .forEachOrdered(ComponentMethodValidator::validateMethod); } + private void validateClassMethodName() { + if (metadataUtil.hasMetadata(toJavac(component))) { + metadataUtil + .getAllMethodNamesBySignature(toJavac(component)) + .forEach( + (signature, name) -> { + if (SourceVersion.isKeyword(name)) { + report.addError("Can not use a Java keyword as method name: " + signature); + } + }); + } + } + private class ComponentMethodValidator { - private final ExecutableElement method; - private final ExecutableType resolvedMethod; - private final List<? extends TypeMirror> parameterTypes; - private final List<? extends VariableElement> parameters; - private final TypeMirror returnType; + private final XMethodElement method; + private final XMethodType resolvedMethod; + private final List<XType> parameterTypes; + private final List<XExecutableParameterElement> parameters; + private final XType returnType; - ComponentMethodValidator(ExecutableElement method) { + ComponentMethodValidator(XMethodElement method) { this.method = method; - this.resolvedMethod = asExecutable(types.asMemberOf(componentType(), method)); + this.resolvedMethod = method.asMemberOf(component.getType()); this.parameterTypes = resolvedMethod.getParameterTypes(); this.parameters = method.getParameters(); this.returnType = resolvedMethod.getReturnType(); @@ -268,7 +275,7 @@ public final class ComponentValidator implements ClearableCache { // abstract methods are ones we have to implement, so they each need to be validated // first, check the return type. if it's a subcomponent, validate that method as // such. - Optional<AnnotationMirror> subcomponentAnnotation = subcomponentAnnotation(); + Optional<ComponentAnnotation> subcomponentAnnotation = legalSubcomponentAnnotation(); if (subcomponentAnnotation.isPresent()) { validateSubcomponentFactoryMethod(subcomponentAnnotation.get()); } else if (subcomponentCreatorAnnotation().isPresent()) { @@ -290,20 +297,25 @@ public final class ComponentValidator implements ClearableCache { } private void validateNoTypeVariables() { - if (!resolvedMethod.getTypeVariables().isEmpty()) { + if (!resolvedMethod.getTypeVariableNames().isEmpty()) { report.addError("Component methods cannot have type variables", method); } } - private Optional<AnnotationMirror> subcomponentAnnotation() { - return checkForAnnotations( - returnType, - componentKind().legalSubcomponentKinds().stream() - .map(ComponentKind::annotation) - .collect(toImmutableSet())); + private Optional<ComponentAnnotation> legalSubcomponentAnnotation() { + return Optional.ofNullable(returnType.getTypeElement()) + .flatMap(element -> subcomponentAnnotation(element, superficialValidation)) + // TODO(bcorso): Consider failing on illegal subcomponents rather than just filtering. + .filter(annotation -> legalSubcomponentAnnotations().contains(annotation.className())); } - private Optional<AnnotationMirror> subcomponentCreatorAnnotation() { + private ImmutableSet<ClassName> legalSubcomponentAnnotations() { + return componentKind().legalSubcomponentKinds().stream() + .map(ComponentKind::annotation) + .collect(toImmutableSet()); + } + + private Optional<XAnnotation> subcomponentCreatorAnnotation() { return checkForAnnotations( returnType, componentAnnotation().isProduction() @@ -311,64 +323,46 @@ public final class ComponentValidator implements ClearableCache { : subcomponentCreatorAnnotations()); } - private void validateSubcomponentFactoryMethod(AnnotationMirror subcomponentAnnotation) { - referencedSubcomponents.put(MoreTypes.asElement(returnType), method); + private void validateSubcomponentFactoryMethod(ComponentAnnotation subcomponentAnnotation) { + referencedSubcomponents.put(returnType.getTypeElement(), method); - ComponentKind subcomponentKind = - ComponentKind.forAnnotatedElement(MoreTypes.asTypeElement(returnType)).get(); - ImmutableSet<TypeElement> moduleTypes = - ComponentAnnotation.componentAnnotation(subcomponentAnnotation).modules(); + ImmutableSet<ClassName> legalModuleAnnotations = + ComponentKind.forAnnotatedElement(returnType.getTypeElement()) + .get() + .legalModuleKinds() + .stream() + .map(ModuleKind::annotation) + .collect(toImmutableSet()); + ImmutableSet<XTypeElement> moduleTypes = subcomponentAnnotation.modules(); // TODO(gak): This logic maybe/probably shouldn't live here as it requires us to traverse // subcomponents and their modules separately from how it is done in ComponentDescriptor and // ModuleDescriptor - @SuppressWarnings("deprecation") - ImmutableSet<TypeElement> transitiveModules = - getTransitiveModules(types, elements, moduleTypes); - - Set<TypeElement> variableTypes = Sets.newHashSet(); + ImmutableSet<XTypeElement> transitiveModules = getTransitiveModules(moduleTypes); + Set<XTypeElement> referencedModules = Sets.newHashSet(); for (int i = 0; i < parameterTypes.size(); i++) { - VariableElement parameter = parameters.get(i); - TypeMirror parameterType = parameterTypes.get(i); - Optional<TypeElement> moduleType = - parameterType.accept( - new SimpleTypeVisitor8<Optional<TypeElement>, Void>() { - @Override - protected Optional<TypeElement> defaultAction(TypeMirror e, Void p) { - return Optional.empty(); - } - - @Override - public Optional<TypeElement> visitDeclared(DeclaredType t, Void p) { - for (ModuleKind moduleKind : subcomponentKind.legalModuleKinds()) { - if (isAnnotationPresent(t.asElement(), moduleKind.annotation())) { - return Optional.of(MoreTypes.asTypeElement(t)); - } - } - return Optional.empty(); - } - }, - null); - if (moduleType.isPresent()) { - if (variableTypes.contains(moduleType.get())) { + XExecutableParameterElement parameter = parameters.get(i); + XType parameterType = parameterTypes.get(i); + if (checkForAnnotations(parameterType, legalModuleAnnotations).isPresent()) { + XTypeElement module = parameterType.getTypeElement(); + if (referencedModules.contains(module)) { report.addError( String.format( - "A module may only occur once an an argument in a Subcomponent factory " + "A module may only occur once as an argument in a Subcomponent factory " + "method, but %s was already passed.", - moduleType.get().getQualifiedName()), + module.getQualifiedName()), parameter); } - if (!transitiveModules.contains(moduleType.get())) { + if (!transitiveModules.contains(module)) { report.addError( String.format( "%s is present as an argument to the %s factory method, but is not one of the" + " modules used to implement the subcomponent.", - moduleType.get().getQualifiedName(), - MoreTypes.asTypeElement(returnType).getQualifiedName()), + module.getQualifiedName(), returnType.getTypeElement().getQualifiedName()), method); } - variableTypes.add(moduleType.get()); + referencedModules.add(module); } else { report.addError( String.format( @@ -379,14 +373,52 @@ public final class ComponentValidator implements ClearableCache { } } + /** + * Returns the full set of modules transitively included from the given seed modules, which + * includes all transitive {@link Module#includes} and all transitive super classes. If a + * module is malformed and a type listed in {@link Module#includes} is not annotated with + * {@link Module}, it is ignored. + */ + private ImmutableSet<XTypeElement> getTransitiveModules( + Collection<XTypeElement> seedModules) { + Set<XTypeElement> processedElements = Sets.newLinkedHashSet(); + Queue<XTypeElement> moduleQueue = new ArrayDeque<>(seedModules); + ImmutableSet.Builder<XTypeElement> moduleElements = ImmutableSet.builder(); + for (XTypeElement moduleElement : consumingIterable(moduleQueue)) { + if (processedElements.add(moduleElement)) { + moduleAnnotation(moduleElement, superficialValidation) + .ifPresent( + moduleAnnotation -> { + moduleElements.add(moduleElement); + moduleQueue.addAll(moduleAnnotation.includes()); + moduleQueue.addAll(includesFromSuperclasses(moduleElement)); + }); + } + } + return moduleElements.build(); + } + + /** Returns {@link Module#includes()} from all transitive super classes. */ + private ImmutableSet<XTypeElement> includesFromSuperclasses(XTypeElement element) { + ImmutableSet.Builder<XTypeElement> builder = ImmutableSet.builder(); + XType superclass = element.getSuperType(); + while (superclass != null && !TypeName.OBJECT.equals(superclass.getTypeName())) { + element = superclass.getTypeElement(); + moduleAnnotation(element, superficialValidation) + .ifPresent(moduleAnnotation -> builder.addAll(moduleAnnotation.includes())); + superclass = element.getSuperType(); + } + return builder.build(); + } + private void validateSubcomponentCreatorMethod() { - referencedSubcomponents.put(MoreTypes.asElement(returnType).getEnclosingElement(), method); + referencedSubcomponents.put(returnType.getTypeElement().getEnclosingTypeElement(), method); if (!parameters.isEmpty()) { report.addError(builderMethodRequiresNoArgs(), method); } - TypeElement creatorElement = MoreTypes.asTypeElement(returnType); + XTypeElement creatorElement = returnType.getTypeElement(); // TODO(sameb): The creator validator right now assumes the element is being compiled // in this pass, which isn't true here. We should change error messages to spit out // this method as the subject and add the original subject to the message output. @@ -398,10 +430,10 @@ public final class ComponentValidator implements ClearableCache { } private void validateMembersInjectionMethod() { - TypeMirror parameterType = getOnlyElement(parameterTypes); + XType parameterType = getOnlyElement(parameterTypes); report.addSubreport( membersInjectionValidator.validateMembersInjectionMethod(method, parameterType)); - if (!(returnType.getKind().equals(VOID) || types.isSameType(returnType, parameterType))) { + if (!(isVoid(returnType) || returnType.isSameType(parameterType))) { report.addError( "Members injection methods may only return the injected type or void.", method); } @@ -418,38 +450,40 @@ public final class ComponentValidator implements ClearableCache { private void validateNoConflictingEntryPoints() { // Collect entry point methods that are not overridden by others. If the "same" method is // inherited from more than one supertype, each will be in the multimap. - SetMultimap<String, ExecutableElement> entryPointMethods = HashMultimap.create(); - - methodsIn(elements.getAllMembers(component)).stream() - .filter( - method -> - isEntryPoint(method, asExecutable(types.asMemberOf(componentType(), method)))) + SetMultimap<String, XMethodElement> entryPoints = HashMultimap.create(); + + // TODO(b/201729320): There's a bug in auto-common's MoreElements#overrides(), b/201729320, + // which prevents us from using XTypeElement#getAllMethods() here (since that method relies on + // MoreElements#overrides() under the hood). + // + // There's two options here. + // 1. Fix the bug in auto-common and update XProcessing's auto-common dependency + // 2. Add a new method in XProcessing which relies on Elements#overrides(), which does not + // have this issue. However, this approach risks causing issues for EJC (Eclipse) users. + methodsIn(elements.getAllMembers(toJavac(component))).stream() + .map(method -> asMethod(toXProcessing(method, processingEnv))) + .filter(method -> isEntryPoint(method, method.asMemberOf(component.getType()))) .forEach( - method -> - addMethodUnlessOverridden( - method, entryPointMethods.get(method.getSimpleName().toString()))); + method -> addMethodUnlessOverridden(method, entryPoints.get(getSimpleName(method)))); - for (Set<ExecutableElement> methods : asMap(entryPointMethods).values()) { - if (distinctKeys(methods).size() > 1) { - reportConflictingEntryPoints(methods); - } - } + asMap(entryPoints).values().stream() + .filter(methods -> distinctKeys(methods).size() > 1) + .forEach(this::reportConflictingEntryPoints); } - private void reportConflictingEntryPoints(Collection<ExecutableElement> methods) { + private void reportConflictingEntryPoints(Collection<XMethodElement> methods) { verify( - methods.stream().map(ExecutableElement::getEnclosingElement).distinct().count() + methods.stream().map(XMethodElement::getEnclosingElement).distinct().count() == methods.size(), "expected each method to be declared on a different type: %s", methods); StringBuilder message = new StringBuilder("conflicting entry point declarations:"); methodSignatureFormatter - .typedFormatter(componentType()) + .typedFormatter(component.getType()) .formatIndentedList( message, ImmutableList.sortedCopyOf( - comparing( - method -> asType(method.getEnclosingElement()).getQualifiedName().toString()), + comparing(method -> method.getEnclosingElement().getClassName().canonicalName()), methods), 1); report.addError(message.toString()); @@ -465,8 +499,12 @@ public final class ComponentValidator implements ClearableCache { } private void validateComponentDependencies() { - for (TypeMirror type : componentAnnotation().dependencyTypes()) { - type.accept(CHECK_DEPENDENCY_TYPES, report); + for (XType type : componentAnnotation().dependencyTypes()) { + if (!isDeclared(type)) { + report.addError(type + " is not a valid component dependency type"); + } else if (type.getTypeElement().hasAnyAnnotation(moduleAnnotations())) { + report.addError(type + " is a module, which cannot be a component dependency"); + } } } @@ -481,35 +519,34 @@ public final class ComponentValidator implements ClearableCache { private void validateSubcomponents() { // Make sure we validate any subcomponents we're referencing. - for (Element subcomponent : referencedSubcomponents.keySet()) { - ValidationReport<TypeElement> subreport = validate(asType(subcomponent)); - report.addSubreport(subreport); - } + referencedSubcomponents + .keySet() + .forEach(subcomponent -> report.addSubreport(validate(subcomponent))); } - private ImmutableSet<Key> distinctKeys(Set<ExecutableElement> methods) { + private ImmutableSet<Key> distinctKeys(Set<XMethodElement> methods) { return methods.stream() .map(this::dependencyRequest) .map(DependencyRequest::key) .collect(toImmutableSet()); } - private DependencyRequest dependencyRequest(ExecutableElement method) { - ExecutableType methodType = asExecutable(types.asMemberOf(componentType(), method)); - return ComponentKind.forAnnotatedElement(component).get().isProducer() + private DependencyRequest dependencyRequest(XMethodElement method) { + XMethodType methodType = method.asMemberOf(component.getType()); + return componentKind().isProducer() ? dependencyRequestFactory.forComponentProductionMethod(method, methodType) : dependencyRequestFactory.forComponentProvisionMethod(method, methodType); } } - private static boolean isEntryPoint(ExecutableElement method, ExecutableType methodType) { - return method.getModifiers().contains(ABSTRACT) + private static boolean isEntryPoint(XMethodElement method, XMethodType methodType) { + return method.isAbstract() && method.getParameters().isEmpty() - && !methodType.getReturnType().getKind().equals(VOID) - && methodType.getTypeVariables().isEmpty(); + && !isVoid(methodType.getReturnType()) + && methodType.getTypeVariableNames().isEmpty(); } - private void addMethodUnlessOverridden(ExecutableElement method, Set<ExecutableElement> methods) { + private void addMethodUnlessOverridden(XMethodElement method, Set<XMethodElement> methods) { if (methods.stream().noneMatch(existingMethod -> overridesAsDeclared(existingMethod, method))) { methods.removeIf(existingMethod -> overridesAsDeclared(method, existingMethod)); methods.add(method); @@ -521,36 +558,15 @@ public final class ComponentValidator implements ClearableCache { * the type that declares {@code overrider}. */ // TODO(dpb): Does this break for ECJ? - private boolean overridesAsDeclared(ExecutableElement overrider, ExecutableElement overridden) { - return elements.overrides(overrider, overridden, asType(overrider.getEnclosingElement())); + private boolean overridesAsDeclared(XMethodElement overrider, XMethodElement overridden) { + return elements.overrides( + toJavac(overrider), + toJavac(overridden), + toJavac(asTypeElement(overrider.getEnclosingElement()))); } - private static final TypeVisitor<Void, ValidationReport.Builder<?>> CHECK_DEPENDENCY_TYPES = - new SimpleTypeVisitor8<Void, ValidationReport.Builder<?>>() { - @Override - protected Void defaultAction(TypeMirror type, ValidationReport.Builder<?> report) { - report.addError(type + " is not a valid component dependency type"); - return null; - } - - @Override - public Void visitDeclared(DeclaredType type, ValidationReport.Builder<?> report) { - if (moduleAnnotation(MoreTypes.asTypeElement(type)).isPresent()) { - report.addError(type + " is a module, which cannot be a component dependency"); - } - return null; - } - }; - - private static Optional<AnnotationMirror> checkForAnnotations( - TypeMirror type, final Set<? extends Class<? extends Annotation>> annotations) { - return type.accept( - new SimpleTypeVisitor8<Optional<AnnotationMirror>, Void>(Optional.empty()) { - @Override - public Optional<AnnotationMirror> visitDeclared(DeclaredType t, Void p) { - return getAnyAnnotation(t.asElement(), annotations); - } - }, - null); + private static Optional<XAnnotation> checkForAnnotations(XType type, Set<ClassName> annotations) { + return Optional.ofNullable(type.getTypeElement()) + .flatMap(typeElement -> getAnyAnnotation(typeElement, annotations)); } } diff --git a/java/dagger/internal/codegen/validation/CompositeBindingGraphPlugin.java b/java/dagger/internal/codegen/validation/CompositeBindingGraphPlugin.java index a9c650a1b..63bf32b54 100644 --- a/java/dagger/internal/codegen/validation/CompositeBindingGraphPlugin.java +++ b/java/dagger/internal/codegen/validation/CompositeBindingGraphPlugin.java @@ -22,17 +22,17 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Lists.asList; import static dagger.internal.codegen.base.ElementFormatter.elementToString; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import static dagger.internal.codegen.langmodel.DaggerElements.elementEncloses; +import static dagger.internal.codegen.langmodel.DaggerElements.transitivelyEncloses; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.FormatMethod; -import dagger.model.BindingGraph; -import dagger.model.BindingGraph.ChildFactoryMethodEdge; -import dagger.model.BindingGraph.ComponentNode; -import dagger.model.BindingGraph.DependencyEdge; -import dagger.model.BindingGraph.MaybeBinding; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge; +import dagger.spi.model.BindingGraph.ComponentNode; +import dagger.spi.model.BindingGraph.DependencyEdge; +import dagger.spi.model.BindingGraph.MaybeBinding; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.DiagnosticReporter; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -216,16 +216,18 @@ public final class CompositeBindingGraphPlugin implements BindingGraphPlugin { String message) { // TODO(erichang): This repeats some of the logic in DiagnosticReporterImpl. Remove when // merged. - if (elementEncloses( - graph.rootComponentNode().componentPath().currentComponent(), - childFactoryMethodEdge.factoryMethod())) { + if (transitivelyEncloses( + graph.rootComponentNode().componentPath().currentComponent().java(), + childFactoryMethodEdge.factoryMethod().java())) { // Let this pass through since it is not an error reported on the root component delegate.reportSubcomponentFactoryMethod(diagnosticKind, childFactoryMethodEdge, message); } else { addMessage( diagnosticKind, String.format( - "[%s] %s", elementToString(childFactoryMethodEdge.factoryMethod()), message)); + "[%s] %s", + elementToString(childFactoryMethodEdge.factoryMethod().java()), + message)); } } diff --git a/java/dagger/internal/codegen/validation/DependencyRequestValidator.java b/java/dagger/internal/codegen/validation/DependencyRequestValidator.java index f28049753..45b77631a 100644 --- a/java/dagger/internal/codegen/validation/DependencyRequestValidator.java +++ b/java/dagger/internal/codegen/validation/DependencyRequestValidator.java @@ -16,55 +16,54 @@ package dagger.internal.codegen.validation; -import static com.google.auto.common.MoreElements.asType; -import static com.google.auto.common.MoreElements.asVariable; -import static com.google.auto.common.MoreTypes.asTypeElement; +import static androidx.room.compiler.processing.XElementKt.isField; +import static androidx.room.compiler.processing.XElementKt.isTypeElement; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static dagger.internal.codegen.base.RequestKinds.extractKeyType; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedInjectionType; import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; -import static javax.lang.model.element.Modifier.STATIC; -import static javax.lang.model.type.TypeKind.WILDCARD; - -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; -import com.google.common.collect.ImmutableCollection; -import dagger.MembersInjector; -import dagger.assisted.Assisted; +import static dagger.internal.codegen.xprocessing.XElements.asField; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; +import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; + +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFieldElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.XVariableElement; +import com.google.common.collect.ImmutableSet; import dagger.internal.codegen.base.FrameworkTypes; import dagger.internal.codegen.base.RequestKinds; import dagger.internal.codegen.binding.InjectionAnnotations; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.model.RequestKind; +import dagger.spi.model.RequestKind; import java.util.Optional; import javax.inject.Inject; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; /** Validation for dependency requests. */ final class DependencyRequestValidator { + private final XProcessingEnv processingEnv; private final MembersInjectionValidator membersInjectionValidator; private final InjectionAnnotations injectionAnnotations; private final KotlinMetadataUtil metadataUtil; - private final DaggerElements elements; @Inject DependencyRequestValidator( + XProcessingEnv processingEnv, MembersInjectionValidator membersInjectionValidator, InjectionAnnotations injectionAnnotations, - KotlinMetadataUtil metadataUtil, - DaggerElements elements) { + KotlinMetadataUtil metadataUtil) { + this.processingEnv = processingEnv; this.membersInjectionValidator = membersInjectionValidator; this.injectionAnnotations = injectionAnnotations; this.metadataUtil = metadataUtil; - this.elements = elements; } /** @@ -72,15 +71,16 @@ final class DependencyRequestValidator { * non-instance request with a wildcard type. */ void validateDependencyRequest( - ValidationReport.Builder<?> report, Element requestElement, TypeMirror requestType) { - if (MoreElements.isAnnotationPresent(requestElement, Assisted.class)) { + ValidationReport.Builder report, XElement requestElement, XType requestType) { + if (requestElement.hasAnnotation(TypeNames.ASSISTED)) { // Don't validate assisted parameters. These are not dependency requests. return; } if (missingQualifierMetadata(requestElement)) { report.addError( - "Unable to read annotations on an injected Kotlin property. The Dagger compiler must" - + " also be applied to any project containing @Inject properties.", + "Unable to read annotations on an injected Kotlin property. " + + "The Dagger compiler must also be applied to any project containing @Inject " + + "properties.", requestElement); // Skip any further validation if we don't have valid metadata for a type that needs it. @@ -91,36 +91,36 @@ final class DependencyRequestValidator { } /** Returns {@code true} if a kotlin inject field is missing metadata about its qualifiers. */ - private boolean missingQualifierMetadata(Element requestElement) { - if (requestElement.getKind() == ElementKind.FIELD - // static injected fields are not supported, no need to get qualifier from kotlin metadata - && !requestElement.getModifiers().contains(STATIC) - && metadataUtil.hasMetadata(requestElement) - && metadataUtil.isMissingSyntheticPropertyForAnnotations(asVariable(requestElement))) { - Optional<TypeElement> membersInjector = - Optional.ofNullable( - elements.getTypeElement( - membersInjectorNameForType(asType(requestElement.getEnclosingElement())))); - return !membersInjector.isPresent(); + private boolean missingQualifierMetadata(XElement requestElement) { + if (isField(requestElement)) { + XFieldElement fieldElement = asField(requestElement); + // static/top-level injected fields are not supported, + // so no need to get qualifier from kotlin metadata + if ((!fieldElement.isStatic() || !isTypeElement(fieldElement.getEnclosingElement())) + && metadataUtil.hasMetadata(toJavac(fieldElement)) + && metadataUtil.isMissingSyntheticPropertyForAnnotations(toJavac(fieldElement))) { + Optional<XTypeElement> membersInjector = + Optional.ofNullable( + processingEnv.findTypeElement( + membersInjectorNameForType(asTypeElement(fieldElement.getEnclosingElement())))); + return !membersInjector.isPresent(); + } } return false; } private final class Validator { - private final ValidationReport.Builder<?> report; - private final Element requestElement; - private final TypeMirror requestType; - private final TypeMirror keyType; - private final RequestKind requestKind; - private final ImmutableCollection<? extends AnnotationMirror> qualifiers; - + private final ValidationReport.Builder report; + private final XElement requestElement; + private final XType requestType; + private final XType keyType; + private final ImmutableSet<XAnnotation> qualifiers; - Validator(ValidationReport.Builder<?> report, Element requestElement, TypeMirror requestType) { + Validator(ValidationReport.Builder report, XElement requestElement, XType requestType) { this.report = report; this.requestElement = requestElement; this.requestType = requestType; this.keyType = extractKeyType(requestType); - this.requestKind = RequestKinds.getRequestKind(requestType); this.qualifiers = injectionAnnotations.getQualifiers(requestElement); } @@ -131,7 +131,7 @@ final class DependencyRequestValidator { private void checkQualifiers() { if (qualifiers.size() > 1) { - for (AnnotationMirror qualifier : qualifiers) { + for (XAnnotation qualifier : qualifiers) { report.addError( "A single dependency request may not use more than one @Qualifier", requestElement, @@ -141,8 +141,8 @@ final class DependencyRequestValidator { } private void checkType() { - if (qualifiers.isEmpty() && keyType.getKind() == TypeKind.DECLARED) { - TypeElement typeElement = asTypeElement(keyType); + if (qualifiers.isEmpty() && isDeclared(keyType)) { + XTypeElement typeElement = keyType.getTypeElement(); if (isAssistedInjectionType(typeElement)) { report.addError( "Dagger does not support injecting @AssistedInject type, " @@ -150,15 +150,17 @@ final class DependencyRequestValidator { + ". Did you mean to inject its assisted factory type instead?", requestElement); } - if (requestKind != RequestKind.INSTANCE && isAssistedFactoryType(typeElement)) { + RequestKind requestKind = RequestKinds.getRequestKind(requestType); + if (!(requestKind == RequestKind.INSTANCE || requestKind == RequestKind.PROVIDER) + && isAssistedFactoryType(typeElement)) { report.addError( - "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, " + "Dagger does not support injecting Lazy<T>, Producer<T>, " + "or Produced<T> when T is an @AssistedFactory-annotated type such as " + keyType, requestElement); } } - if (keyType.getKind().equals(WILDCARD)) { + if (isWildcard(keyType)) { // TODO(ronshapiro): Explore creating this message using RequestKinds. report.addError( "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, " @@ -166,14 +168,13 @@ final class DependencyRequestValidator { + keyType, requestElement); } - if (MoreTypes.isType(keyType) && MoreTypes.isTypeOf(MembersInjector.class, keyType)) { - DeclaredType membersInjectorType = MoreTypes.asDeclared(keyType); - if (membersInjectorType.getTypeArguments().isEmpty()) { + if (isTypeOf(keyType, TypeNames.MEMBERS_INJECTOR)) { + if (keyType.getTypeArguments().isEmpty()) { report.addError("Cannot inject a raw MembersInjector", requestElement); } else { report.addSubreport( membersInjectionValidator.validateMembersInjectionRequest( - requestElement, membersInjectorType.getTypeArguments().get(0))); + requestElement, keyType.getTypeArguments().get(0))); } } } @@ -186,13 +187,13 @@ final class DependencyRequestValidator { * <p>Only call this when processing a provision binding. */ // TODO(dpb): Should we disallow Producer entry points in non-production components? - void checkNotProducer(ValidationReport.Builder<?> report, VariableElement requestElement) { - TypeMirror requestType = requestElement.asType(); + void checkNotProducer(ValidationReport.Builder report, XVariableElement requestElement) { + XType requestType = requestElement.getType(); if (FrameworkTypes.isProducerType(requestType)) { report.addError( String.format( "%s may only be injected in @Produces methods", - MoreTypes.asTypeElement(requestType).getSimpleName()), + getSimpleName(requestType.getTypeElement())), requestElement); } } diff --git a/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java b/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java index 7d7b9e3a9..dca74a68f 100644 --- a/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java +++ b/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java @@ -48,12 +48,14 @@ import dagger.internal.codegen.base.ElementFormatter; import dagger.internal.codegen.base.Formatter; import dagger.internal.codegen.binding.DependencyRequestFormatter; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.BindingGraph; -import dagger.model.BindingGraph.DependencyEdge; -import dagger.model.BindingGraph.Edge; -import dagger.model.BindingGraph.MaybeBinding; -import dagger.model.BindingGraph.Node; -import dagger.model.ComponentPath; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraph.DependencyEdge; +import dagger.spi.model.BindingGraph.Edge; +import dagger.spi.model.BindingGraph.MaybeBinding; +import dagger.spi.model.BindingGraph.Node; +import dagger.spi.model.ComponentPath; +import dagger.spi.model.DaggerElement; import java.util.Comparator; import java.util.Set; import java.util.function.Function; @@ -149,7 +151,7 @@ public final class DiagnosticMessageGenerator { dependencyTrace = ImmutableList.of(dependencyEdge); } else { // It's not an entry point, so it's part of a binding - dagger.model.Binding binding = (dagger.model.Binding) source(dependencyEdge); + Binding binding = (Binding) source(dependencyEdge); entryPoints = graph.entryPointEdgesDependingOnBinding(binding); dependencyTrace = ImmutableList.<DependencyEdge>builder() @@ -178,7 +180,15 @@ public final class DiagnosticMessageGenerator { appendComponentPathUnlessAtRoot(message, source(getLast(dependencyTrace))); } } + message.append(getRequestsNotInTrace(dependencyTrace, requests, entryPoints)); + return message.toString(); + } + public String getRequestsNotInTrace( + ImmutableList<DependencyEdge> dependencyTrace, + ImmutableSet<DependencyEdge> requests, + ImmutableSet<DependencyEdge> entryPoints) { + StringBuilder message = new StringBuilder(); // Print any dependency requests that aren't shown as part of the dependency trace. ImmutableSet<Element> requestsToPrint = requests.stream() @@ -189,6 +199,7 @@ public final class DiagnosticMessageGenerator { || (!request.isEntryPoint() && !isTracedRequest(dependencyTrace, request))) .map(request -> request.dependencyRequest().requestElement()) .flatMap(presentValues()) + .map(DaggerElement::java) .collect(toImmutableSet()); if (!requestsToPrint.isEmpty()) { message @@ -229,14 +240,14 @@ public final class DiagnosticMessageGenerator { new Formatter<DependencyEdge>() { @Override public String format(DependencyEdge object) { - Element requestElement = object.dependencyRequest().requestElement().get(); + Element requestElement = object.dependencyRequest().requestElement().get().java(); StringBuilder element = new StringBuilder(elementToString(requestElement)); // For entry points declared in subcomponents or supertypes of the root component, // append the component path to make clear to the user which component it's in. ComponentPath componentPath = source(object).componentPath(); if (!componentPath.atRoot() - || !requestElement.getEnclosingElement().equals(componentPath.rootComponent())) { + || !requestElement.getEnclosingElement().equals(componentPath.rootComponent().java())) { element.append(String.format(" [%s]", componentPath)); } return element.toString(); @@ -252,9 +263,9 @@ public final class DiagnosticMessageGenerator { * Returns the dependency trace from one of the {@code entryPoints} to {@code binding} to {@code * message} as a list <i>ending with</i> the entry point. */ - // TODO(ronshapiro): Adding a DependencyPath type to dagger.model could be useful, i.e. + // TODO(ronshapiro): Adding a DependencyPath type to dagger.spi.model could be useful, i.e. // bindingGraph.shortestPathFromEntryPoint(DependencyEdge, MaybeBindingNode) - ImmutableList<DependencyEdge> dependencyTrace( + public ImmutableList<DependencyEdge> dependencyTrace( MaybeBinding binding, ImmutableSet<DependencyEdge> entryPoints) { // Module binding graphs may have bindings unreachable from any entry points. If there are // no entry points for this DiagnosticInfo, don't try to print a dependency trace. @@ -299,7 +310,7 @@ public final class DiagnosticMessageGenerator { } /** Returns all the nonsynthetic dependency requests for a binding. */ - ImmutableSet<DependencyEdge> requests(MaybeBinding binding) { + public ImmutableSet<DependencyEdge> requests(MaybeBinding binding) { return graph.network().inEdges(binding).stream() .flatMap(instancesOf(DependencyEdge.class)) .filter(edge -> edge.dependencyRequest().requestElement().isPresent()) @@ -353,12 +364,12 @@ public final class DiagnosticMessageGenerator { } TypeElement componentContainingEntryPoint(DependencyEdge entryPoint) { - return source(entryPoint).componentPath().currentComponent(); + return source(entryPoint).componentPath().currentComponent().java(); } TypeElement typeDeclaringEntryPoint(DependencyEdge entryPoint) { return MoreElements.asType( - entryPoint.dependencyRequest().requestElement().get().getEnclosingElement()); + entryPoint.dependencyRequest().requestElement().get().java().getEnclosingElement()); } /** @@ -368,7 +379,7 @@ public final class DiagnosticMessageGenerator { Comparator<DependencyEdge> requestEnclosingTypeName() { return comparing( edge -> - closestEnclosingTypeElement(edge.dependencyRequest().requestElement().get()) + closestEnclosingTypeElement(edge.dependencyRequest().requestElement().get().java()) .getQualifiedName() .toString()); } @@ -380,7 +391,8 @@ public final class DiagnosticMessageGenerator { * <p>Only useful to compare edges whose request elements were declared in the same type. */ Comparator<DependencyEdge> requestElementDeclarationOrder() { - return comparing(edge -> edge.dependencyRequest().requestElement().get(), DECLARATION_ORDER); + return comparing( + edge -> edge.dependencyRequest().requestElement().get().java(), DECLARATION_ORDER); } private Node source(Edge edge) { diff --git a/java/dagger/internal/codegen/validation/DiagnosticReporterFactory.java b/java/dagger/internal/codegen/validation/DiagnosticReporterFactory.java index 35ae7c728..32cbf183e 100644 --- a/java/dagger/internal/codegen/validation/DiagnosticReporterFactory.java +++ b/java/dagger/internal/codegen/validation/DiagnosticReporterFactory.java @@ -18,22 +18,21 @@ package dagger.internal.codegen.validation; import static com.google.common.collect.Lists.asList; import static dagger.internal.codegen.base.ElementFormatter.elementToString; -import static dagger.internal.codegen.langmodel.DaggerElements.elementEncloses; +import static dagger.internal.codegen.langmodel.DaggerElements.transitivelyEncloses; import static javax.tools.Diagnostic.Kind.ERROR; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.FormatMethod; -import dagger.model.BindingGraph; -import dagger.model.BindingGraph.ChildFactoryMethodEdge; -import dagger.model.BindingGraph.ComponentNode; -import dagger.model.BindingGraph.DependencyEdge; -import dagger.model.BindingGraph.MaybeBinding; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; -import javax.annotation.processing.Messager; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge; +import dagger.spi.model.BindingGraph.ComponentNode; +import dagger.spi.model.BindingGraph.DependencyEdge; +import dagger.spi.model.BindingGraph.MaybeBinding; +import dagger.spi.model.DiagnosticReporter; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import org.checkerframework.checker.nullness.compatqual.NullableDecl; @@ -41,20 +40,20 @@ import org.checkerframework.checker.nullness.compatqual.NullableDecl; // TODO(ronshapiro): If multiple plugins print errors on the same node/edge, should we condense the // messages and only print the dependency trace once? final class DiagnosticReporterFactory { - private final Messager messager; + private final XMessager messager; private final DiagnosticMessageGenerator.Factory diagnosticMessageGeneratorFactory; @Inject DiagnosticReporterFactory( - Messager messager, DiagnosticMessageGenerator.Factory diagnosticMessageGeneratorFactory) { + XMessager messager, DiagnosticMessageGenerator.Factory diagnosticMessageGeneratorFactory) { this.messager = messager; this.diagnosticMessageGeneratorFactory = diagnosticMessageGeneratorFactory; } /** Creates a reporter for a binding graph and a plugin. */ DiagnosticReporterImpl reporter( - BindingGraph graph, BindingGraphPlugin plugin, boolean reportErrorsAsWarnings) { - return new DiagnosticReporterImpl(graph, plugin.pluginName(), reportErrorsAsWarnings); + BindingGraph graph, String pluginName, boolean reportErrorsAsWarnings) { + return new DiagnosticReporterImpl(graph, pluginName, reportErrorsAsWarnings); } /** @@ -63,7 +62,7 @@ final class DiagnosticReporterFactory { */ final class DiagnosticReporterImpl implements DiagnosticReporter { private final String plugin; - private final TypeElement rootComponent; + private final XTypeElement rootComponent; private final boolean reportErrorsAsWarnings; private final ImmutableSet.Builder<Diagnostic.Kind> reportedDiagnosticKinds = ImmutableSet.builder(); @@ -72,7 +71,8 @@ final class DiagnosticReporterFactory { DiagnosticReporterImpl(BindingGraph graph, String plugin, boolean reportErrorsAsWarnings) { this.plugin = plugin; this.reportErrorsAsWarnings = reportErrorsAsWarnings; - this.rootComponent = graph.rootComponentNode().componentPath().currentComponent(); + this.rootComponent = + graph.rootComponentNode().componentPath().currentComponent().xprocessing(); this.diagnosticMessageGenerator = diagnosticMessageGeneratorFactory.create(graph); } @@ -145,7 +145,7 @@ final class DiagnosticReporterFactory { Diagnostic.Kind diagnosticKind, ChildFactoryMethodEdge childFactoryMethodEdge, String message) { - printMessage(diagnosticKind, message, childFactoryMethodEdge.factoryMethod()); + printMessage(diagnosticKind, message, childFactoryMethodEdge.factoryMethod().xprocessing()); } @Override @@ -163,10 +163,10 @@ final class DiagnosticReporterFactory { return String.format(messageFormat, asList(firstArg, moreArgs).toArray()); } - void printMessage( + private void printMessage( Diagnostic.Kind diagnosticKind, CharSequence message, - @NullableDecl Element elementToReport) { + @NullableDecl XElement elementToReport) { if (diagnosticKind.equals(ERROR) && reportErrorsAsWarnings) { diagnosticKind = Diagnostic.Kind.WARNING; } @@ -174,14 +174,16 @@ final class DiagnosticReporterFactory { StringBuilder fullMessage = new StringBuilder(); appendBracketPrefix(fullMessage, plugin); - // TODO(ronshapiro): should we create a HashSet out of elementEncloses() so we don't - // need to do an O(n) contains() each time? - if (elementToReport != null && !elementEncloses(rootComponent, elementToReport)) { - appendBracketPrefix(fullMessage, elementToString(elementToReport)); - elementToReport = rootComponent; + if (elementToReport == null) { + messager.printMessage(diagnosticKind, fullMessage.append(message).toString()); + } else { + if (!transitivelyEncloses(rootComponent, elementToReport)) { + appendBracketPrefix(fullMessage, elementToString(elementToReport)); + elementToReport = rootComponent; + } + messager.printMessage( + diagnosticKind, fullMessage.append(message).toString(), elementToReport); } - - messager.printMessage(diagnosticKind, fullMessage.append(message), elementToReport); } private void appendBracketPrefix(StringBuilder message, String prefix) { diff --git a/java/dagger/internal/codegen/validation/ExternalBindingGraphConverter.java b/java/dagger/internal/codegen/validation/ExternalBindingGraphConverter.java new file mode 100644 index 000000000..e2618cc48 --- /dev/null +++ b/java/dagger/internal/codegen/validation/ExternalBindingGraphConverter.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2021 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.validation; + +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.graph.EndpointPair; +import com.google.common.graph.ImmutableNetwork; +import com.google.common.graph.MutableNetwork; +import com.google.common.graph.Network; +import com.google.common.graph.NetworkBuilder; +import com.google.errorprone.annotations.FormatMethod; +import dagger.model.Binding; +import dagger.model.BindingGraph; +import dagger.model.BindingGraph.ChildFactoryMethodEdge; +import dagger.model.BindingGraph.ComponentNode; +import dagger.model.BindingGraph.DependencyEdge; +import dagger.model.BindingGraph.Edge; +import dagger.model.BindingGraph.MaybeBinding; +import dagger.model.BindingGraph.MissingBinding; +import dagger.model.BindingGraph.Node; +import dagger.model.BindingGraph.SubcomponentCreatorBindingEdge; +import dagger.model.BindingKind; +import dagger.model.ComponentPath; +import dagger.model.DependencyRequest; +import dagger.model.Key; +import dagger.model.Key.MultibindingContributionIdentifier; +import dagger.model.RequestKind; +import dagger.model.Scope; +import dagger.spi.DiagnosticReporter; +import dagger.spi.model.DaggerAnnotation; +import dagger.spi.model.DaggerElement; +import dagger.spi.model.DaggerTypeElement; +import java.util.Optional; +import javax.tools.Diagnostic; + +/** A Utility class for converting to the {@link BindingGraph} used by external plugins. */ +public final class ExternalBindingGraphConverter { + private ExternalBindingGraphConverter() {} + + /** Returns a {@link DiagnosticReporter} from a {@link dagger.spi.DiagnosticReporter}. */ + public static DiagnosticReporter fromSpiModel(dagger.spi.model.DiagnosticReporter reporter) { + return DiagnosticReporterImpl.create(reporter); + } + + /** Returns a {@link BindingGraph} from a {@link dagger.spi.model.BindingGraph}. */ + public static BindingGraph fromSpiModel(dagger.spi.model.BindingGraph graph) { + return BindingGraphImpl.create(graph); + } + + private static ImmutableNetwork<Node, Edge> fromSpiModel( + Network<dagger.spi.model.BindingGraph.Node, dagger.spi.model.BindingGraph.Edge> spiNetwork) { + MutableNetwork<Node, Edge> network = + NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build(); + + ImmutableMap<dagger.spi.model.BindingGraph.Node, Node> fromSpiNodes = + spiNetwork.nodes().stream() + .collect( + toImmutableMap( + spiNode -> spiNode, + ExternalBindingGraphConverter::fromSpiModel)); + + for (Node node : fromSpiNodes.values()) { + network.addNode(node); + } + for (dagger.spi.model.BindingGraph.Edge edge : spiNetwork.edges()) { + EndpointPair<dagger.spi.model.BindingGraph.Node> edgePair = spiNetwork.incidentNodes(edge); + network.addEdge( + fromSpiNodes.get(edgePair.source()), + fromSpiNodes.get(edgePair.target()), + fromSpiModel(edge)); + } + return ImmutableNetwork.copyOf(network); + } + + private static Node fromSpiModel(dagger.spi.model.BindingGraph.Node node) { + if (node instanceof dagger.spi.model.Binding) { + return BindingNodeImpl.create((dagger.spi.model.Binding) node); + } else if (node instanceof dagger.spi.model.BindingGraph.ComponentNode) { + return ComponentNodeImpl.create((dagger.spi.model.BindingGraph.ComponentNode) node); + } else if (node instanceof dagger.spi.model.BindingGraph.MissingBinding) { + return MissingBindingImpl.create((dagger.spi.model.BindingGraph.MissingBinding) node); + } else { + throw new IllegalStateException("Unhandled node type: " + node.getClass()); + } + } + + private static Edge fromSpiModel(dagger.spi.model.BindingGraph.Edge edge) { + if (edge instanceof dagger.spi.model.BindingGraph.DependencyEdge) { + return DependencyEdgeImpl.create((dagger.spi.model.BindingGraph.DependencyEdge) edge); + } else if (edge instanceof dagger.spi.model.BindingGraph.ChildFactoryMethodEdge) { + return ChildFactoryMethodEdgeImpl.create( + (dagger.spi.model.BindingGraph.ChildFactoryMethodEdge) edge); + } else if (edge instanceof dagger.spi.model.BindingGraph.SubcomponentCreatorBindingEdge) { + return SubcomponentCreatorBindingEdgeImpl.create( + (dagger.spi.model.BindingGraph.SubcomponentCreatorBindingEdge) edge); + } else { + throw new IllegalStateException("Unhandled edge type: " + edge.getClass()); + } + } + + private static MultibindingContributionIdentifier fromSpiModel( + dagger.spi.model.Key.MultibindingContributionIdentifier identifier) { + return new MultibindingContributionIdentifier(identifier.bindingElement(), identifier.module()); + } + + private static Key fromSpiModel(dagger.spi.model.Key key) { + return Key.builder(key.type().java()) + .qualifier(key.qualifier().map(DaggerAnnotation::java)) + .multibindingContributionIdentifier( + key.multibindingContributionIdentifier().isPresent() + ? Optional.of(fromSpiModel(key.multibindingContributionIdentifier().get())) + : Optional.empty()) + .build(); + } + + private static BindingKind fromSpiModel(dagger.spi.model.BindingKind bindingKind) { + return BindingKind.valueOf(bindingKind.name()); + } + + private static RequestKind fromSpiModel(dagger.spi.model.RequestKind requestKind) { + return RequestKind.valueOf(requestKind.name()); + } + + private static DependencyRequest fromSpiModel(dagger.spi.model.DependencyRequest request) { + DependencyRequest.Builder builder = + DependencyRequest.builder() + .kind(fromSpiModel(request.kind())) + .key(fromSpiModel(request.key())) + .isNullable(request.isNullable()); + + request.requestElement().ifPresent(e -> builder.requestElement(e.java())); + return builder.build(); + } + + private static Scope fromSpiModel(dagger.spi.model.Scope scope) { + return Scope.scope(scope.scopeAnnotation().java()); + } + + private static ComponentPath fromSpiModel(dagger.spi.model.ComponentPath path) { + return ComponentPath.create( + path.components().stream().map(DaggerTypeElement::java).collect(toImmutableList())); + } + + private static dagger.spi.model.BindingGraph.ComponentNode toSpiModel( + ComponentNode componentNode) { + return ((ComponentNodeImpl) componentNode).spiDelegate(); + } + + private static dagger.spi.model.BindingGraph.MaybeBinding toSpiModel(MaybeBinding maybeBinding) { + if (maybeBinding instanceof MissingBindingImpl) { + return ((MissingBindingImpl) maybeBinding).spiDelegate(); + } else if (maybeBinding instanceof BindingNodeImpl) { + return ((BindingNodeImpl) maybeBinding).spiDelegate(); + } else { + throw new IllegalStateException("Unhandled binding type: " + maybeBinding.getClass()); + } + } + + private static dagger.spi.model.BindingGraph.DependencyEdge toSpiModel( + DependencyEdge dependencyEdge) { + return ((DependencyEdgeImpl) dependencyEdge).spiDelegate(); + } + + private static dagger.spi.model.BindingGraph.ChildFactoryMethodEdge toSpiModel( + ChildFactoryMethodEdge childFactoryMethodEdge) { + return ((ChildFactoryMethodEdgeImpl) childFactoryMethodEdge).spiDelegate(); + } + + @AutoValue + abstract static class ComponentNodeImpl implements ComponentNode { + static ComponentNode create(dagger.spi.model.BindingGraph.ComponentNode componentNode) { + return new AutoValue_ExternalBindingGraphConverter_ComponentNodeImpl( + fromSpiModel(componentNode.componentPath()), + componentNode.isSubcomponent(), + componentNode.isRealComponent(), + componentNode.entryPoints().stream() + .map(ExternalBindingGraphConverter::fromSpiModel) + .collect(toImmutableSet()), + componentNode.scopes().stream() + .map(ExternalBindingGraphConverter::fromSpiModel) + .collect(toImmutableSet()), + componentNode); + } + + abstract dagger.spi.model.BindingGraph.ComponentNode spiDelegate(); + + @Override + public final String toString() { + return spiDelegate().toString(); + } + } + + @AutoValue + abstract static class BindingNodeImpl implements Binding { + static Binding create(dagger.spi.model.Binding binding) { + return new AutoValue_ExternalBindingGraphConverter_BindingNodeImpl( + fromSpiModel(binding.key()), + fromSpiModel(binding.componentPath()), + binding.dependencies().stream() + .map(ExternalBindingGraphConverter::fromSpiModel) + .collect(toImmutableSet()), + binding.bindingElement().map(DaggerElement::java), + binding.contributingModule().map(DaggerTypeElement::java), + binding.requiresModuleInstance(), + binding.scope().map(ExternalBindingGraphConverter::fromSpiModel), + binding.isNullable(), + binding.isProduction(), + fromSpiModel(binding.kind()), + binding); + } + + abstract dagger.spi.model.Binding spiDelegate(); + + @Override + public final String toString() { + return spiDelegate().toString(); + } + } + + @AutoValue + abstract static class MissingBindingImpl extends MissingBinding { + static MissingBinding create(dagger.spi.model.BindingGraph.MissingBinding missingBinding) { + return new AutoValue_ExternalBindingGraphConverter_MissingBindingImpl( + fromSpiModel(missingBinding.componentPath()), + fromSpiModel(missingBinding.key()), + missingBinding); + } + + abstract dagger.spi.model.BindingGraph.MissingBinding spiDelegate(); + + @Memoized + @Override + public abstract int hashCode(); + + @Override + public abstract boolean equals(Object o); + } + + @AutoValue + abstract static class DependencyEdgeImpl implements DependencyEdge { + static DependencyEdge create(dagger.spi.model.BindingGraph.DependencyEdge dependencyEdge) { + return new AutoValue_ExternalBindingGraphConverter_DependencyEdgeImpl( + fromSpiModel(dependencyEdge.dependencyRequest()), + dependencyEdge.isEntryPoint(), + dependencyEdge); + } + + abstract dagger.spi.model.BindingGraph.DependencyEdge spiDelegate(); + + @Override + public final String toString() { + return spiDelegate().toString(); + } + } + + @AutoValue + abstract static class ChildFactoryMethodEdgeImpl implements ChildFactoryMethodEdge { + static ChildFactoryMethodEdge create( + dagger.spi.model.BindingGraph.ChildFactoryMethodEdge childFactoryMethodEdge) { + return new AutoValue_ExternalBindingGraphConverter_ChildFactoryMethodEdgeImpl( + childFactoryMethodEdge.factoryMethod().java(), childFactoryMethodEdge); + } + + abstract dagger.spi.model.BindingGraph.ChildFactoryMethodEdge spiDelegate(); + + @Override + public final String toString() { + return spiDelegate().toString(); + } + } + + @AutoValue + abstract static class SubcomponentCreatorBindingEdgeImpl + implements SubcomponentCreatorBindingEdge { + static SubcomponentCreatorBindingEdge create( + dagger.spi.model.BindingGraph.SubcomponentCreatorBindingEdge + subcomponentCreatorBindingEdge) { + return new AutoValue_ExternalBindingGraphConverter_SubcomponentCreatorBindingEdgeImpl( + subcomponentCreatorBindingEdge.declaringModules().stream() + .map(DaggerTypeElement::java) + .collect(toImmutableSet()), + subcomponentCreatorBindingEdge); + } + + abstract dagger.spi.model.BindingGraph.SubcomponentCreatorBindingEdge spiDelegate(); + + @Override + public final String toString() { + return spiDelegate().toString(); + } + } + + @AutoValue + abstract static class BindingGraphImpl extends BindingGraph { + static BindingGraph create(dagger.spi.model.BindingGraph bindingGraph) { + BindingGraphImpl bindingGraphImpl = + new AutoValue_ExternalBindingGraphConverter_BindingGraphImpl( + fromSpiModel(bindingGraph.network()), bindingGraph.isFullBindingGraph()); + + bindingGraphImpl.componentNodesByPath = + bindingGraphImpl.componentNodes().stream() + .collect(toImmutableMap(ComponentNode::componentPath, node -> node)); + + return bindingGraphImpl; + } + + private ImmutableMap<ComponentPath, ComponentNode> componentNodesByPath; + + // This overrides dagger.model.BindingGraph with a more efficient implementation. + @Override + public Optional<ComponentNode> componentNode(ComponentPath componentPath) { + return componentNodesByPath.containsKey(componentPath) + ? Optional.of(componentNodesByPath.get(componentPath)) + : Optional.empty(); + } + + // This overrides dagger.model.BindingGraph to memoize the output. + @Override + @Memoized + public ImmutableSetMultimap<Class<? extends Node>, ? extends Node> nodesByClass() { + return super.nodesByClass(); + } + } + + private static final class DiagnosticReporterImpl implements DiagnosticReporter { + static DiagnosticReporterImpl create(dagger.spi.model.DiagnosticReporter reporter) { + return new DiagnosticReporterImpl(reporter); + } + + private final dagger.spi.model.DiagnosticReporter delegate; + + DiagnosticReporterImpl(dagger.spi.model.DiagnosticReporter delegate) { + this.delegate = delegate; + } + + @Override + public void reportComponent( + Diagnostic.Kind diagnosticKind, ComponentNode componentNode, String message) { + delegate.reportComponent(diagnosticKind, toSpiModel(componentNode), message); + } + + @Override + @FormatMethod + public void reportComponent( + Diagnostic.Kind diagnosticKind, + ComponentNode componentNode, + String messageFormat, + Object firstArg, + Object... moreArgs) { + delegate.reportComponent( + diagnosticKind, toSpiModel(componentNode), messageFormat, firstArg, moreArgs); + } + + @Override + public void reportBinding( + Diagnostic.Kind diagnosticKind, MaybeBinding binding, String message) { + delegate.reportBinding(diagnosticKind, toSpiModel(binding), message); + } + + @Override + @FormatMethod + public void reportBinding( + Diagnostic.Kind diagnosticKind, + MaybeBinding binding, + String messageFormat, + Object firstArg, + Object... moreArgs) { + delegate.reportBinding( + diagnosticKind, toSpiModel(binding), messageFormat, firstArg, moreArgs); + } + + @Override + public void reportDependency( + Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String message) { + delegate.reportDependency(diagnosticKind, toSpiModel(dependencyEdge), message); + } + + @Override + @FormatMethod + public void reportDependency( + Diagnostic.Kind diagnosticKind, + DependencyEdge dependencyEdge, + String messageFormat, + Object firstArg, + Object... moreArgs) { + delegate.reportDependency( + diagnosticKind, toSpiModel(dependencyEdge), messageFormat, firstArg, moreArgs); + } + + @Override + public void reportSubcomponentFactoryMethod( + Diagnostic.Kind diagnosticKind, + ChildFactoryMethodEdge childFactoryMethodEdge, + String message) { + delegate.reportSubcomponentFactoryMethod( + diagnosticKind, toSpiModel(childFactoryMethodEdge), message); + } + + @Override + @FormatMethod + public void reportSubcomponentFactoryMethod( + Diagnostic.Kind diagnosticKind, + ChildFactoryMethodEdge childFactoryMethodEdge, + String messageFormat, + Object firstArg, + Object... moreArgs) { + delegate.reportSubcomponentFactoryMethod( + diagnosticKind, toSpiModel(childFactoryMethodEdge), messageFormat, firstArg, moreArgs); + } + } +} diff --git a/java/dagger/internal/codegen/validation/BindingGraphPlugins.java b/java/dagger/internal/codegen/validation/ExternalBindingGraphPlugins.java index 16ef78f79..9021790be 100644 --- a/java/dagger/internal/codegen/validation/BindingGraphPlugins.java +++ b/java/dagger/internal/codegen/validation/ExternalBindingGraphPlugins.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Dagger Authors. + * Copyright (C) 2021 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. @@ -17,36 +17,42 @@ package dagger.internal.codegen.validation; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static javax.tools.Diagnostic.Kind.ERROR; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.compat.XConverters; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import dagger.internal.codegen.compileroption.ProcessingOptions; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.validation.DiagnosticReporterFactory.DiagnosticReporterImpl; +import dagger.model.BindingGraph; import dagger.spi.BindingGraphPlugin; +import dagger.spi.DiagnosticReporter; import java.util.Map; import java.util.Set; -import javax.annotation.processing.Filer; import javax.inject.Inject; /** Initializes {@link BindingGraphPlugin}s. */ -public final class BindingGraphPlugins { +public final class ExternalBindingGraphPlugins { private final ImmutableSet<BindingGraphPlugin> plugins; - private final Filer filer; + private final DiagnosticReporterFactory diagnosticReporterFactory; + private final XFiler filer; private final DaggerTypes types; private final DaggerElements elements; private final Map<String, String> processingOptions; @Inject - BindingGraphPlugins( - @Validation ImmutableSet<BindingGraphPlugin> validationPlugins, - ImmutableSet<BindingGraphPlugin> externalPlugins, - Filer filer, + ExternalBindingGraphPlugins( + ImmutableSet<BindingGraphPlugin> plugins, + DiagnosticReporterFactory diagnosticReporterFactory, + XFiler filer, DaggerTypes types, DaggerElements elements, @ProcessingOptions Map<String, String> processingOptions) { - this.plugins = Sets.union(validationPlugins, externalPlugins).immutableCopy(); + this.plugins = plugins; + this.diagnosticReporterFactory = diagnosticReporterFactory; this.filer = filer; this.types = types; this.elements = elements; @@ -67,7 +73,7 @@ public final class BindingGraphPlugins { } private void initializePlugin(BindingGraphPlugin plugin) { - plugin.initFiler(filer); + plugin.initFiler(XConverters.toJavac(filer)); plugin.initTypes(types); plugin.initElements(elements); Set<String> supportedOptions = plugin.supportedOptions(); @@ -75,4 +81,21 @@ public final class BindingGraphPlugins { plugin.initOptions(Maps.filterKeys(processingOptions, supportedOptions::contains)); } } + + /** Returns {@code false} if any of the plugins reported an error. */ + boolean visit(dagger.spi.model.BindingGraph spiGraph) { + BindingGraph graph = ExternalBindingGraphConverter.fromSpiModel(spiGraph); + boolean isClean = true; + for (BindingGraphPlugin plugin : plugins) { + DiagnosticReporterImpl spiReporter = + diagnosticReporterFactory.reporter( + spiGraph, plugin.pluginName(), /* reportErrorsAsWarnings= */ false); + DiagnosticReporter reporter = ExternalBindingGraphConverter.fromSpiModel(spiReporter); + plugin.visitGraph(graph, reporter); + if (spiReporter.reportedDiagnosticKinds().contains(ERROR)) { + isClean = false; + } + } + return isClean; + } } diff --git a/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java b/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java index 9787f4f37..7aa95ae00 100644 --- a/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java +++ b/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java @@ -16,6 +16,9 @@ package dagger.internal.codegen.validation; +import static androidx.room.compiler.processing.XElementKt.isTypeElement; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; +import static com.google.auto.common.MoreTypes.asTypeElement; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; @@ -24,17 +27,23 @@ import static dagger.internal.codegen.base.Keys.isValidMembersInjectionKey; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors; import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; +import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; import static dagger.internal.codegen.langmodel.DaggerTypes.unwrapType; - -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static javax.lang.model.type.TypeKind.DECLARED; + +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XFieldElement; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.squareup.javapoet.ClassName; import dagger.Component; -import dagger.MembersInjector; import dagger.Provides; import dagger.internal.codegen.base.SourceFileGenerationException; import dagger.internal.codegen.base.SourceFileGenerator; @@ -45,20 +54,18 @@ import dagger.internal.codegen.binding.KeyFactory; import dagger.internal.codegen.binding.MembersInjectionBinding; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.compileroption.CompilerOptions; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Key; +import dagger.spi.model.Key; import java.util.ArrayDeque; import java.util.Deque; import java.util.Map; import java.util.Optional; import java.util.Set; -import javax.annotation.processing.Messager; +import java.util.stream.Stream; import javax.inject.Inject; -import javax.inject.Provider; import javax.inject.Singleton; -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.tools.Diagnostic.Kind; @@ -71,9 +78,10 @@ import javax.tools.Diagnostic.Kind; */ @Singleton final class InjectBindingRegistryImpl implements InjectBindingRegistry { + private final XProcessingEnv processingEnv; private final DaggerElements elements; private final DaggerTypes types; - private final Messager messager; + private final XMessager messager; private final InjectValidator injectValidator; private final InjectValidator injectValidatorWhenGeneratingCode; private final KeyFactory keyFactory; @@ -81,12 +89,12 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { private final CompilerOptions compilerOptions; final class BindingsCollection<B extends Binding> { - private final Class<?> factoryClass; + private final ClassName factoryClass; private final Map<Key, B> bindingsByKey = Maps.newLinkedHashMap(); private final Deque<B> bindingsRequiringGeneration = new ArrayDeque<>(); private final Set<Key> materializedBindingKeys = Sets.newLinkedHashSet(); - BindingsCollection(Class<?> factoryClass) { + BindingsCollection(ClassName factoryClass) { this.factoryClass = factoryClass; } @@ -95,7 +103,11 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { binding != null; binding = bindingsRequiringGeneration.poll()) { checkState(!binding.unresolved().isPresent()); - if (injectValidatorWhenGeneratingCode.isValidType(binding.key().type())) { + TypeMirror type = binding.key().type().java(); + if (!type.getKind().equals(DECLARED) + || injectValidatorWhenGeneratingCode + .validate(toXProcessing(asTypeElement(type), processingEnv)) + .isClean()) { generator.generate(binding); } materializedBindingKeys.add(binding.key()); @@ -134,8 +146,8 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { String.format( "Generating a %s for %s. " + "Prefer to run the dagger processor over that class instead.", - factoryClass.getSimpleName(), - types.erasure(binding.key().type()))); // erasure to strip <T> from msgs. + factoryClass.simpleName(), + types.erasure(binding.key().type().java()))); // erasure to strip <T> from msgs. } } } @@ -153,7 +165,7 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { // We only cache resolved bindings or unresolved bindings w/o type arguments. // Unresolved bindings w/ type arguments aren't valid for the object graph. if (binding.unresolved().isPresent() - || binding.bindingTypeElement().get().getTypeParameters().isEmpty()) { + || binding.bindingTypeElement().get().getType().getTypeArguments().isEmpty()) { Key key = binding.key(); Binding previousValue = bindingsByKey.put(key, binding); checkState(previousValue == null || binding.equals(previousValue), @@ -164,19 +176,21 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { } private final BindingsCollection<ProvisionBinding> provisionBindings = - new BindingsCollection<>(Provider.class); + new BindingsCollection<>(TypeNames.PROVIDER); private final BindingsCollection<MembersInjectionBinding> membersInjectionBindings = - new BindingsCollection<>(MembersInjector.class); + new BindingsCollection<>(TypeNames.MEMBERS_INJECTOR); @Inject InjectBindingRegistryImpl( + XProcessingEnv processingEnv, DaggerElements elements, DaggerTypes types, - Messager messager, + XMessager messager, InjectValidator injectValidator, KeyFactory keyFactory, BindingFactory bindingFactory, CompilerOptions compilerOptions) { + this.processingEnv = processingEnv; this.elements = elements; this.types = types; this.messager = messager; @@ -187,7 +201,6 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { this.compilerOptions = compilerOptions; } - // TODO(dpb): make the SourceFileGenerators fields so they don't have to be passed in @Override public void generateSourcesForRequiredBindings( @@ -234,29 +247,32 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { } @Override - public Optional<ProvisionBinding> tryRegisterConstructor(ExecutableElement constructorElement) { + public Optional<ProvisionBinding> tryRegisterInjectConstructor( + XConstructorElement constructorElement) { return tryRegisterConstructor(constructorElement, Optional.empty(), false); } @CanIgnoreReturnValue private Optional<ProvisionBinding> tryRegisterConstructor( - ExecutableElement constructorElement, - Optional<TypeMirror> resolvedType, + XConstructorElement constructorElement, + Optional<XType> resolvedType, boolean warnIfNotAlreadyGenerated) { - TypeElement typeElement = MoreElements.asType(constructorElement.getEnclosingElement()); - DeclaredType type = MoreTypes.asDeclared(typeElement.asType()); - Key key = keyFactory.forInjectConstructorWithResolvedType(type); - ProvisionBinding cachedBinding = provisionBindings.getBinding(key); - if (cachedBinding != null) { - return Optional.of(cachedBinding); - } + XTypeElement typeElement = constructorElement.getEnclosingElement(); - ValidationReport<TypeElement> report = injectValidator.validateConstructor(constructorElement); + // Validating here shouldn't have a performance penalty because the validator caches its reports + ValidationReport report = injectValidator.validate(typeElement); report.printMessagesTo(messager); if (!report.isClean()) { return Optional.empty(); } + XType type = typeElement.getType(); + Key key = keyFactory.forInjectConstructorWithResolvedType(type); + ProvisionBinding cachedBinding = provisionBindings.getBinding(key); + if (cachedBinding != null) { + return Optional.of(cachedBinding); + } + ProvisionBinding binding = bindingFactory.injectionBinding(constructorElement, resolvedType); registerBinding(binding, warnIfNotAlreadyGenerated); if (!binding.injectionSites().isEmpty()) { @@ -266,29 +282,50 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { } @Override - public Optional<MembersInjectionBinding> tryRegisterMembersInjectedType(TypeElement typeElement) { - return tryRegisterMembersInjectedType(typeElement, Optional.empty(), false); + public Optional<MembersInjectionBinding> tryRegisterInjectField(XFieldElement fieldElement) { + // TODO(b/204116636): Add a test for this once we're able to test kotlin sources. + // TODO(b/204208307): Add validation for KAPT to test if this came from a top-level field. + if (!isTypeElement(fieldElement.getEnclosingElement())) { + messager.printMessage( + Kind.ERROR, + "@Inject fields must be enclosed in a type.", + fieldElement); + } + return tryRegisterMembersInjectedType( + asTypeElement(fieldElement.getEnclosingElement()), Optional.empty(), false); + } + + @Override + public Optional<MembersInjectionBinding> tryRegisterInjectMethod(XMethodElement methodElement) { + // TODO(b/204116636): Add a test for this once we're able to test kotlin sources. + // TODO(b/204208307): Add validation for KAPT to test if this came from a top-level method. + if (!isTypeElement(methodElement.getEnclosingElement())) { + messager.printMessage( + Kind.ERROR, + "@Inject methods must be enclosed in a type.", + methodElement); + } + return tryRegisterMembersInjectedType( + asTypeElement(methodElement.getEnclosingElement()), Optional.empty(), false); } @CanIgnoreReturnValue private Optional<MembersInjectionBinding> tryRegisterMembersInjectedType( - TypeElement typeElement, - Optional<TypeMirror> resolvedType, - boolean warnIfNotAlreadyGenerated) { - DeclaredType type = MoreTypes.asDeclared(typeElement.asType()); + XTypeElement typeElement, Optional<XType> resolvedType, boolean warnIfNotAlreadyGenerated) { + // Validating here shouldn't have a performance penalty because the validator caches its reports + ValidationReport report = injectValidator.validateForMembersInjection(typeElement); + report.printMessagesTo(messager); + if (!report.isClean()) { + return Optional.empty(); + } + + XType type = typeElement.getType(); Key key = keyFactory.forInjectConstructorWithResolvedType(type); MembersInjectionBinding cachedBinding = membersInjectionBindings.getBinding(key); if (cachedBinding != null) { return Optional.of(cachedBinding); } - ValidationReport<TypeElement> report = - injectValidator.validateMembersInjectionType(typeElement); - report.printMessagesTo(messager); - if (!report.isClean()) { - return Optional.empty(); - } - MembersInjectionBinding binding = bindingFactory.membersInjectionBinding(type, resolvedType); registerBinding(binding, warnIfNotAlreadyGenerated); for (Optional<DeclaredType> supertype = types.nonObjectSuperclass(type); @@ -303,7 +340,7 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { @Override public Optional<ProvisionBinding> getOrFindProvisionBinding(Key key) { checkNotNull(key); - if (!isValidImplicitProvisionKey(key, types)) { + if (!isValidImplicitProvisionKey(key)) { return Optional.empty(); } ProvisionBinding binding = provisionBindings.getBinding(key); @@ -311,24 +348,21 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { return Optional.of(binding); } - // ok, let's see if we can find an @Inject constructor - TypeElement element = MoreElements.asType(types.asElement(key.type())); - ImmutableSet<ExecutableElement> injectConstructors = - ImmutableSet.<ExecutableElement>builder() - .addAll(injectedConstructors(element)) - .addAll(assistedInjectedConstructors(element)) - .build(); - switch (injectConstructors.size()) { - case 0: - // No constructor found. - return Optional.empty(); - case 1: - return tryRegisterConstructor( - Iterables.getOnlyElement(injectConstructors), Optional.of(key.type()), true); - default: - throw new IllegalStateException("Found multiple @Inject constructors: " - + injectConstructors); + XType type = key.type().xprocessing(); + XTypeElement element = type.getTypeElement(); + + ValidationReport report = injectValidator.validate(element); + report.printMessagesTo(messager); + if (!report.isClean()) { + return Optional.empty(); } + + return Stream.concat( + injectedConstructors(element).stream(), + assistedInjectedConstructors(element).stream()) + // We're guaranteed that there's at most 1 @Inject constructors from above validation. + .collect(toOptional()) + .flatMap(constructor -> tryRegisterConstructor(constructor, Optional.of(type), true)); } @CanIgnoreReturnValue @@ -341,10 +375,8 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { if (binding != null) { return Optional.of(binding); } - Optional<MembersInjectionBinding> newBinding = - tryRegisterMembersInjectedType( - MoreTypes.asTypeElement(key.type()), Optional.of(key.type()), true); - return newBinding; + return tryRegisterMembersInjectedType( + key.type().xprocessing().getTypeElement(), Optional.of(key.type().xprocessing()), true); } @Override @@ -352,7 +384,7 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { if (!isValidMembersInjectionKey(key)) { return Optional.empty(); } - Key membersInjectionKey = keyFactory.forMembersInjectedType(unwrapType(key.type())); + Key membersInjectionKey = keyFactory.forMembersInjectedType(unwrapType(key.type().java())); return getOrFindMembersInjectionBinding(membersInjectionKey) .map(binding -> bindingFactory.membersInjectorBinding(key, binding)); } diff --git a/java/dagger/internal/codegen/validation/InjectValidator.java b/java/dagger/internal/codegen/validation/InjectValidator.java index 240f9d099..4758d32dc 100644 --- a/java/dagger/internal/codegen/validation/InjectValidator.java +++ b/java/dagger/internal/codegen/validation/InjectValidator.java @@ -16,45 +16,43 @@ package dagger.internal.codegen.validation; -import static com.google.auto.common.MoreElements.asType; -import static com.google.auto.common.MoreElements.isAnnotationPresent; -import static dagger.internal.codegen.base.Scopes.scopesOf; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; +import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors; import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors; -import static javax.lang.model.element.Modifier.ABSTRACT; -import static javax.lang.model.element.Modifier.FINAL; -import static javax.lang.model.element.Modifier.PRIVATE; -import static javax.lang.model.element.Modifier.STATIC; -import static javax.lang.model.type.TypeKind.DECLARED; - -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; +import static dagger.internal.codegen.binding.SourceFiles.factoryNameForElement; +import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; +import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.getAnyAnnotation; +import static dagger.internal.codegen.xprocessing.XMethodElements.hasTypeParameters; + +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XFieldElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.XVariableElement; import com.google.common.collect.ImmutableSet; -import dagger.assisted.AssistedInject; +import com.squareup.javapoet.ClassName; import dagger.internal.codegen.base.ClearableCache; +import dagger.internal.codegen.base.DaggerSuperficialValidation; import dagger.internal.codegen.binding.InjectionAnnotations; import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.Accessibility; -import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Scope; +import dagger.internal.codegen.xprocessing.XAnnotations; +import dagger.spi.model.Scope; import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -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.ElementFilter; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; @@ -64,53 +62,55 @@ import javax.tools.Diagnostic.Kind; */ @Singleton public final class InjectValidator implements ClearableCache { + private final XProcessingEnv processingEnv; private final DaggerTypes types; - private final DaggerElements elements; private final CompilerOptions compilerOptions; private final DependencyRequestValidator dependencyRequestValidator; private final Optional<Diagnostic.Kind> privateAndStaticInjectionDiagnosticKind; private final InjectionAnnotations injectionAnnotations; - private final KotlinMetadataUtil metadataUtil; - private final Map<ExecutableElement, ValidationReport<TypeElement>> reports = new HashMap<>(); + private final DaggerSuperficialValidation superficialValidation; + private final Map<XTypeElement, ValidationReport> provisionReports = new HashMap<>(); + private final Map<XTypeElement, ValidationReport> membersInjectionReports = new HashMap<>(); @Inject InjectValidator( + XProcessingEnv processingEnv, DaggerTypes types, - DaggerElements elements, DependencyRequestValidator dependencyRequestValidator, CompilerOptions compilerOptions, InjectionAnnotations injectionAnnotations, - KotlinMetadataUtil metadataUtil) { + DaggerSuperficialValidation superficialValidation) { this( + processingEnv, types, - elements, compilerOptions, dependencyRequestValidator, Optional.empty(), injectionAnnotations, - metadataUtil); + superficialValidation); } private InjectValidator( + XProcessingEnv processingEnv, DaggerTypes types, - DaggerElements elements, CompilerOptions compilerOptions, DependencyRequestValidator dependencyRequestValidator, Optional<Kind> privateAndStaticInjectionDiagnosticKind, InjectionAnnotations injectionAnnotations, - KotlinMetadataUtil metadataUtil) { + DaggerSuperficialValidation superficialValidation) { + this.processingEnv = processingEnv; this.types = types; - this.elements = elements; this.compilerOptions = compilerOptions; this.dependencyRequestValidator = dependencyRequestValidator; this.privateAndStaticInjectionDiagnosticKind = privateAndStaticInjectionDiagnosticKind; this.injectionAnnotations = injectionAnnotations; - this.metadataUtil = metadataUtil; + this.superficialValidation = superficialValidation; } @Override public void clearCache() { - reports.clear(); + provisionReports.clear(); + membersInjectionReports.clear(); } /** @@ -122,60 +122,104 @@ public final class InjectValidator implements ClearableCache { return compilerOptions.ignorePrivateAndStaticInjectionForComponent() ? this : new InjectValidator( + processingEnv, types, - elements, compilerOptions, dependencyRequestValidator, Optional.of(Diagnostic.Kind.ERROR), injectionAnnotations, - metadataUtil); + superficialValidation); } - public ValidationReport<TypeElement> validateConstructor(ExecutableElement constructorElement) { - return reentrantComputeIfAbsent(reports, constructorElement, this::validateConstructorUncached); + public ValidationReport validate(XTypeElement typeElement) { + return reentrantComputeIfAbsent(provisionReports, typeElement, this::validateUncached); } - private ValidationReport<TypeElement> validateConstructorUncached( - ExecutableElement constructorElement) { - ValidationReport.Builder<TypeElement> builder = - ValidationReport.about(asType(constructorElement.getEnclosingElement())); + private ValidationReport validateUncached(XTypeElement typeElement) { + ValidationReport.Builder builder = ValidationReport.about(typeElement); + builder.addSubreport(validateForMembersInjectionInternal(typeElement)); - if (isAnnotationPresent(constructorElement, Inject.class) - && isAnnotationPresent(constructorElement, AssistedInject.class)) { + ImmutableSet<XConstructorElement> injectConstructors = + ImmutableSet.<XConstructorElement>builder() + .addAll(injectedConstructors(typeElement)) + .addAll(assistedInjectedConstructors(typeElement)) + .build(); + + switch (injectConstructors.size()) { + case 0: + break; // Nothing to validate. + case 1: + builder.addSubreport(validateConstructor(getOnlyElement(injectConstructors))); + break; + default: + builder.addError( + String.format( + "Type %s may only contain one injected constructor. Found: %s", + typeElement, + injectConstructors), + typeElement); + } + + return builder.build(); + } + + private ValidationReport validateConstructor(XConstructorElement constructorElement) { + superficialValidation.validateTypeOf(constructorElement); + ValidationReport.Builder builder = + ValidationReport.about(constructorElement.getEnclosingElement()); + + if (InjectionAnnotations.hasInjectAnnotation(constructorElement) + && constructorElement.hasAnnotation(TypeNames.ASSISTED_INJECT)) { builder.addError("Constructors cannot be annotated with both @Inject and @AssistedInject"); } - Class<?> injectAnnotation = - isAnnotationPresent(constructorElement, Inject.class) ? Inject.class : AssistedInject.class; + ClassName injectAnnotation = + getAnyAnnotation( + constructorElement, + TypeNames.INJECT, + TypeNames.INJECT_JAVAX, + TypeNames.ASSISTED_INJECT).map(XAnnotations::getClassName).get(); - if (constructorElement.getModifiers().contains(PRIVATE)) { + if (constructorElement.isPrivate()) { builder.addError( "Dagger does not support injection into private constructors", constructorElement); } - for (AnnotationMirror qualifier : injectionAnnotations.getQualifiers(constructorElement)) { - builder.addError( - String.format( - "@Qualifier annotations are not allowed on @%s constructors", - injectAnnotation.getSimpleName()), - constructorElement, - qualifier); - } + // If this type has already been processed in a previous round or compilation unit then there + // is no reason to recheck for invalid scope annotations since it's already been checked. + // This allows us to skip superficial validation of constructor annotations in subsequent + // compilations where the annotation types may no longer be on the classpath. + if (!processedInPreviousRoundOrCompilationUnit(constructorElement)) { + superficialValidation.validateAnnotationsOf(constructorElement); + for (XAnnotation qualifier : injectionAnnotations.getQualifiers(constructorElement)) { + builder.addError( + String.format( + "@Qualifier annotations are not allowed on @%s constructors", + injectAnnotation.simpleName()), + constructorElement, + qualifier); + } - String scopeErrorMsg = - String.format( - "@Scope annotations are not allowed on @%s constructors", - injectAnnotation.getSimpleName()); + String scopeErrorMsg = + String.format( + "@Scope annotations are not allowed on @%s constructors", + injectAnnotation.simpleName()); - if (injectAnnotation == Inject.class) { - scopeErrorMsg += "; annotate the class instead"; - } + if (injectAnnotation.equals(TypeNames.INJECT) + || injectAnnotation.equals(TypeNames.INJECT_JAVAX)) { + scopeErrorMsg += "; annotate the class instead"; + } - for (Scope scope : scopesOf(constructorElement)) { - builder.addError(scopeErrorMsg, constructorElement, scope.scopeAnnotation()); + for (Scope scope : injectionAnnotations.getScopes(constructorElement)) { + builder.addError( + scopeErrorMsg, + constructorElement, + toXProcessing(scope.scopeAnnotation().java(), processingEnv)); + } } - for (VariableElement parameter : constructorElement.getParameters()) { + for (XExecutableParameterElement parameter : constructorElement.getParameters()) { + superficialValidation.validateTypeOf(parameter); validateDependencyRequest(builder, parameter); } @@ -183,81 +227,68 @@ public final class InjectValidator implements ClearableCache { builder.addItem( String.format( "Dagger does not support checked exceptions on @%s constructors", - injectAnnotation.getSimpleName()), + injectAnnotation.simpleName()), privateMemberDiagnosticKind(), constructorElement); } checkInjectIntoPrivateClass(constructorElement, builder); - TypeElement enclosingElement = - MoreElements.asType(constructorElement.getEnclosingElement()); - - Set<Modifier> typeModifiers = enclosingElement.getModifiers(); - if (typeModifiers.contains(ABSTRACT)) { + XTypeElement enclosingElement = constructorElement.getEnclosingElement(); + if (enclosingElement.isAbstract()) { builder.addError( String.format( "@%s is nonsense on the constructor of an abstract class", - injectAnnotation.getSimpleName()), + injectAnnotation.simpleName()), constructorElement); } - if (enclosingElement.getNestingKind().isNested() - && !typeModifiers.contains(STATIC)) { + if (enclosingElement.isNested() && !enclosingElement.isStatic()) { builder.addError( String.format( "@%s constructors are invalid on inner classes. " + "Did you mean to make the class static?", - injectAnnotation.getSimpleName()), + injectAnnotation.simpleName()), constructorElement); } - // This is computationally expensive, but probably preferable to a giant index - ImmutableSet<ExecutableElement> injectConstructors = - ImmutableSet.<ExecutableElement>builder() - .addAll(injectedConstructors(enclosingElement)) - .addAll(assistedInjectedConstructors(enclosingElement)) - .build(); - - if (injectConstructors.size() > 1) { - builder.addError("Types may only contain one injected constructor", constructorElement); - } - - ImmutableSet<Scope> scopes = scopesOf(enclosingElement); - if (injectAnnotation == AssistedInject.class) { + // Note: superficial validation of the annotations is done as part of getting the scopes. + ImmutableSet<Scope> scopes = + injectionAnnotations.getScopes(constructorElement.getEnclosingElement()); + if (injectAnnotation.equals(TypeNames.ASSISTED_INJECT)) { for (Scope scope : scopes) { builder.addError( "A type with an @AssistedInject-annotated constructor cannot be scoped", enclosingElement, - scope.scopeAnnotation()); + toXProcessing(scope.scopeAnnotation().java(), processingEnv)); } } else if (scopes.size() > 1) { for (Scope scope : scopes) { builder.addError( "A single binding may not declare more than one @Scope", enclosingElement, - scope.scopeAnnotation()); + toXProcessing(scope.scopeAnnotation().java(), processingEnv)); } } return builder.build(); } - private ValidationReport<VariableElement> validateField(VariableElement fieldElement) { - ValidationReport.Builder<VariableElement> builder = ValidationReport.about(fieldElement); - Set<Modifier> modifiers = fieldElement.getModifiers(); - if (modifiers.contains(FINAL)) { + private ValidationReport validateField(XFieldElement fieldElement) { + superficialValidation.validateTypeOf(fieldElement); + ValidationReport.Builder builder = ValidationReport.about(fieldElement); + if (fieldElement.isFinal()) { builder.addError("@Inject fields may not be final", fieldElement); } - if (modifiers.contains(PRIVATE)) { + if (fieldElement.isPrivate()) { builder.addItem( "Dagger does not support injection into private fields", privateMemberDiagnosticKind(), fieldElement); } - if (modifiers.contains(STATIC)) { + if (fieldElement.isStatic()) { builder.addItem( "Dagger does not support injection into static fields", staticMemberDiagnosticKind(), @@ -269,37 +300,40 @@ public final class InjectValidator implements ClearableCache { return builder.build(); } - private ValidationReport<ExecutableElement> validateMethod(ExecutableElement methodElement) { - ValidationReport.Builder<ExecutableElement> builder = ValidationReport.about(methodElement); - Set<Modifier> modifiers = methodElement.getModifiers(); - if (modifiers.contains(ABSTRACT)) { + private ValidationReport validateMethod(XMethodElement methodElement) { + superficialValidation.validateTypeOf(methodElement); + ValidationReport.Builder builder = ValidationReport.about(methodElement); + if (methodElement.isAbstract()) { builder.addError("Methods with @Inject may not be abstract", methodElement); } - if (modifiers.contains(PRIVATE)) { + if (methodElement.isPrivate()) { builder.addItem( "Dagger does not support injection into private methods", privateMemberDiagnosticKind(), methodElement); } - if (modifiers.contains(STATIC)) { + if (methodElement.isStatic()) { builder.addItem( "Dagger does not support injection into static methods", staticMemberDiagnosticKind(), methodElement); } - if (!methodElement.getTypeParameters().isEmpty()) { + // No need to resolve type parameters since we're only checking existence. + if (hasTypeParameters(methodElement)) { builder.addError("Methods with @Inject may not declare type parameters", methodElement); } + // No need to resolve thrown types since we're only checking existence. if (!methodElement.getThrownTypes().isEmpty()) { builder.addError("Methods with @Inject may not throw checked exceptions. " + "Please wrap your exceptions in a RuntimeException instead.", methodElement); } - for (VariableElement parameter : methodElement.getParameters()) { + for (XExecutableParameterElement parameter : methodElement.getParameters()) { + superficialValidation.validateTypeOf(parameter); validateDependencyRequest(builder, parameter); } @@ -307,29 +341,41 @@ public final class InjectValidator implements ClearableCache { } private void validateDependencyRequest( - ValidationReport.Builder<?> builder, VariableElement parameter) { - dependencyRequestValidator.validateDependencyRequest(builder, parameter, parameter.asType()); + ValidationReport.Builder builder, XVariableElement parameter) { + dependencyRequestValidator.validateDependencyRequest(builder, parameter, parameter.getType()); dependencyRequestValidator.checkNotProducer(builder, parameter); } - public ValidationReport<TypeElement> validateMembersInjectionType(TypeElement typeElement) { + public ValidationReport validateForMembersInjection(XTypeElement typeElement) { + return !processedInPreviousRoundOrCompilationUnit(typeElement) + ? validate(typeElement) // validate everything + : validateForMembersInjectionInternal(typeElement); // validate only inject members + } + + private ValidationReport validateForMembersInjectionInternal(XTypeElement typeElement) { + return reentrantComputeIfAbsent( + membersInjectionReports, typeElement, this::validateForMembersInjectionInternalUncached); + } + + private ValidationReport validateForMembersInjectionInternalUncached(XTypeElement typeElement) { + superficialValidation.validateTypeOf(typeElement); // TODO(beder): This element might not be currently compiled, so this error message could be // left in limbo. Find an appropriate way to display the error message in that case. - ValidationReport.Builder<TypeElement> builder = ValidationReport.about(typeElement); + ValidationReport.Builder builder = ValidationReport.about(typeElement); boolean hasInjectedMembers = false; - for (VariableElement element : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) { - if (MoreElements.isAnnotationPresent(element, Inject.class)) { + for (XFieldElement field : typeElement.getDeclaredFields()) { + if (InjectionAnnotations.hasInjectAnnotation(field)) { hasInjectedMembers = true; - ValidationReport<VariableElement> report = validateField(element); + ValidationReport report = validateField(field); if (!report.isClean()) { builder.addSubreport(report); } } } - for (ExecutableElement element : ElementFilter.methodsIn(typeElement.getEnclosedElements())) { - if (MoreElements.isAnnotationPresent(element, Inject.class)) { + for (XMethodElement method : typeElement.getDeclaredMethods()) { + if (InjectionAnnotations.hasInjectAnnotation(method)) { hasInjectedMembers = true; - ValidationReport<ExecutableElement> report = validateMethod(element); + ValidationReport report = validateMethod(method); if (!report.isClean()) { builder.addSubreport(report); } @@ -340,9 +386,10 @@ public final class InjectValidator implements ClearableCache { checkInjectIntoPrivateClass(typeElement, builder); checkInjectIntoKotlinObject(typeElement, builder); } - TypeMirror superclass = typeElement.getSuperclass(); - if (!superclass.getKind().equals(TypeKind.NONE)) { - ValidationReport<TypeElement> report = validateType(MoreTypes.asTypeElement(superclass)); + if (typeElement.getSuperType() != null) { + superficialValidation.validateSuperTypeOf(typeElement); + ValidationReport report = + validateForMembersInjection(typeElement.getSuperType().getTypeElement()); if (!report.isClean()) { builder.addSubreport(report); } @@ -350,50 +397,17 @@ public final class InjectValidator implements ClearableCache { return builder.build(); } - public ValidationReport<TypeElement> validateType(TypeElement typeElement) { - ValidationReport.Builder<TypeElement> builder = ValidationReport.about(typeElement); - ValidationReport<TypeElement> membersInjectionReport = - validateMembersInjectionType(typeElement); - if (!membersInjectionReport.isClean()) { - builder.addSubreport(membersInjectionReport); - } - for (ExecutableElement element : - ElementFilter.constructorsIn(typeElement.getEnclosedElements())) { - if (isAnnotationPresent(element, Inject.class) - || isAnnotationPresent(element, AssistedInject.class)) { - ValidationReport<TypeElement> report = validateConstructor(element); - if (!report.isClean()) { - builder.addSubreport(report); - } - } - } - return builder.build(); - } - - public boolean isValidType(TypeMirror type) { - if (!type.getKind().equals(DECLARED)) { - return true; - } - return validateType(MoreTypes.asTypeElement(type)).isClean(); - } - /** Returns true if the given method element declares a checked exception. */ - private boolean throwsCheckedExceptions(ExecutableElement methodElement) { - TypeMirror runtimeExceptionType = elements.getTypeElement(RuntimeException.class).asType(); - TypeMirror errorType = elements.getTypeElement(Error.class).asType(); - for (TypeMirror thrownType : methodElement.getThrownTypes()) { - if (!types.isSubtype(thrownType, runtimeExceptionType) - && !types.isSubtype(thrownType, errorType)) { - return true; - } - } - return false; + private boolean throwsCheckedExceptions(XConstructorElement constructorElement) { + XType runtimeException = processingEnv.findType(TypeNames.RUNTIME_EXCEPTION); + XType error = processingEnv.findType(TypeNames.ERROR); + superficialValidation.validateThrownTypesOf(constructorElement); + return !constructorElement.getThrownTypes().stream() + .allMatch(type -> types.isSubtype(type, runtimeException) || types.isSubtype(type, error)); } - private void checkInjectIntoPrivateClass( - Element element, ValidationReport.Builder<TypeElement> builder) { - if (!Accessibility.isElementAccessibleFromOwnPackage( - DaggerElements.closestEnclosingTypeElement(element))) { + private void checkInjectIntoPrivateClass(XElement element, ValidationReport.Builder builder) { + if (!Accessibility.isElementAccessibleFromOwnPackage(closestEnclosingTypeElement(element))) { builder.addItem( "Dagger does not support injection into private classes", privateMemberDiagnosticKind(), @@ -401,9 +415,8 @@ public final class InjectValidator implements ClearableCache { } } - private void checkInjectIntoKotlinObject( - TypeElement element, ValidationReport.Builder<TypeElement> builder) { - if (metadataUtil.isObjectClass(element) || metadataUtil.isCompanionObjectClass(element)) { + private void checkInjectIntoKotlinObject(XTypeElement element, ValidationReport.Builder builder) { + if (element.isKotlinObject() || element.isCompanionObject()) { builder.addError("Dagger does not support injection into Kotlin objects", element); } } @@ -417,4 +430,12 @@ public final class InjectValidator implements ClearableCache { return privateAndStaticInjectionDiagnosticKind.orElse( compilerOptions.staticMemberValidationKind()); } + + private boolean processedInPreviousRoundOrCompilationUnit(XConstructorElement injectConstructor) { + return processingEnv.findTypeElement(factoryNameForElement(injectConstructor)) != null; + } + + private boolean processedInPreviousRoundOrCompilationUnit(XTypeElement membersInjectedType) { + return processingEnv.findTypeElement(membersInjectorNameForType(membersInjectedType)) != null; + } } diff --git a/java/dagger/internal/codegen/validation/MapKeyValidator.java b/java/dagger/internal/codegen/validation/MapKeyValidator.java index 6aa514710..1ec64959b 100644 --- a/java/dagger/internal/codegen/validation/MapKeyValidator.java +++ b/java/dagger/internal/codegen/validation/MapKeyValidator.java @@ -16,20 +16,17 @@ package dagger.internal.codegen.validation; -import static javax.lang.model.util.ElementFilter.methodsIn; - +import androidx.room.compiler.processing.XAnnotationKt; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.XTypeKt; import dagger.MapKey; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; import java.util.List; import javax.inject.Inject; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -/** - * A validator for {@link MapKey} annotations. - */ +/** A validator for {@link MapKey} annotations. */ // TODO(dpb,gak): Should unwrapped MapKeys be required to have their single member be named "value"? public final class MapKeyValidator { private final DaggerElements elements; @@ -39,22 +36,26 @@ public final class MapKeyValidator { this.elements = elements; } - public ValidationReport<Element> validate(Element element) { - ValidationReport.Builder<Element> builder = ValidationReport.about(element); - List<ExecutableElement> members = methodsIn(((TypeElement) element).getEnclosedElements()); + public ValidationReport validate(XTypeElement element) { + ValidationReport.Builder builder = ValidationReport.about(element); + List<XMethodElement> members = element.getDeclaredMethods(); if (members.isEmpty()) { builder.addError("Map key annotations must have members", element); - } else if (element.getAnnotation(MapKey.class).unwrapValue()) { + } else if (XAnnotationKt.get( + element.getAnnotation(TypeNames.MAP_KEY), "unwrapValue", Boolean.class)) { if (members.size() > 1) { builder.addError( "Map key annotations with unwrapped values must have exactly one member", element); - } else if (members.get(0).getReturnType().getKind() == TypeKind.ARRAY) { + } else if (XTypeKt.isArray(members.get(0).getReturnType())) { builder.addError("Map key annotations with unwrapped values cannot use arrays", element); } } else if (autoAnnotationIsMissing()) { builder.addError( "@AutoAnnotation is a necessary dependency if @MapKey(unwrapValue = false). Add a " - + "dependency on com.google.auto.value:auto-value:<current version>"); + + "dependency for the annotation, " + + "\"com.google.auto.value:auto-value-annotations:<current version>\", " + + "and the annotation processor, " + + "\"com.google.auto.value:auto-value:<current version>\""); } return builder.build(); } diff --git a/java/dagger/internal/codegen/validation/MembersInjectionValidator.java b/java/dagger/internal/codegen/validation/MembersInjectionValidator.java index afa6270f4..0ff11b893 100644 --- a/java/dagger/internal/codegen/validation/MembersInjectionValidator.java +++ b/java/dagger/internal/codegen/validation/MembersInjectionValidator.java @@ -16,20 +16,19 @@ package dagger.internal.codegen.validation; +import static androidx.room.compiler.processing.XTypeKt.isArray; import static com.google.common.base.Preconditions.checkArgument; +import static dagger.internal.codegen.xprocessing.XTypes.asArray; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; +import static dagger.internal.codegen.xprocessing.XTypes.isRawParameterizedType; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; import dagger.internal.codegen.binding.InjectionAnnotations; import javax.inject.Inject; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVisitor; -import javax.lang.model.util.SimpleTypeVisitor8; /** * Validates members injection requests (members injection methods on components and requests for @@ -44,11 +43,11 @@ final class MembersInjectionValidator { } /** Reports errors if a request for a {@code MembersInjector<Foo>}) is invalid. */ - ValidationReport<Element> validateMembersInjectionRequest( - Element requestElement, TypeMirror membersInjectedType) { - ValidationReport.Builder<Element> report = ValidationReport.about(requestElement); + ValidationReport validateMembersInjectionRequest( + XElement requestElement, XType membersInjectedType) { + ValidationReport.Builder report = ValidationReport.about(requestElement); checkQualifiers(report, requestElement); - membersInjectedType.accept(VALIDATE_MEMBERS_INJECTED_TYPE, report); + checkMembersInjectedType(report, membersInjectedType); return report.build(); } @@ -57,96 +56,61 @@ final class MembersInjectionValidator { * * @throws IllegalArgumentException if the method doesn't have exactly one parameter */ - ValidationReport<ExecutableElement> validateMembersInjectionMethod( - ExecutableElement method, TypeMirror membersInjectedType) { + ValidationReport validateMembersInjectionMethod( + XMethodElement method, XType membersInjectedType) { checkArgument( method.getParameters().size() == 1, "expected a method with one parameter: %s", method); - ValidationReport.Builder<ExecutableElement> report = ValidationReport.about(method); + ValidationReport.Builder report = ValidationReport.about(method); checkQualifiers(report, method); checkQualifiers(report, method.getParameters().get(0)); - membersInjectedType.accept(VALIDATE_MEMBERS_INJECTED_TYPE, report); + checkMembersInjectedType(report, membersInjectedType); return report.build(); } - private void checkQualifiers(ValidationReport.Builder<?> report, Element element) { - for (AnnotationMirror qualifier : injectionAnnotations.getQualifiers(element)) { + private void checkQualifiers(ValidationReport.Builder report, XElement element) { + for (XAnnotation qualifier : injectionAnnotations.getQualifiers(element)) { report.addError("Cannot inject members into qualified types", element, qualifier); break; // just report on the first qualifier, in case there is more than one } } - private static final TypeVisitor<Void, ValidationReport.Builder<?>> - VALIDATE_MEMBERS_INJECTED_TYPE = - new SimpleTypeVisitor8<Void, ValidationReport.Builder<?>>() { - // Only declared types can be members-injected. - @Override - protected Void defaultAction(TypeMirror type, ValidationReport.Builder<?> report) { - report.addError("Cannot inject members into " + type); - return null; - } + private void checkMembersInjectedType(ValidationReport.Builder report, XType type) { + // Only declared types can be members-injected. + if (!isDeclared(type)) { + report.addError("Cannot inject members into " + type); + return; + } + + // If the type is the erasure of a generic type, that means the user referred to + // Foo<T> as just 'Foo', which we don't allow. (This is a judgement call; we + // *could* allow it and instantiate the type bounds, but we don't.) + if (isRawParameterizedType(type)) { + report.addError("Cannot inject members into raw type " + type); + return; + } - @Override - public Void visitDeclared(DeclaredType type, ValidationReport.Builder<?> report) { - if (type.getTypeArguments().isEmpty()) { - // If the type is the erasure of a generic type, that means the user referred to - // Foo<T> as just 'Foo', which we don't allow. (This is a judgement call; we - // *could* allow it and instantiate the type bounds, but we don't.) - if (!MoreElements.asType(type.asElement()).getTypeParameters().isEmpty()) { - report.addError("Cannot inject members into raw type " + type); - } - } else { - // If the type has arguments, validate that each type argument is declared. - // Otherwise the type argument may be a wildcard (or other type), and we can't - // resolve that to actual types. For array type arguments, validate the type of the - // array. - for (TypeMirror arg : type.getTypeArguments()) { - if (!arg.accept(DECLARED_OR_ARRAY, null)) { - report.addError( - "Cannot inject members into types with unbounded type arguments: " + type); - } - } - } - return null; - } - }; + // If the type has arguments, validate that each type argument is declared. + // Otherwise the type argument may be a wildcard (or other type), and we can't + // resolve that to actual types. For array type arguments, validate the type of the array. + if (!type.getTypeArguments().stream().allMatch(this::isResolvableTypeArgument)) { + report.addError("Cannot inject members into types with unbounded type arguments: " + type); + } + } // TODO(dpb): Can this be inverted so it explicitly rejects wildcards or type variables? // This logic is hard to describe. - private static final TypeVisitor<Boolean, Void> DECLARED_OR_ARRAY = - new SimpleTypeVisitor8<Boolean, Void>(false) { - @Override - public Boolean visitArray(ArrayType arrayType, Void p) { - return arrayType - .getComponentType() - .accept( - new SimpleTypeVisitor8<Boolean, Void>(false) { - @Override - public Boolean visitDeclared(DeclaredType declaredType, Void p) { - for (TypeMirror arg : declaredType.getTypeArguments()) { - if (!arg.accept(this, null)) { - return false; - } - } - return true; - } - - @Override - public Boolean visitArray(ArrayType arrayType, Void p) { - return arrayType.getComponentType().accept(this, null); - } - - @Override - public Boolean visitPrimitive(PrimitiveType primitiveType, Void p) { - return true; - } - }, - null); - } + private boolean isResolvableTypeArgument(XType type) { + return isDeclared(type) + || (isArray(type) && isResolvableArrayComponentType(asArray(type).getComponentType())); + } - @Override - public Boolean visitDeclared(DeclaredType t, Void p) { - return true; - } - }; + private boolean isResolvableArrayComponentType(XType type) { + if (isDeclared(type)) { + return type.getTypeArguments().stream().allMatch(this::isResolvableTypeArgument); + } else if (isArray(type)) { + return isResolvableArrayComponentType(asArray(type).getComponentType()); + } + return isPrimitive(type); + } } diff --git a/java/dagger/internal/codegen/validation/ModuleValidator.java b/java/dagger/internal/codegen/validation/ModuleValidator.java index 02ac06a25..4d3ea3b00 100644 --- a/java/dagger/internal/codegen/validation/ModuleValidator.java +++ b/java/dagger/internal/codegen/validation/ModuleValidator.java @@ -16,35 +16,32 @@ package dagger.internal.codegen.validation; -import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations; -import static com.google.auto.common.MoreElements.isAnnotationPresent; -import static com.google.auto.common.MoreTypes.asTypeElement; -import static com.google.auto.common.Visibility.PRIVATE; -import static com.google.auto.common.Visibility.PUBLIC; -import static com.google.auto.common.Visibility.effectiveVisibilityOfElement; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.internal.codegen.base.ComponentAnnotation.componentAnnotation; import static dagger.internal.codegen.base.ComponentAnnotation.isComponentAnnotation; import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.getCreatorAnnotations; import static dagger.internal.codegen.base.ModuleAnnotation.isModuleAnnotation; -import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation; -import static dagger.internal.codegen.base.MoreAnnotationMirrors.simpleName; -import static dagger.internal.codegen.base.MoreAnnotationValues.asType; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.getCreatorAnnotations; import static dagger.internal.codegen.binding.ConfigurationAnnotations.getSubcomponentCreator; +import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror; -import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent; +import static dagger.internal.codegen.xprocessing.XAnnotations.getClassName; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation; +import static dagger.internal.codegen.xprocessing.XTypeElements.hasTypeParameters; +import static dagger.internal.codegen.xprocessing.XTypeElements.isEffectivelyPrivate; +import static dagger.internal.codegen.xprocessing.XTypeElements.isEffectivelyPublic; +import static dagger.internal.codegen.xprocessing.XTypes.areEquivalentTypes; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import static java.util.stream.Collectors.joining; -import static javax.lang.model.element.Modifier.ABSTRACT; -import static javax.lang.model.element.Modifier.STATIC; -import static javax.lang.model.util.ElementFilter.methodsIn; - -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; -import com.google.auto.common.Visibility; +import static kotlin.streams.jdk8.StreamsKt.asStream; + +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XAnnotationValue; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; @@ -53,22 +50,19 @@ import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; -import com.google.errorprone.annotations.FormatMethod; -import dagger.Module; -import dagger.Subcomponent; -import dagger.internal.codegen.base.ModuleAnnotation; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; +import dagger.internal.codegen.base.ComponentCreatorAnnotation; +import dagger.internal.codegen.base.DaggerSuperficialValidation; +import dagger.internal.codegen.base.ModuleKind; import dagger.internal.codegen.binding.BindingGraphFactory; -import dagger.internal.codegen.binding.ComponentCreatorAnnotation; import dagger.internal.codegen.binding.ComponentDescriptorFactory; +import dagger.internal.codegen.binding.InjectionAnnotations; import dagger.internal.codegen.binding.MethodSignatureFormatter; -import dagger.internal.codegen.binding.ModuleKind; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.BindingGraph; -import dagger.producers.ProducerModule; -import dagger.producers.ProductionSubcomponent; -import java.lang.annotation.Annotation; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.internal.codegen.xprocessing.XElements; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.Scope; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; @@ -76,34 +70,25 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import javax.inject.Inject; -import javax.inject.Scope; import javax.inject.Singleton; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleAnnotationValueVisitor8; -import javax.lang.model.util.SimpleTypeVisitor8; - -/** A {@linkplain ValidationReport validator} for {@link Module}s or {@link ProducerModule}s. */ + +/** + * A {@linkplain ValidationReport validator} for {@link dagger.Module}s or {@link + * dagger.producers.ProducerModule}s. + */ @Singleton public final class ModuleValidator { - private static final ImmutableSet<Class<? extends Annotation>> SUBCOMPONENT_TYPES = - ImmutableSet.of(Subcomponent.class, ProductionSubcomponent.class); - private static final ImmutableSet<Class<? extends Annotation>> SUBCOMPONENT_CREATOR_TYPES = + private static final ImmutableSet<ClassName> SUBCOMPONENT_TYPES = + ImmutableSet.of(TypeNames.SUBCOMPONENT, TypeNames.PRODUCTION_SUBCOMPONENT); + private static final ImmutableSet<ClassName> SUBCOMPONENT_CREATOR_TYPES = ImmutableSet.of( - Subcomponent.Builder.class, - Subcomponent.Factory.class, - ProductionSubcomponent.Builder.class, - ProductionSubcomponent.Factory.class); + TypeNames.SUBCOMPONENT_BUILDER, + TypeNames.SUBCOMPONENT_FACTORY, + TypeNames.PRODUCTION_SUBCOMPONENT_BUILDER, + TypeNames.PRODUCTION_SUBCOMPONENT_FACTORY); private static final Optional<Class<?>> ANDROID_PROCESSOR; private static final String CONTRIBUTES_ANDROID_INJECTOR_NAME = "dagger.android.ContributesAndroidInjector"; @@ -119,35 +104,35 @@ public final class ModuleValidator { ANDROID_PROCESSOR = Optional.ofNullable(clazz); } - private final DaggerTypes types; - private final DaggerElements elements; private final AnyBindingMethodValidator anyBindingMethodValidator; private final MethodSignatureFormatter methodSignatureFormatter; private final ComponentDescriptorFactory componentDescriptorFactory; private final BindingGraphFactory bindingGraphFactory; private final BindingGraphValidator bindingGraphValidator; - private final KotlinMetadataUtil metadataUtil; - private final Map<TypeElement, ValidationReport<TypeElement>> cache = new HashMap<>(); - private final Set<TypeElement> knownModules = new HashSet<>(); + private final InjectionAnnotations injectionAnnotations; + private final DaggerSuperficialValidation superficialValidation; + private final XProcessingEnv processingEnv; + private final Map<XTypeElement, ValidationReport> cache = new HashMap<>(); + private final Set<XTypeElement> knownModules = new HashSet<>(); @Inject ModuleValidator( - DaggerTypes types, - DaggerElements elements, AnyBindingMethodValidator anyBindingMethodValidator, MethodSignatureFormatter methodSignatureFormatter, ComponentDescriptorFactory componentDescriptorFactory, BindingGraphFactory bindingGraphFactory, BindingGraphValidator bindingGraphValidator, - KotlinMetadataUtil metadataUtil) { - this.types = types; - this.elements = elements; + InjectionAnnotations injectionAnnotations, + DaggerSuperficialValidation superficialValidation, + XProcessingEnv processingEnv) { this.anyBindingMethodValidator = anyBindingMethodValidator; this.methodSignatureFormatter = methodSignatureFormatter; this.componentDescriptorFactory = componentDescriptorFactory; this.bindingGraphFactory = bindingGraphFactory; this.bindingGraphValidator = bindingGraphValidator; - this.metadataUtil = metadataUtil; + this.injectionAnnotations = injectionAnnotations; + this.superficialValidation = superficialValidation; + this.processingEnv = processingEnv; } /** @@ -156,49 +141,44 @@ public final class ModuleValidator { * is assumed to be valid because it was processed in a previous compilation step. If it were * invalid, that previous compilation step would have failed and blocked this one. * - * <p>This logic depends on this method being called before {@linkplain #validate(TypeElement) - * validating} any module or {@linkplain #validateReferencedModules(TypeElement, AnnotationMirror, - * ImmutableSet, Set) component}. + * <p>This logic depends on this method being called before {@linkplain #validate(XTypeElement) + * validating} any module or {@linkplain #validateReferencedModules(XTypeElement, ModuleKind, Set, + * DiagnosticReporter.Builder) component}. */ - public void addKnownModules(Collection<TypeElement> modules) { + public void addKnownModules(Collection<XTypeElement> modules) { knownModules.addAll(modules); } /** Returns a validation report for a module type. */ - public ValidationReport<TypeElement> validate(TypeElement module) { + public ValidationReport validate(XTypeElement module) { return validate(module, new HashSet<>()); } - private ValidationReport<TypeElement> validate( - TypeElement module, Set<TypeElement> visitedModules) { + private ValidationReport validate(XTypeElement module, Set<XTypeElement> visitedModules) { if (visitedModules.add(module)) { return reentrantComputeIfAbsent(cache, module, m -> validateUncached(module, visitedModules)); } return ValidationReport.about(module).build(); } - private ValidationReport<TypeElement> validateUncached( - TypeElement module, Set<TypeElement> visitedModules) { - ValidationReport.Builder<TypeElement> builder = ValidationReport.about(module); + private ValidationReport validateUncached(XTypeElement module, Set<XTypeElement> visitedModules) { + ValidationReport.Builder builder = ValidationReport.about(module); ModuleKind moduleKind = ModuleKind.forAnnotatedElement(module).get(); - TypeElement contributesAndroidInjectorElement = - elements.getTypeElement(CONTRIBUTES_ANDROID_INJECTOR_NAME); - TypeMirror contributesAndroidInjector = - contributesAndroidInjectorElement != null - ? contributesAndroidInjectorElement.asType() - : null; - List<ExecutableElement> moduleMethods = methodsIn(module.getEnclosedElements()); - List<ExecutableElement> bindingMethods = new ArrayList<>(); - for (ExecutableElement moduleMethod : moduleMethods) { + Optional<XType> contributesAndroidInjector = + Optional.ofNullable(processingEnv.findTypeElement(CONTRIBUTES_ANDROID_INJECTOR_NAME)) + .map(XTypeElement::getType); + List<XMethodElement> moduleMethods = module.getDeclaredMethods(); + List<XMethodElement> bindingMethods = new ArrayList<>(); + for (XMethodElement moduleMethod : moduleMethods) { if (anyBindingMethodValidator.isBindingMethod(moduleMethod)) { builder.addSubreport(anyBindingMethodValidator.validate(moduleMethod)); bindingMethods.add(moduleMethod); } - for (AnnotationMirror annotation : moduleMethod.getAnnotationMirrors()) { + for (XAnnotation annotation : moduleMethod.getAllAnnotations()) { if (!ANDROID_PROCESSOR.isPresent() - && MoreTypes.equivalence() - .equivalent(contributesAndroidInjector, annotation.getAnnotationType())) { + && contributesAndroidInjector.isPresent() + && areEquivalentTypes(contributesAndroidInjector.get(), annotation.getType())) { builder.addSubreport( ValidationReport.about(moduleMethod) .addError( @@ -219,30 +199,31 @@ public final class ModuleValidator { builder.addError( String.format( "A @%s may not contain both non-static and abstract binding methods", - moduleKind.annotation().getSimpleName())); + moduleKind.annotation().simpleName())); } validateModuleVisibility(module, moduleKind, builder); - ImmutableListMultimap<Name, ExecutableElement> bindingMethodsByName = - Multimaps.index(bindingMethods, ExecutableElement::getSimpleName); + ImmutableListMultimap<String, XMethodElement> bindingMethodsByName = + Multimaps.index(bindingMethods, XElements::getSimpleName); validateMethodsWithSameName(builder, bindingMethodsByName); - if (module.getKind() != ElementKind.INTERFACE) { + if (!module.isInterface()) { validateBindingMethodOverrides( module, builder, - Multimaps.index(moduleMethods, ExecutableElement::getSimpleName), + Multimaps.index(moduleMethods, XElements::getSimpleName), bindingMethodsByName); } validateModifiers(module, builder); validateReferencedModules(module, moduleKind, visitedModules, builder); validateReferencedSubcomponents(module, moduleKind, builder); validateNoScopeAnnotationsOnModuleElement(module, moduleKind, builder); - validateSelfCycles(module, builder); - if (metadataUtil.hasEnclosedCompanionObject(module)) { - validateCompanionModule(module, builder); - } + validateSelfCycles(module, moduleKind, builder); + module.getEnclosedTypeElements().stream() + .filter(XTypeElement::isCompanionObject) + .collect(toOptional()) + .ifPresent(companionModule -> validateCompanionModule(companionModule, builder)); if (builder.build().isClean() && bindingGraphValidator.shouldDoFullBindingGraphValidation(module)) { @@ -253,89 +234,74 @@ public final class ModuleValidator { } private void validateReferencedSubcomponents( - final TypeElement subject, - ModuleKind moduleKind, - final ValidationReport.Builder<TypeElement> builder) { - // TODO(ronshapiro): use validateTypesAreDeclared when it is checked in - ModuleAnnotation moduleAnnotation = moduleAnnotation(moduleKind.getModuleAnnotation(subject)); - for (AnnotationValue subcomponentAttribute : - moduleAnnotation.subcomponentsAsAnnotationValues()) { - asType(subcomponentAttribute) - .accept( - new SimpleTypeVisitor8<Void, Void>() { - @Override - protected Void defaultAction(TypeMirror e, Void aVoid) { - builder.addError( - e + " is not a valid subcomponent type", - subject, - moduleAnnotation.annotation(), - subcomponentAttribute); - return null; - } - - @Override - public Void visitDeclared(DeclaredType declaredType, Void aVoid) { - TypeElement attributeType = asTypeElement(declaredType); - if (isAnyAnnotationPresent(attributeType, SUBCOMPONENT_TYPES)) { - validateSubcomponentHasBuilder( - attributeType, moduleAnnotation.annotation(), builder); - } else { - builder.addError( - isAnyAnnotationPresent(attributeType, SUBCOMPONENT_CREATOR_TYPES) - ? moduleSubcomponentsIncludesCreator(attributeType) - : moduleSubcomponentsIncludesNonSubcomponent(attributeType), - subject, - moduleAnnotation.annotation(), - subcomponentAttribute); - } - - return null; - } - }, - null); + XTypeElement subject, ModuleKind moduleKind, ValidationReport.Builder builder) { + XAnnotation moduleAnnotation = moduleKind.getModuleAnnotation(subject); + for (XAnnotationValue subcomponentValue : + moduleAnnotation.getAsAnnotationValueList("subcomponents")) { + XType type = subcomponentValue.asType(); + if (!isDeclared(type)) { + builder.addError( + type + " is not a valid subcomponent type", + subject, + moduleAnnotation, + subcomponentValue); + continue; + } + + XTypeElement subcomponentElement = type.getTypeElement(); + if (hasAnyAnnotation(subcomponentElement, SUBCOMPONENT_TYPES)) { + validateSubcomponentHasBuilder(subject, subcomponentElement, moduleAnnotation, builder); + } else { + builder.addError( + hasAnyAnnotation(subcomponentElement, SUBCOMPONENT_CREATOR_TYPES) + ? moduleSubcomponentsIncludesCreator(subcomponentElement) + : moduleSubcomponentsIncludesNonSubcomponent(subcomponentElement), + subject, + moduleAnnotation, + subcomponentValue); + } } } - private static String moduleSubcomponentsIncludesNonSubcomponent(TypeElement notSubcomponent) { + private static String moduleSubcomponentsIncludesNonSubcomponent(XTypeElement notSubcomponent) { return notSubcomponent.getQualifiedName() + " is not a @Subcomponent or @ProductionSubcomponent"; } - private static String moduleSubcomponentsIncludesCreator( - TypeElement moduleSubcomponentsAttribute) { - TypeElement subcomponentType = - MoreElements.asType(moduleSubcomponentsAttribute.getEnclosingElement()); + private String moduleSubcomponentsIncludesCreator(XTypeElement moduleSubcomponentsAttribute) { + XTypeElement subcomponentType = moduleSubcomponentsAttribute.getEnclosingTypeElement(); ComponentCreatorAnnotation creatorAnnotation = getOnlyElement(getCreatorAnnotations(moduleSubcomponentsAttribute)); return String.format( "%s is a @%s.%s. Did you mean to use %s?", moduleSubcomponentsAttribute.getQualifiedName(), - subcomponentAnnotation(subcomponentType).get().simpleName(), + subcomponentAnnotation(subcomponentType, superficialValidation).get().simpleName(), creatorAnnotation.creatorKind().typeName(), subcomponentType.getQualifiedName()); } - private static void validateSubcomponentHasBuilder( - TypeElement subcomponentAttribute, - AnnotationMirror moduleAnnotation, - ValidationReport.Builder<TypeElement> builder) { + private void validateSubcomponentHasBuilder( + XTypeElement subject, + XTypeElement subcomponentAttribute, + XAnnotation moduleAnnotation, + ValidationReport.Builder builder) { if (getSubcomponentCreator(subcomponentAttribute).isPresent()) { return; } builder.addError( moduleSubcomponentsDoesntHaveCreator(subcomponentAttribute, moduleAnnotation), - builder.getSubject(), + subject, moduleAnnotation); } - private static String moduleSubcomponentsDoesntHaveCreator( - TypeElement subcomponent, AnnotationMirror moduleAnnotation) { + private String moduleSubcomponentsDoesntHaveCreator( + XTypeElement subcomponent, XAnnotation moduleAnnotation) { return String.format( "%1$s doesn't have a @%2$s.Builder or @%2$s.Factory, which is required when used with " + "@%3$s.subcomponents", subcomponent.getQualifiedName(), - subcomponentAnnotation(subcomponent).get().simpleName(), - simpleName(moduleAnnotation)); + subcomponentAnnotation(subcomponent, superficialValidation).get().simpleName(), + getClassName(moduleAnnotation).simpleName()); } enum ModuleMethodKind { @@ -344,10 +310,10 @@ public final class ModuleValidator { STATIC_BINDING, ; - static ModuleMethodKind ofMethod(ExecutableElement moduleMethod) { - if (moduleMethod.getModifiers().contains(STATIC)) { + static ModuleMethodKind ofMethod(XMethodElement moduleMethod) { + if (moduleMethod.isStatic()) { return STATIC_BINDING; - } else if (moduleMethod.getModifiers().contains(ABSTRACT)) { + } else if (moduleMethod.isAbstract()) { return ABSTRACT_DECLARATION; } else { return INSTANCE_BINDING; @@ -355,38 +321,34 @@ public final class ModuleValidator { } } - private void validateModifiers( - TypeElement subject, ValidationReport.Builder<TypeElement> builder) { + private void validateModifiers(XTypeElement subject, ValidationReport.Builder builder) { // This coupled with the check for abstract modules in ComponentValidator guarantees that // only modules without type parameters are referenced from @Component(modules={...}). - if (!subject.getTypeParameters().isEmpty() && !subject.getModifiers().contains(ABSTRACT)) { + if (hasTypeParameters(subject) && !subject.isAbstract()) { builder.addError("Modules with type parameters must be abstract", subject); } } private void validateMethodsWithSameName( - ValidationReport.Builder<TypeElement> builder, - ListMultimap<Name, ExecutableElement> bindingMethodsByName) { - for (Entry<Name, Collection<ExecutableElement>> entry : - bindingMethodsByName.asMap().entrySet()) { - if (entry.getValue().size() > 1) { - for (ExecutableElement offendingMethod : entry.getValue()) { - builder.addError( - String.format( - "Cannot have more than one binding method with the same name in a single module"), - offendingMethod); - } - } - } + ValidationReport.Builder builder, ListMultimap<String, XMethodElement> bindingMethodsByName) { + bindingMethodsByName.asMap().values().stream() + .filter(methods -> methods.size() > 1) + .flatMap(Collection::stream) + .forEach( + duplicateMethod -> { + builder.addError( + "Cannot have more than one binding method with the same name in a single module", + duplicateMethod); + }); } private void validateReferencedModules( - TypeElement subject, + XTypeElement subject, ModuleKind moduleKind, - Set<TypeElement> visitedModules, - ValidationReport.Builder<TypeElement> builder) { + Set<XTypeElement> visitedModules, + ValidationReport.Builder builder) { // Validate that all the modules we include are valid for inclusion. - AnnotationMirror mirror = moduleKind.getModuleAnnotation(subject); + XAnnotation mirror = moduleKind.getModuleAnnotation(subject); builder.addSubreport( validateReferencedModules( subject, mirror, moduleKind.legalIncludedModuleKinds(), visitedModules)); @@ -407,80 +369,87 @@ public final class ModuleValidator { * {@code @Module}, or {@code @ProducerModule}) * @param validModuleKinds the module kinds that the annotated type is permitted to include */ - ValidationReport<TypeElement> validateReferencedModules( - TypeElement annotatedType, - AnnotationMirror annotation, + ValidationReport validateReferencedModules( + XTypeElement annotatedType, + XAnnotation annotation, ImmutableSet<ModuleKind> validModuleKinds, - Set<TypeElement> visitedModules) { - ValidationReport.Builder<TypeElement> subreport = ValidationReport.about(annotatedType); - ImmutableSet<? extends Class<? extends Annotation>> validModuleAnnotations = - validModuleKinds.stream().map(ModuleKind::annotation).collect(toImmutableSet()); - - for (AnnotationValue includedModule : getModules(annotation)) { - asType(includedModule) - .accept( - new SimpleTypeVisitor8<Void, Void>() { - @Override - protected Void defaultAction(TypeMirror mirror, Void p) { - reportError("%s is not a valid module type.", mirror); - return null; - } - - @Override - public Void visitDeclared(DeclaredType t, Void p) { - TypeElement module = MoreElements.asType(t.asElement()); - if (!t.getTypeArguments().isEmpty()) { - reportError( - "%s is listed as a module, but has type parameters", - module.getQualifiedName()); - } - if (!isAnyAnnotationPresent(module, validModuleAnnotations)) { - reportError( - "%s is listed as a module, but is not annotated with %s", - module.getQualifiedName(), - (validModuleAnnotations.size() > 1 ? "one of " : "") - + validModuleAnnotations.stream() - .map(otherClass -> "@" + otherClass.getSimpleName()) - .collect(joining(", "))); - } else if (knownModules.contains(module) - && !validate(module, visitedModules).isClean()) { - reportError("%s has errors", module.getQualifiedName()); - } - if (metadataUtil.isCompanionObjectClass(module)) { - reportError( - "%s is listed as a module, but it is a companion object class. " - + "Add @Module to the enclosing class and reference that instead.", - module.getQualifiedName()); - } - return null; - } - - @FormatMethod - private void reportError(String format, Object... args) { - subreport.addError( - String.format(format, args), annotatedType, annotation, includedModule); - } - }, - null); + Set<XTypeElement> visitedModules) { + superficialValidation.validateAnnotationOf(annotatedType, annotation); + + ValidationReport.Builder subreport = ValidationReport.about(annotatedType); + // TODO(bcorso): Consider creating a DiagnosticLocation object to encapsulate the location in a + // single object to avoid duplication across all reported errors + for (XAnnotationValue includedModule : getModules(annotation)) { + XType type = includedModule.asType(); + if (!isDeclared(type)) { + subreport.addError( + String.format("%s is not a valid module type.", type), + annotatedType, + annotation, + includedModule); + continue; + } + + XTypeElement module = type.getTypeElement(); + if (hasTypeParameters(module)) { + subreport.addError( + String.format( + "%s is listed as a module, but has type parameters", module.getQualifiedName()), + annotatedType, + annotation, + includedModule); + } + + ImmutableSet<ClassName> validModuleAnnotations = + validModuleKinds.stream().map(ModuleKind::annotation).collect(toImmutableSet()); + if (!hasAnyAnnotation(module, validModuleAnnotations)) { + subreport.addError( + String.format( + "%s is listed as a module, but is not annotated with %s", + module.getQualifiedName(), + (validModuleAnnotations.size() > 1 ? "one of " : "") + + validModuleAnnotations.stream() + .map(otherClass -> "@" + otherClass.simpleName()) + .collect(joining(", "))), + annotatedType, + annotation, + includedModule); + } else if (knownModules.contains(module) && !validate(module, visitedModules).isClean()) { + subreport.addError( + String.format("%s has errors", module.getQualifiedName()), + annotatedType, + annotation, + includedModule); + } + if (module.isCompanionObject()) { + subreport.addError( + String.format( + "%s is listed as a module, but it is a companion object class. " + + "Add @Module to the enclosing class and reference that instead.", + module.getQualifiedName()), + annotatedType, + annotation, + includedModule); + } } return subreport.build(); } - private static ImmutableList<AnnotationValue> getModules(AnnotationMirror annotation) { + private static ImmutableList<XAnnotationValue> getModules(XAnnotation annotation) { if (isModuleAnnotation(annotation)) { - return moduleAnnotation(annotation).includesAsAnnotationValues(); + return ImmutableList.copyOf(annotation.getAsAnnotationValueList("includes")); } if (isComponentAnnotation(annotation)) { - return componentAnnotation(annotation).moduleValues(); + return ImmutableList.copyOf(annotation.getAsAnnotationValueList("modules")); } throw new IllegalArgumentException(String.format("unsupported annotation: %s", annotation)); } private void validateBindingMethodOverrides( - TypeElement subject, - ValidationReport.Builder<TypeElement> builder, - ImmutableListMultimap<Name, ExecutableElement> moduleMethodsByName, - ImmutableListMultimap<Name, ExecutableElement> bindingMethodsByName) { + XTypeElement subject, + ValidationReport.Builder builder, + ImmutableListMultimap<String, XMethodElement> moduleMethodsByName, + ImmutableListMultimap<String, XMethodElement> bindingMethodsByName) { // For every binding method, confirm it overrides nothing *and* nothing overrides it. // Consider the following hierarchy: // class Parent { @@ -496,22 +465,22 @@ public final class ModuleValidator { // In each of those cases, we want to fail. "a" is clear, "b" because Child is overriding // a binding method in Parent, and "c" because Child is defining a binding method that overrides // Parent. - TypeElement currentClass = subject; - TypeMirror objectType = elements.getTypeElement(Object.class).asType(); - // We keep track of methods that failed so we don't spam with multiple failures. - Set<ExecutableElement> failedMethods = Sets.newHashSet(); - ListMultimap<Name, ExecutableElement> allMethodsByName = + XTypeElement currentClass = subject; + XType objectType = processingEnv.findType(TypeName.OBJECT); + // We keep track of visited methods so we don't spam with multiple failures. + Set<XMethodElement> visitedMethods = Sets.newHashSet(); + ListMultimap<String, XMethodElement> allMethodsByName = MultimapBuilder.hashKeys().arrayListValues().build(moduleMethodsByName); - while (!types.isSameType(currentClass.getSuperclass(), objectType)) { - currentClass = MoreElements.asType(types.asElement(currentClass.getSuperclass())); - List<ExecutableElement> superclassMethods = methodsIn(currentClass.getEnclosedElements()); - for (ExecutableElement superclassMethod : superclassMethods) { - Name name = superclassMethod.getSimpleName(); + while (!currentClass.getSuperType().isSameType(objectType)) { + currentClass = currentClass.getSuperType().getTypeElement(); + List<XMethodElement> superclassMethods = currentClass.getDeclaredMethods(); + for (XMethodElement superclassMethod : superclassMethods) { + String name = getSimpleName(superclassMethod); // For each method in the superclass, confirm our binding methods don't override it - for (ExecutableElement bindingMethod : bindingMethodsByName.get(name)) { - if (failedMethods.add(bindingMethod) - && elements.overrides(bindingMethod, superclassMethod, subject)) { + for (XMethodElement bindingMethod : bindingMethodsByName.get(name)) { + if (visitedMethods.add(bindingMethod) + && bindingMethod.overrides(superclassMethod, subject)) { builder.addError( String.format( "Binding methods may not override another method. Overrides: %s", @@ -521,9 +490,8 @@ public final class ModuleValidator { } // For each binding method in superclass, confirm our methods don't override it. if (anyBindingMethodValidator.isBindingMethod(superclassMethod)) { - for (ExecutableElement method : allMethodsByName.get(name)) { - if (failedMethods.add(method) - && elements.overrides(method, superclassMethod, subject)) { + for (XMethodElement method : allMethodsByName.get(name)) { + if (visitedMethods.add(method) && method.overrides(superclassMethod, subject)) { builder.addError( String.format( "Binding methods may not be overridden in modules. Overrides: %s", @@ -532,54 +500,41 @@ public final class ModuleValidator { } } } - allMethodsByName.put(superclassMethod.getSimpleName(), superclassMethod); + // TODO(b/202521399): Add a test for cases that add to this map. + allMethodsByName.put(getSimpleName(superclassMethod), superclassMethod); } } } private void validateModuleVisibility( - final TypeElement moduleElement, - ModuleKind moduleKind, - final ValidationReport.Builder<?> reportBuilder) { - ModuleAnnotation moduleAnnotation = - moduleAnnotation(getAnnotationMirror(moduleElement, moduleKind.annotation()).get()); - Visibility moduleVisibility = Visibility.ofElement(moduleElement); - Visibility moduleEffectiveVisibility = effectiveVisibilityOfElement(moduleElement); - if (moduleVisibility.equals(PRIVATE)) { + XTypeElement moduleElement, ModuleKind moduleKind, ValidationReport.Builder reportBuilder) { + if (moduleElement.isPrivate()) { reportBuilder.addError("Modules cannot be private.", moduleElement); - } else if (moduleEffectiveVisibility.equals(PRIVATE)) { + } else if (isEffectivelyPrivate(moduleElement)) { reportBuilder.addError("Modules cannot be enclosed in private types.", moduleElement); } - - switch (moduleElement.getNestingKind()) { - case ANONYMOUS: - throw new IllegalStateException("Can't apply @Module to an anonymous class"); - case LOCAL: - throw new IllegalStateException("Local classes shouldn't show up in the processor"); - case MEMBER: - case TOP_LEVEL: - if (moduleEffectiveVisibility.equals(PUBLIC)) { - ImmutableSet<TypeElement> invalidVisibilityIncludes = - getModuleIncludesWithInvalidVisibility(moduleAnnotation); - if (!invalidVisibilityIncludes.isEmpty()) { - reportBuilder.addError( - String.format( - "This module is public, but it includes non-public (or effectively non-public) " - + "modules (%s) that have non-static, non-abstract binding methods. Either " - + "reduce the visibility of this module, make the included modules " - + "public, or make all of the binding methods on the included modules " - + "abstract or static.", - formatListForErrorMessage(invalidVisibilityIncludes.asList())), - moduleElement); - } - } + if (isEffectivelyPublic(moduleElement)) { + ImmutableSet<XTypeElement> invalidVisibilityIncludes = + getModuleIncludesWithInvalidVisibility(moduleKind.getModuleAnnotation(moduleElement)); + if (!invalidVisibilityIncludes.isEmpty()) { + reportBuilder.addError( + String.format( + "This module is public, but it includes non-public (or effectively non-public) " + + "modules (%s) that have non-static, non-abstract binding methods. Either " + + "reduce the visibility of this module, make the included modules " + + "public, or make all of the binding methods on the included modules " + + "abstract or static.", + formatListForErrorMessage(invalidVisibilityIncludes.asList())), + moduleElement); + } } } - private ImmutableSet<TypeElement> getModuleIncludesWithInvalidVisibility( - ModuleAnnotation moduleAnnotation) { - return moduleAnnotation.includes().stream() - .filter(include -> !effectiveVisibilityOfElement(include).equals(PUBLIC)) + private ImmutableSet<XTypeElement> getModuleIncludesWithInvalidVisibility( + XAnnotation moduleAnnotation) { + return moduleAnnotation.getAnnotationValue("includes").asTypeList().stream() + .map(XType::getTypeElement) + .filter(include -> !isEffectivelyPublic(include)) .filter(this::requiresModuleInstance) .collect(toImmutableSet()); } @@ -590,103 +545,82 @@ public final class ModuleValidator { * {@code abstract} nor {@code static}. Alternatively, if the module is a Kotlin Object then the * binding methods are considered {@code static}, requiring no module instance. */ - private boolean requiresModuleInstance(TypeElement module) { - // Note elements.getAllMembers(module) rather than module.getEnclosedElements() here: we need to - // include binding methods declared in supertypes because unlike most other validations being - // done in this class, which assume that supertype binding methods will be validated in a - // separate call to the validator since the supertype itself must be a @Module, we need to look - // at all the binding methods in the module's type hierarchy here. - boolean isKotlinObject = - metadataUtil.isObjectClass(module) || metadataUtil.isCompanionObjectClass(module); - if (isKotlinObject) { - return false; - } - return methodsIn(elements.getAllMembers(module)).stream() - .filter(anyBindingMethodValidator::isBindingMethod) - .map(ExecutableElement::getModifiers) - .anyMatch(modifiers -> !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC)); + private boolean requiresModuleInstance(XTypeElement module) { + // Note: We use XTypeElement#getAllMethods() rather than XTypeElement#getDeclaredMethods() here + // because we need to include binding methods declared in supertypes because unlike most other + // validations being done in this class, which assume that supertype binding methods will be + // validated in a separate call to the validator since the supertype itself must be a @Module, + // we need to look at all the binding methods in the module's type hierarchy here. + return !(module.isKotlinObject() || module.isCompanionObject()) + && !asStream(module.getAllMethods()) + .filter(anyBindingMethodValidator::isBindingMethod) + .allMatch(method -> method.isAbstract() || method.isStatic()); } private void validateNoScopeAnnotationsOnModuleElement( - TypeElement module, ModuleKind moduleKind, ValidationReport.Builder<TypeElement> report) { - for (AnnotationMirror scope : getAnnotatedAnnotations(module, Scope.class)) { + XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder report) { + for (Scope scope : injectionAnnotations.getScopes(module)) { report.addError( String.format( "@%ss cannot be scoped. Did you mean to scope a method instead?", - moduleKind.annotation().getSimpleName()), + moduleKind.annotation().simpleName()), module, - scope); + scope.scopeAnnotation().xprocessing()); } } private void validateSelfCycles( - TypeElement module, ValidationReport.Builder<TypeElement> builder) { - ModuleAnnotation moduleAnnotation = moduleAnnotation(module).get(); - moduleAnnotation - .includesAsAnnotationValues() + XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder builder) { + XAnnotation moduleAnnotation = moduleKind.getModuleAnnotation(module); + moduleAnnotation.getAsAnnotationValueList("includes").stream() + .filter(includedModule -> areEquivalentTypes(module.getType(), includedModule.asType())) .forEach( - value -> - value.accept( - new SimpleAnnotationValueVisitor8<Void, Void>() { - @Override - public Void visitType(TypeMirror includedModule, Void aVoid) { - if (MoreTypes.equivalence().equivalent(module.asType(), includedModule)) { - String moduleKind = moduleAnnotation.annotationName(); - builder.addError( - String.format("@%s cannot include themselves.", moduleKind), - module, - moduleAnnotation.annotation(), - value); - } - return null; - } - }, - null)); + includedModule -> + builder.addError( + String.format( + "@%s cannot include themselves.", moduleKind.annotation().simpleName()), + module, + moduleAnnotation, + includedModule)); } private void validateCompanionModule( - TypeElement module, ValidationReport.Builder<TypeElement> builder) { - checkArgument(metadataUtil.hasEnclosedCompanionObject(module)); - TypeElement companionModule = metadataUtil.getEnclosedCompanionObject(module); - List<ExecutableElement> companionModuleMethods = - methodsIn(companionModule.getEnclosedElements()); - List<ExecutableElement> companionBindingMethods = new ArrayList<>(); - for (ExecutableElement companionModuleMethod : companionModuleMethods) { - if (anyBindingMethodValidator.isBindingMethod(companionModuleMethod)) { - builder.addSubreport(anyBindingMethodValidator.validate(companionModuleMethod)); - companionBindingMethods.add(companionModuleMethod); + XTypeElement companionModule, ValidationReport.Builder builder) { + List<XMethodElement> companionBindingMethods = new ArrayList<>(); + for (XMethodElement companionMethod : companionModule.getDeclaredMethods()) { + if (anyBindingMethodValidator.isBindingMethod(companionMethod)) { + builder.addSubreport(anyBindingMethodValidator.validate(companionMethod)); + companionBindingMethods.add(companionMethod); } // On normal modules only overriding other binding methods is disallowed, but for companion // objects we are prohibiting any override. For this can rely on checking the @Override // annotation since the Kotlin compiler will always produce them for overriding methods. - if (isAnnotationPresent(companionModuleMethod, Override.class)) { + if (companionMethod.hasAnnotation(TypeNames.OVERRIDE)) { builder.addError( - "Binding method in companion object may not override another method.", - companionModuleMethod); + "Binding method in companion object may not override another method.", companionMethod); } // TODO(danysantiago): Be strict about the usage of @JvmStatic, i.e. tell user to remove it. } - ImmutableListMultimap<Name, ExecutableElement> bindingMethodsByName = - Multimaps.index(companionBindingMethods, ExecutableElement::getSimpleName); + ImmutableListMultimap<String, XMethodElement> bindingMethodsByName = + Multimaps.index(companionBindingMethods, XElements::getSimpleName); validateMethodsWithSameName(builder, bindingMethodsByName); // If there are provision methods, then check the visibility. Companion objects are composed by // an inner class and a static field, it is not enough to check the visibility on the type // element or the field, therefore we check the metadata. - if (!companionBindingMethods.isEmpty() && metadataUtil.isVisibilityPrivate(companionModule)) { + if (!companionBindingMethods.isEmpty() && companionModule.isPrivate()) { builder.addError( "A Companion Module with binding methods cannot be private.", companionModule); } } - private void validateModuleBindings( - TypeElement module, ValidationReport.Builder<TypeElement> report) { + private void validateModuleBindings(XTypeElement module, ValidationReport.Builder report) { BindingGraph bindingGraph = - bindingGraphFactory.create( - componentDescriptorFactory.moduleComponentDescriptor(module), true) + bindingGraphFactory + .create(componentDescriptorFactory.moduleComponentDescriptor(module), true) .topLevelBindingGraph(); if (!bindingGraphValidator.isValid(bindingGraph)) { // Since the validator uses a DiagnosticReporter to report errors, the ValdiationReport won't diff --git a/java/dagger/internal/codegen/validation/MonitoringModuleGenerator.java b/java/dagger/internal/codegen/validation/MonitoringModuleGenerator.java index 1c2eb1c9a..fbbd18f32 100644 --- a/java/dagger/internal/codegen/validation/MonitoringModuleGenerator.java +++ b/java/dagger/internal/codegen/validation/MonitoringModuleGenerator.java @@ -26,40 +26,36 @@ import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.STATIC; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableList; -import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import dagger.Module; -import dagger.Provides; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.SourceFiles; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.multibindings.Multibinds; -import dagger.producers.ProductionScope; -import dagger.producers.monitoring.ProductionComponentMonitor; -import dagger.producers.monitoring.internal.Monitors; -import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; /** Generates a monitoring module for use with production components. */ -final class MonitoringModuleGenerator extends SourceFileGenerator<TypeElement> { +final class MonitoringModuleGenerator extends SourceFileGenerator<XTypeElement> { @Inject - MonitoringModuleGenerator(Filer filer, DaggerElements elements, SourceVersion sourceVersion) { + MonitoringModuleGenerator(XFiler filer, DaggerElements elements, SourceVersion sourceVersion) { super(filer, elements, sourceVersion); } @Override - public Element originatingElement(TypeElement componentElement) { + public XElement originatingElement(XTypeElement componentElement) { return componentElement; } @Override - public ImmutableList<TypeSpec.Builder> topLevelTypes(TypeElement componentElement) { + public ImmutableList<TypeSpec.Builder> topLevelTypes(XTypeElement componentElement) { return ImmutableList.of( classBuilder(SourceFiles.generatedMonitoringModuleName(componentElement)) .addAnnotation(Module.class) @@ -81,17 +77,16 @@ final class MonitoringModuleGenerator extends SourceFileGenerator<TypeElement> { .build(); } - private MethodSpec monitor(TypeElement componentElement) { + private MethodSpec monitor(XTypeElement componentElement) { return methodBuilder("monitor") - .returns(ProductionComponentMonitor.class) + .returns(TypeNames.PRODUCTION_COMPONENT_MONITOR) .addModifiers(STATIC) - .addAnnotation(Provides.class) - .addAnnotation(ProductionScope.class) - .addParameter(providerOf(ClassName.get(componentElement.asType())), "component") - .addParameter( - providerOf(setOf(PRODUCTION_COMPONENT_MONITOR_FACTORY)), "factories") + .addAnnotation(TypeNames.PROVIDES) + .addAnnotation(TypeNames.PRODUCTION_SCOPE) + .addParameter(providerOf(componentElement.getType().getTypeName()), "component") + .addParameter(providerOf(setOf(PRODUCTION_COMPONENT_MONITOR_FACTORY)), "factories") .addStatement( - "return $T.createMonitorForComponent(component, factories)", Monitors.class) + "return $T.createMonitorForComponent(component, factories)", TypeNames.MONITORS) .build(); } } diff --git a/java/dagger/internal/codegen/validation/MonitoringModuleProcessingStep.java b/java/dagger/internal/codegen/validation/MonitoringModuleProcessingStep.java index 5d4c64e39..e7ba8fd3e 100644 --- a/java/dagger/internal/codegen/validation/MonitoringModuleProcessingStep.java +++ b/java/dagger/internal/codegen/validation/MonitoringModuleProcessingStep.java @@ -16,40 +16,35 @@ package dagger.internal.codegen.validation; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableSet; -import dagger.producers.ProductionComponent; -import dagger.producers.ProductionSubcomponent; -import java.lang.annotation.Annotation; -import java.util.Set; -import javax.annotation.processing.Messager; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.javapoet.TypeNames; import javax.inject.Inject; -import javax.lang.model.element.TypeElement; /** * A processing step that is responsible for generating a special module for a {@link - * ProductionComponent} or {@link ProductionSubcomponent}. + * dagger.producers.ProductionComponent} or {@link dagger.producers.ProductionSubcomponent}. */ -public final class MonitoringModuleProcessingStep extends TypeCheckingProcessingStep<TypeElement> { - private final Messager messager; +public final class MonitoringModuleProcessingStep extends TypeCheckingProcessingStep<XTypeElement> { + private final XMessager messager; private final MonitoringModuleGenerator monitoringModuleGenerator; @Inject MonitoringModuleProcessingStep( - Messager messager, MonitoringModuleGenerator monitoringModuleGenerator) { - super(MoreElements::asType); + XMessager messager, MonitoringModuleGenerator monitoringModuleGenerator) { this.messager = messager; this.monitoringModuleGenerator = monitoringModuleGenerator; } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(ProductionComponent.class, ProductionSubcomponent.class); + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.PRODUCTION_COMPONENT, TypeNames.PRODUCTION_SUBCOMPONENT); } @Override - protected void process( - TypeElement element, ImmutableSet<Class<? extends Annotation>> annotations) { - monitoringModuleGenerator.generate(MoreElements.asType(element), messager); + protected void process(XTypeElement productionComponent, ImmutableSet<ClassName> annotations) { + monitoringModuleGenerator.generate(productionComponent, messager); } } diff --git a/java/dagger/internal/codegen/validation/MultibindingAnnotationsProcessingStep.java b/java/dagger/internal/codegen/validation/MultibindingAnnotationsProcessingStep.java index fd75eac24..4771de20c 100644 --- a/java/dagger/internal/codegen/validation/MultibindingAnnotationsProcessingStep.java +++ b/java/dagger/internal/codegen/validation/MultibindingAnnotationsProcessingStep.java @@ -16,45 +16,39 @@ package dagger.internal.codegen.validation; -import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror; import static javax.tools.Diagnostic.Kind.ERROR; -import com.google.auto.common.MoreElements; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XMessager; import com.google.common.collect.ImmutableSet; -import dagger.multibindings.ElementsIntoSet; -import dagger.multibindings.IntoMap; -import dagger.multibindings.IntoSet; -import java.lang.annotation.Annotation; -import java.util.Set; -import javax.annotation.processing.Messager; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.javapoet.TypeNames; import javax.inject.Inject; -import javax.lang.model.element.ExecutableElement; /** - * Processing step that verifies that {@link IntoSet}, {@link ElementsIntoSet} and {@link IntoMap} - * are not present on non-binding methods. + * Processing step that verifies that {@link dagger.multibindings.IntoSet}, {@link + * dagger.multibindings.ElementsIntoSet} and {@link dagger.multibindings.IntoMap} are not present on + * non-binding methods. */ public final class MultibindingAnnotationsProcessingStep - extends TypeCheckingProcessingStep<ExecutableElement> { + extends TypeCheckingProcessingStep<XExecutableElement> { private final AnyBindingMethodValidator anyBindingMethodValidator; - private final Messager messager; + private final XMessager messager; @Inject MultibindingAnnotationsProcessingStep( - AnyBindingMethodValidator anyBindingMethodValidator, Messager messager) { - super(MoreElements::asExecutable); + AnyBindingMethodValidator anyBindingMethodValidator, XMessager messager) { this.anyBindingMethodValidator = anyBindingMethodValidator; this.messager = messager; } @Override - public Set<? extends Class<? extends Annotation>> annotations() { - return ImmutableSet.of(IntoSet.class, ElementsIntoSet.class, IntoMap.class); + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.INTO_SET, TypeNames.ELEMENTS_INTO_SET, TypeNames.INTO_MAP); } @Override - protected void process( - ExecutableElement method, ImmutableSet<Class<? extends Annotation>> annotations) { + protected void process(XExecutableElement method, ImmutableSet<ClassName> annotations) { if (!anyBindingMethodValidator.isBindingMethod(method)) { annotations.forEach( annotation -> @@ -62,7 +56,7 @@ public final class MultibindingAnnotationsProcessingStep ERROR, "Multibinding annotations may only be on @Provides, @Produces, or @Binds methods", method, - getAnnotationMirror(method, annotation).get())); + method.getAnnotation(annotation))); } } } diff --git a/java/dagger/internal/codegen/validation/MultibindsMethodValidator.java b/java/dagger/internal/codegen/validation/MultibindsMethodValidator.java index 8829667b2..de54141d9 100644 --- a/java/dagger/internal/codegen/validation/MultibindsMethodValidator.java +++ b/java/dagger/internal/codegen/validation/MultibindsMethodValidator.java @@ -21,39 +21,31 @@ import static dagger.internal.codegen.validation.BindingElementValidator.AllowsM import static dagger.internal.codegen.validation.BindingElementValidator.AllowsScoping.NO_SCOPING; import static dagger.internal.codegen.validation.BindingMethodValidator.Abstractness.MUST_BE_ABSTRACT; import static dagger.internal.codegen.validation.BindingMethodValidator.ExceptionSuperclass.NO_EXCEPTIONS; +import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; import com.google.common.collect.ImmutableSet; -import dagger.Module; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.SetType; import dagger.internal.codegen.binding.InjectionAnnotations; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.multibindings.Multibinds; -import dagger.producers.ProducerModule; import javax.inject.Inject; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; -/** A validator for {@link Multibinds} methods. */ +/** A validator for {@link dagger.multibindings.Multibinds} methods. */ class MultibindsMethodValidator extends BindingMethodValidator { - /** Creates a validator for {@link Multibinds @Multibinds} methods. */ + /** Creates a validator for {@link dagger.multibindings.Multibinds @Multibinds} methods. */ @Inject MultibindsMethodValidator( - DaggerElements elements, DaggerTypes types, - KotlinMetadataUtil kotlinMetadataUtil, DependencyRequestValidator dependencyRequestValidator, InjectionAnnotations injectionAnnotations) { super( - elements, types, - kotlinMetadataUtil, - Multibinds.class, - ImmutableSet.of(Module.class, ProducerModule.class), + TypeNames.MULTIBINDS, + ImmutableSet.of(TypeNames.MODULE, TypeNames.PRODUCER_MODULE), dependencyRequestValidator, MUST_BE_ABSTRACT, NO_EXCEPTIONS, @@ -63,18 +55,21 @@ class MultibindsMethodValidator extends BindingMethodValidator { } @Override - protected ElementValidator elementValidator(ExecutableElement element) { - return new Validator(element); + protected ElementValidator elementValidator(XMethodElement method) { + return new Validator(method); } private class Validator extends MethodValidator { - Validator(ExecutableElement element) { - super(element); + private final XMethodElement method; + + Validator(XMethodElement method) { + super(method); + this.method = method; } @Override protected void checkParameters() { - if (!element.getParameters().isEmpty()) { + if (!method.getParameters().isEmpty()) { report.addError(bindingMethods("cannot have parameters")); } } @@ -82,29 +77,28 @@ class MultibindsMethodValidator extends BindingMethodValidator { /** Adds an error unless the method returns a {@code Map<K, V>} or {@code Set<T>}. */ @Override protected void checkType() { - if (!isPlainMap(element.getReturnType()) - && !isPlainSet(element.getReturnType())) { + if (!isPlainMap(method.getReturnType()) && !isPlainSet(method.getReturnType())) { report.addError(bindingMethods("must return Map<K, V> or Set<T>")); } } - private boolean isPlainMap(TypeMirror returnType) { + private boolean isPlainMap(XType returnType) { if (!MapType.isMap(returnType)) { return false; } MapType mapType = MapType.from(returnType); return !mapType.isRawType() - && MoreTypes.isType(mapType.valueType()) // No wildcards. + && !isWildcard(mapType.valueType()) && !isFrameworkType(mapType.valueType()); } - private boolean isPlainSet(TypeMirror returnType) { + private boolean isPlainSet(XType returnType) { if (!SetType.isSet(returnType)) { return false; } SetType setType = SetType.from(returnType); return !setType.isRawType() - && MoreTypes.isType(setType.elementType()) // No wildcards. + && !isWildcard(setType.elementType()) && !isFrameworkType(setType.elementType()); } } diff --git a/java/dagger/internal/codegen/validation/ProducesMethodValidator.java b/java/dagger/internal/codegen/validation/ProducesMethodValidator.java index 1606ebec0..ed7108627 100644 --- a/java/dagger/internal/codegen/validation/ProducesMethodValidator.java +++ b/java/dagger/internal/codegen/validation/ProducesMethodValidator.java @@ -17,45 +17,36 @@ package dagger.internal.codegen.validation; import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.internal.codegen.binding.ConfigurationAnnotations.getNullableAnnotation; import static dagger.internal.codegen.validation.BindingElementValidator.AllowsMultibindings.ALLOWS_MULTIBINDINGS; import static dagger.internal.codegen.validation.BindingElementValidator.AllowsScoping.NO_SCOPING; import static dagger.internal.codegen.validation.BindingMethodValidator.Abstractness.MUST_BE_CONCRETE; import static dagger.internal.codegen.validation.BindingMethodValidator.ExceptionSuperclass.EXCEPTION; +import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; import com.google.common.util.concurrent.ListenableFuture; -import dagger.internal.codegen.binding.ConfigurationAnnotations; import dagger.internal.codegen.binding.InjectionAnnotations; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.multibindings.ElementsIntoSet; -import dagger.producers.ProducerModule; -import dagger.producers.Produces; import java.util.Optional; import java.util.Set; import javax.inject.Inject; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -/** A validator for {@link Produces} methods. */ +/** A validator for {@link dagger.producers.Produces} methods. */ final class ProducesMethodValidator extends BindingMethodValidator { @Inject ProducesMethodValidator( - DaggerElements elements, DaggerTypes types, - KotlinMetadataUtil kotlinMetadataUtil, DependencyRequestValidator dependencyRequestValidator, InjectionAnnotations injectionAnnotations) { super( - elements, types, - kotlinMetadataUtil, dependencyRequestValidator, - Produces.class, - ProducerModule.class, + TypeNames.PRODUCES, + TypeNames.PRODUCER_MODULE, MUST_BE_CONCRETE, EXCEPTION, ALLOWS_MULTIBINDINGS, @@ -75,13 +66,16 @@ final class ProducesMethodValidator extends BindingMethodValidator { } @Override - protected ElementValidator elementValidator(ExecutableElement element) { - return new Validator(element); + protected ElementValidator elementValidator(XMethodElement method) { + return new Validator(method); } private class Validator extends MethodValidator { - Validator(ExecutableElement element) { - super(element); + private final XMethodElement method; + + Validator(XMethodElement method) { + super(method); + this.method = method; } @Override @@ -89,10 +83,12 @@ final class ProducesMethodValidator extends BindingMethodValidator { checkNullable(); } - /** Adds a warning if a {@link Produces @Produces} method is declared nullable. */ + /** + * Adds a warning if a {@link dagger.producers.Produces @Produces} method is declared nullable. + */ // TODO(beder): Properly handle nullable with producer methods. private void checkNullable() { - if (ConfigurationAnnotations.getNullableType(element).isPresent()) { + if (getNullableAnnotation(method).isPresent()) { report.addWarning("@Nullable on @Produces methods does not do anything"); } } @@ -103,35 +99,28 @@ final class ProducesMethodValidator extends BindingMethodValidator { * <p>Allows {@code keyType} to be a {@link ListenableFuture} of an otherwise-valid key type. */ @Override - protected void checkKeyType(TypeMirror keyType) { - Optional<TypeMirror> typeToCheck = unwrapListenableFuture(keyType); - if (typeToCheck.isPresent()) { - super.checkKeyType(typeToCheck.get()); - } + protected void checkKeyType(XType keyType) { + unwrapListenableFuture(keyType).ifPresent(super::checkKeyType); } /** * {@inheritDoc} * - * <p>Allows an {@link ElementsIntoSet @ElementsIntoSet} or {@code SET_VALUES} method to return - * a {@link ListenableFuture} of a {@link Set} as well. + * <p>Allows an {@link dagger.multibindings.ElementsIntoSet @ElementsIntoSet} or {@code + * SET_VALUES} method to return a {@link ListenableFuture} of a {@link Set} as well. */ @Override protected void checkSetValuesType() { - Optional<TypeMirror> typeToCheck = unwrapListenableFuture(element.getReturnType()); - if (typeToCheck.isPresent()) { - checkSetValuesType(typeToCheck.get()); - } + unwrapListenableFuture(method.getReturnType()).ifPresent(this::checkSetValuesType); } - private Optional<TypeMirror> unwrapListenableFuture(TypeMirror type) { - if (MoreTypes.isType(type) && MoreTypes.isTypeOf(ListenableFuture.class, type)) { - DeclaredType declaredType = MoreTypes.asDeclared(type); - if (declaredType.getTypeArguments().isEmpty()) { + private Optional<XType> unwrapListenableFuture(XType type) { + if (isTypeOf(type, TypeNames.LISTENABLE_FUTURE)) { + if (type.getTypeArguments().isEmpty()) { report.addError("@Produces methods cannot return a raw ListenableFuture"); return Optional.empty(); } else { - return Optional.of((TypeMirror) getOnlyElement(declaredType.getTypeArguments())); + return Optional.of(getOnlyElement(type.getTypeArguments())); } } return Optional.of(type); diff --git a/java/dagger/internal/codegen/validation/ProvidesMethodValidator.java b/java/dagger/internal/codegen/validation/ProvidesMethodValidator.java index 6b4f30387..509932317 100644 --- a/java/dagger/internal/codegen/validation/ProvidesMethodValidator.java +++ b/java/dagger/internal/codegen/validation/ProvidesMethodValidator.java @@ -21,36 +21,28 @@ import static dagger.internal.codegen.validation.BindingElementValidator.AllowsS import static dagger.internal.codegen.validation.BindingMethodValidator.Abstractness.MUST_BE_CONCRETE; import static dagger.internal.codegen.validation.BindingMethodValidator.ExceptionSuperclass.RUNTIME_EXCEPTION; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XVariableElement; import com.google.common.collect.ImmutableSet; -import dagger.Module; -import dagger.Provides; import dagger.internal.codegen.binding.InjectionAnnotations; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.producers.ProducerModule; import javax.inject.Inject; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -/** A validator for {@link Provides} methods. */ +/** A validator for {@link dagger.Provides} methods. */ final class ProvidesMethodValidator extends BindingMethodValidator { private final DependencyRequestValidator dependencyRequestValidator; @Inject ProvidesMethodValidator( - DaggerElements elements, DaggerTypes types, - KotlinMetadataUtil kotlinMetadataUtil, DependencyRequestValidator dependencyRequestValidator, InjectionAnnotations injectionAnnotations) { super( - elements, types, - kotlinMetadataUtil, - Provides.class, - ImmutableSet.of(Module.class, ProducerModule.class), + TypeNames.PROVIDES, + ImmutableSet.of(TypeNames.MODULE, TypeNames.PRODUCER_MODULE), dependencyRequestValidator, MUST_BE_CONCRETE, RUNTIME_EXCEPTION, @@ -61,22 +53,18 @@ final class ProvidesMethodValidator extends BindingMethodValidator { } @Override - protected ElementValidator elementValidator(ExecutableElement element) { - return new Validator(element); + protected ElementValidator elementValidator(XMethodElement method) { + return new Validator(method); } private class Validator extends MethodValidator { - Validator(ExecutableElement element) { - super(element); + Validator(XMethodElement method) { + super(method); } + /** Adds an error if a {@link dagger.Provides @Provides} method depends on a producer type. */ @Override - protected void checkAdditionalMethodProperties() { - } - - /** Adds an error if a {@link Provides @Provides} method depends on a producer type. */ - @Override - protected void checkParameter(VariableElement parameter) { + protected void checkParameter(XVariableElement parameter) { super.checkParameter(parameter); dependencyRequestValidator.checkNotProducer(report, parameter); } diff --git a/java/dagger/internal/codegen/validation/SuperficialValidator.java b/java/dagger/internal/codegen/validation/SuperficialValidator.java new file mode 100644 index 000000000..6684407e5 --- /dev/null +++ b/java/dagger/internal/codegen/validation/SuperficialValidator.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2021 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.validation; + +import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; + +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XTypeElement; +import dagger.internal.codegen.base.ClearableCache; +import dagger.internal.codegen.base.DaggerSuperficialValidation; +import dagger.internal.codegen.base.DaggerSuperficialValidation.ValidationException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** Validates enclosing type elements in a round. */ +@Singleton +public final class SuperficialValidator implements ClearableCache { + + private final DaggerSuperficialValidation superficialValidation; + private final Map<XTypeElement, Optional<ValidationException>> validationExceptions = + new HashMap<>(); + + @Inject + SuperficialValidator(DaggerSuperficialValidation superficialValidation) { + this.superficialValidation = superficialValidation; + } + + public void throwIfNearestEnclosingTypeNotValid(XElement element) { + Optional<ValidationException> validationException = + validationExceptions.computeIfAbsent( + closestEnclosingTypeElement(element), + this::validationExceptionsUncached); + + if (validationException.isPresent()) { + throw validationException.get(); + } + } + + private Optional<ValidationException> validationExceptionsUncached(XTypeElement element) { + try { + superficialValidation.validateElement(element); + } catch (ValidationException validationException) { + return Optional.of(validationException); + } + return Optional.empty(); + } + + @Override + public void clearCache() { + validationExceptions.clear(); + } +} diff --git a/java/dagger/internal/codegen/validation/TypeCheckingProcessingStep.java b/java/dagger/internal/codegen/validation/TypeCheckingProcessingStep.java index 57867f13b..5b14135eb 100644 --- a/java/dagger/internal/codegen/validation/TypeCheckingProcessingStep.java +++ b/java/dagger/internal/codegen/validation/TypeCheckingProcessingStep.java @@ -16,52 +16,175 @@ package dagger.internal.codegen.validation; -import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Throwables.getStackTraceAsString; +import static com.google.common.collect.Sets.difference; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static javax.tools.Diagnostic.Kind.ERROR; -import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XProcessingStep; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; -import com.google.common.collect.SetMultimap; -import java.lang.annotation.Annotation; -import java.util.function.Function; -import javax.lang.model.element.Element; +import com.google.common.collect.Maps; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.base.DaggerSuperficialValidation.ValidationException; +import dagger.internal.codegen.compileroption.CompilerOptions; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.inject.Inject; /** - * A {@link ProcessingStep} that processes one element at a time and defers any for which {@link + * A {@link XProcessingStep} that processes one element at a time and defers any for which {@link * TypeNotPresentException} is thrown. */ -// TODO(dpb): Contribute to auto-common. -public abstract class TypeCheckingProcessingStep<E extends Element> implements ProcessingStep { - private final Function<Element, E> downcaster; +public abstract class TypeCheckingProcessingStep<E extends XElement> implements XProcessingStep { - protected TypeCheckingProcessingStep(Function<Element, E> downcaster) { - this.downcaster = checkNotNull(downcaster); + private final List<String> lastDeferredErrorMessages = new ArrayList<>(); + @Inject XMessager messager; + @Inject CompilerOptions compilerOptions; + @Inject SuperficialValidator superficialValidator; + + @Override + public final ImmutableSet<String> annotations() { + return annotationClassNames().stream().map(ClassName::canonicalName).collect(toImmutableSet()); } + @SuppressWarnings("unchecked") // Subclass must ensure all annotated targets are of valid type. @Override - public ImmutableSet<Element> process( - SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) { - ImmutableSet.Builder<Element> deferredElements = ImmutableSet.builder(); - ImmutableSetMultimap.copyOf(elementsByAnnotation) - .inverse() - .asMap() + public ImmutableSet<XElement> process( + XProcessingEnv env, Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) { + // We only really care about the deferred error messages from the final round of processing. + // Thus, we can clear the values stored from the previous processing round since that clearly + // wasn't the final round, and we replace it with any deferred error messages from this round. + lastDeferredErrorMessages.clear(); + ImmutableSet.Builder<XElement> deferredElements = ImmutableSet.builder(); + inverse(elementsByAnnotation) .forEach( (element, annotations) -> { try { - process(downcaster.apply(element), ImmutableSet.copyOf(annotations)); + // The XBasicAnnotationProcessor only validates the element itself. However, we + // validate the enclosing type here to keep the previous behavior of + // BasicAnnotationProcessor, since Dagger still relies on this behavior. + // TODO(b/201479062): It's inefficient to require validation of the entire enclosing + // type, we should try to remove this and handle any additional validation into the + // steps that need it. + superficialValidator.throwIfNearestEnclosingTypeNotValid(element); + process((E) element, annotations); } catch (TypeNotPresentException e) { + // TODO(bcorso): We should be able to remove this once we replace all calls to + // SuperficialValidation with DaggerSuperficialValidation. + deferredElements.add(element); + cacheErrorMessage(typeNotPresentErrorMessage(element, e), e); + } catch (ValidationException.UnexpectedException unexpectedException) { + // Rethrow since the exception was created from an unexpected throwable so + // deferring to another round is unlikely to help. + throw unexpectedException; + } catch (ValidationException.KnownErrorType e) { deferredElements.add(element); + cacheErrorMessage(knownErrorTypeErrorMessage(element, e), e); + } catch (ValidationException.UnknownErrorType e) { + deferredElements.add(element); + cacheErrorMessage(unknownErrorTypeErrorMessage(element, e), e); } }); return deferredElements.build(); } + @Override + public void processOver( + XProcessingEnv env, Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) { + // We avoid doing any actual processing here since this is run in the same round as the last + // call to process(). Instead, we just report the last deferred error messages, if any. + lastDeferredErrorMessages.forEach(errorMessage -> messager.printMessage(ERROR, errorMessage)); + lastDeferredErrorMessages.clear(); + } + + private void cacheErrorMessage(String errorMessage, Exception exception) { + lastDeferredErrorMessages.add( + compilerOptions.includeStacktraceWithDeferredErrorMessages() + ? String.format("%s\n\n%s", errorMessage, getStackTraceAsString(exception)) + : errorMessage); + } + + private String typeNotPresentErrorMessage(XElement element, TypeNotPresentException exception) { + return String.format( + "%1$s was unable to process '%2$s' because '%3$s' could not be resolved." + + "\n" + + "\nIf type '%3$s' is a generated type, check above for compilation errors that may " + + "have prevented the type from being generated. Otherwise, ensure that type '%3$s' is " + + "on your classpath.", + this.getClass().getSimpleName(), + element, + exception.typeName()); + } + + private String knownErrorTypeErrorMessage( + XElement element, ValidationException.KnownErrorType exception) { + return String.format( + "%1$s was unable to process '%2$s' because '%3$s' could not be resolved." + + "\n" + + "\nDependency trace:" + + "\n => %4$s" + + "\n" + + "\nIf type '%3$s' is a generated type, check above for compilation errors that may " + + "have prevented the type from being generated. Otherwise, ensure that type '%3$s' is " + + "on your classpath.", + this.getClass().getSimpleName(), + element, + exception.getErrorTypeName(), + exception.getTrace()); + } + + private String unknownErrorTypeErrorMessage( + XElement element, ValidationException.UnknownErrorType exception) { + return String.format( + "%1$s was unable to process '%2$s' because one of its dependencies could not be resolved." + + "\n" + + "\nDependency trace:" + + "\n => %3$s" + + "\n" + + "\nIf the dependency is a generated type, check above for compilation errors that may" + + " have prevented the type from being generated. Otherwise, ensure that the dependency" + + " is on your classpath.", + this.getClass().getSimpleName(), element, exception.getTrace()); + } + /** * Processes one element. If this method throws {@link TypeNotPresentException}, the element will * be deferred until the next round of processing. * - * @param annotations the subset of {@link ProcessingStep#annotations()} that annotate {@code + * @param annotations the subset of {@link XProcessingStep#annotations()} that annotate {@code * element} */ - protected abstract void process(E element, ImmutableSet<Class<? extends Annotation>> annotations); + protected abstract void process(E element, ImmutableSet<ClassName> annotations); + + private ImmutableMap<XElement, ImmutableSet<ClassName>> inverse( + Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) { + ImmutableMap<String, ClassName> annotationClassNames = + annotationClassNames().stream() + .collect(toImmutableMap(ClassName::canonicalName, className -> className)); + checkState( + annotationClassNames.keySet().containsAll(elementsByAnnotation.keySet()), + "Unexpected annotations for %s: %s", + this.getClass().getCanonicalName(), + difference(elementsByAnnotation.keySet(), annotationClassNames.keySet())); + + ImmutableSetMultimap.Builder<XElement, ClassName> builder = ImmutableSetMultimap.builder(); + elementsByAnnotation.forEach( + (annotationName, elementSet) -> + elementSet.forEach( + element -> builder.put(element, annotationClassNames.get(annotationName)))); + + return ImmutableMap.copyOf(Maps.transformValues(builder.build().asMap(), ImmutableSet::copyOf)); + } + + /** Returns the set of annotations processed by this processing step. */ + protected abstract Set<ClassName> annotationClassNames(); } diff --git a/java/dagger/internal/codegen/validation/TypeHierarchyValidator.java b/java/dagger/internal/codegen/validation/TypeHierarchyValidator.java deleted file mode 100644 index 5fa02707a..000000000 --- a/java/dagger/internal/codegen/validation/TypeHierarchyValidator.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2020 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.validation; - -import com.google.auto.common.MoreTypes; -import com.google.auto.common.SuperficialValidation; -import com.google.common.base.Equivalence; -import dagger.internal.codegen.langmodel.DaggerTypes; -import java.util.ArrayDeque; -import java.util.HashSet; -import java.util.Queue; -import java.util.Set; -import javax.lang.model.type.TypeMirror; - -/** Utility methods for validating the type hierarchy of a given type. */ -final class TypeHierarchyValidator { - private TypeHierarchyValidator() {} - - /** - * Validate the type hierarchy of the given type including all super classes, interfaces, and - * type parameters. - * - * @throws TypeNotPresentException if an type in the hierarchy is not valid. - */ - public static void validateTypeHierarchy(TypeMirror type, DaggerTypes types) { - Queue<TypeMirror> queue = new ArrayDeque<>(); - Set<Equivalence.Wrapper<TypeMirror>> queued = new HashSet<>(); - queue.add(type); - queued.add(MoreTypes.equivalence().wrap(type)); - while (!queue.isEmpty()) { - TypeMirror currType = queue.remove(); - if (!SuperficialValidation.validateType(currType)) { - throw new TypeNotPresentException(currType.toString(), null); - } - for (TypeMirror superType : types.directSupertypes(currType)) { - if (queued.add(MoreTypes.equivalence().wrap(superType))) { - queue.add(superType); - } - } - } - } -} diff --git a/java/dagger/internal/codegen/validation/Validation.java b/java/dagger/internal/codegen/validation/Validation.java index 620b0f041..6d7315109 100644 --- a/java/dagger/internal/codegen/validation/Validation.java +++ b/java/dagger/internal/codegen/validation/Validation.java @@ -22,8 +22,8 @@ import java.lang.annotation.RetentionPolicy; import javax.inject.Qualifier; /** - * Qualifier annotation for the {@link dagger.spi.BindingGraphPlugin}s that are used to implement - * core Dagger validation. + * Qualifier annotation for the {@link dagger.spi.model.BindingGraphPlugin}s that are used to + * implement core Dagger validation. */ @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/java/dagger/internal/codegen/validation/ValidationBindingGraphPlugins.java b/java/dagger/internal/codegen/validation/ValidationBindingGraphPlugins.java new file mode 100644 index 000000000..0a43d874a --- /dev/null +++ b/java/dagger/internal/codegen/validation/ValidationBindingGraphPlugins.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2021 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.validation; + +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static javax.tools.Diagnostic.Kind.ERROR; + +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.compat.XConverters; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import dagger.internal.codegen.compileroption.CompilerOptions; +import dagger.internal.codegen.compileroption.ProcessingOptions; +import dagger.internal.codegen.compileroption.ValidationType; +import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.validation.DiagnosticReporterFactory.DiagnosticReporterImpl; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraphPlugin; +import java.util.Map; +import java.util.Set; +import javax.inject.Inject; + +/** Initializes {@link BindingGraphPlugin}s. */ +public final class ValidationBindingGraphPlugins { + private final ImmutableSet<BindingGraphPlugin> plugins; + private final DiagnosticReporterFactory diagnosticReporterFactory; + private final XFiler filer; + private final DaggerTypes types; + private final DaggerElements elements; + private final CompilerOptions compilerOptions; + private final Map<String, String> processingOptions; + + @Inject + ValidationBindingGraphPlugins( + @Validation ImmutableSet<BindingGraphPlugin> plugins, + DiagnosticReporterFactory diagnosticReporterFactory, + XFiler filer, + DaggerTypes types, + DaggerElements elements, + CompilerOptions compilerOptions, + @ProcessingOptions Map<String, String> processingOptions) { + this.plugins = plugins; + this.diagnosticReporterFactory = diagnosticReporterFactory; + this.filer = filer; + this.types = types; + this.elements = elements; + this.compilerOptions = compilerOptions; + this.processingOptions = processingOptions; + } + + /** Returns {@link BindingGraphPlugin#supportedOptions()} from all the plugins. */ + public ImmutableSet<String> allSupportedOptions() { + return plugins.stream() + .flatMap(plugin -> plugin.supportedOptions().stream()) + .collect(toImmutableSet()); + } + + /** Initializes the plugins. */ + // TODO(ronshapiro): Should we validate the uniqueness of plugin names? + public void initializePlugins() { + plugins.forEach(this::initializePlugin); + } + + private void initializePlugin(BindingGraphPlugin plugin) { + plugin.initFiler(XConverters.toJavac(filer)); + plugin.initTypes(types); + plugin.initElements(elements); + Set<String> supportedOptions = plugin.supportedOptions(); + if (!supportedOptions.isEmpty()) { + plugin.initOptions(Maps.filterKeys(processingOptions, supportedOptions::contains)); + } + } + + /** Returns {@code false} if any of the plugins reported an error. */ + boolean visit(BindingGraph graph) { + boolean errorsAsWarnings = + graph.isFullBindingGraph() + && compilerOptions.fullBindingGraphValidationType().equals(ValidationType.WARNING); + + boolean isClean = true; + for (BindingGraphPlugin plugin : plugins) { + DiagnosticReporterImpl reporter = + diagnosticReporterFactory.reporter(graph, plugin.pluginName(), errorsAsWarnings); + plugin.visitGraph(graph, reporter); + if (reporter.reportedDiagnosticKinds().contains(ERROR)) { + isClean = false; + } + } + return isClean; + } +} diff --git a/java/dagger/internal/codegen/validation/ValidationReport.java b/java/dagger/internal/codegen/validation/ValidationReport.java index 7f3737595..f9049cd50 100644 --- a/java/dagger/internal/codegen/validation/ValidationReport.java +++ b/java/dagger/internal/codegen/validation/ValidationReport.java @@ -18,38 +18,39 @@ package dagger.internal.codegen.validation; import static dagger.internal.codegen.base.ElementFormatter.elementToString; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static dagger.internal.codegen.langmodel.DaggerElements.transitivelyEncloses; import static javax.tools.Diagnostic.Kind.ERROR; import static javax.tools.Diagnostic.Kind.NOTE; import static javax.tools.Diagnostic.Kind.WARNING; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XAnnotationValue; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMessager; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableSet; import com.google.common.graph.Traverser; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; import java.util.Optional; -import javax.annotation.processing.Messager; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; /** A collection of issues to report for source code. */ -public final class ValidationReport<T extends Element> { - private static final Traverser<ValidationReport<?>> SUBREPORTS = +public final class ValidationReport { + private static final Traverser<ValidationReport> SUBREPORTS = Traverser.forTree(report -> report.subreports); - private final T subject; + private final XElement subject; private final ImmutableSet<Item> items; - private final ImmutableSet<ValidationReport<?>> subreports; + private final ImmutableSet<ValidationReport> subreports; private final boolean markedDirty; private boolean hasPrintedErrors; private ValidationReport( - T subject, + XElement subject, ImmutableSet<Item> items, - ImmutableSet<ValidationReport<?>> subreports, + ImmutableSet<ValidationReport> subreports, boolean markedDirty) { this.subject = subject; this.items = items; @@ -81,7 +82,7 @@ public final class ValidationReport<T extends Element> { break; } } - for (ValidationReport<?> subreport : subreports) { + for (ValidationReport subreport : subreports) { if (!subreport.isClean()) { return false; } @@ -90,20 +91,20 @@ public final class ValidationReport<T extends Element> { } /** - * Prints all messages to {@code messager} (and recurs for subreports). If a - * message's {@linkplain Item#element() element} is contained within the report's subject, - * associates the message with the message's element. Otherwise, since {@link Diagnostic} - * reporting is expected to be associated with elements that are currently being compiled, - * associates the message with the subject itself and prepends a reference to the item's element. + * Prints all messages to {@code messager} (and recurs for subreports). If a message's {@linkplain + * Item#element() element} is contained within the report's subject, associates the message with + * the message's element. Otherwise, since {@link Diagnostic} reporting is expected to be + * associated with elements that are currently being compiled, associates the message with the + * subject itself and prepends a reference to the item's element. */ - public void printMessagesTo(Messager messager) { + public void printMessagesTo(XMessager messager) { if (hasPrintedErrors) { // Avoid printing the errors from this validation report more than once. return; } hasPrintedErrors = true; for (Item item : items) { - if (isEnclosedIn(subject, item.element())) { + if (transitivelyEncloses(subject, item.element())) { if (item.annotation().isPresent()) { if (item.annotationValue().isPresent()) { messager.printMessage( @@ -124,143 +125,132 @@ public final class ValidationReport<T extends Element> { messager.printMessage(item.kind(), message, subject); } } - for (ValidationReport<?> subreport : subreports) { + for (ValidationReport subreport : subreports) { subreport.printMessagesTo(messager); } } - private static boolean isEnclosedIn(Element parent, Element child) { - Element current = child; - while (current != null) { - if (current.equals(parent)) { - return true; - } - current = current.getEnclosingElement(); - } - return false; - } - /** Metadata about a {@link ValidationReport} item. */ @AutoValue public abstract static class Item { public abstract String message(); public abstract Kind kind(); - public abstract Element element(); - public abstract Optional<AnnotationMirror> annotation(); - abstract Optional<AnnotationValue> annotationValue(); + public abstract XElement element(); + public abstract Optional<XAnnotation> annotation(); + abstract Optional<XAnnotationValue> annotationValue(); } - public static <T extends Element> Builder<T> about(T subject) { - return new Builder<>(subject); + public static Builder about(XElement subject) { + return new Builder(subject); } /** A {@link ValidationReport} builder. */ @CanIgnoreReturnValue - public static final class Builder<T extends Element> { - private final T subject; + public static final class Builder { + private final XElement subject; private final ImmutableSet.Builder<Item> items = ImmutableSet.builder(); - private final ImmutableSet.Builder<ValidationReport<?>> subreports = ImmutableSet.builder(); + private final ImmutableSet.Builder<ValidationReport> subreports = ImmutableSet.builder(); private boolean markedDirty; - private Builder(T subject) { + private Builder(XElement subject) { this.subject = subject; } - @CheckReturnValue - T getSubject() { - return subject; - } - - Builder<T> addItems(Iterable<Item> newItems) { + Builder addItems(Iterable<Item> newItems) { items.addAll(newItems); return this; } - public Builder<T> addError(String message) { + public Builder addError(String message) { return addError(message, subject); } - public Builder<T> addError(String message, Element element) { + public Builder addError(String message, XElement element) { return addItem(message, ERROR, element); } - public Builder<T> addError(String message, Element element, AnnotationMirror annotation) { + public Builder addError(String message, XElement element, XAnnotation annotation) { return addItem(message, ERROR, element, annotation); } - public Builder<T> addError( + public Builder addError( String message, - Element element, - AnnotationMirror annotation, - AnnotationValue annotationValue) { + XElement element, + XAnnotation annotation, + XAnnotationValue annotationValue) { return addItem(message, ERROR, element, annotation, annotationValue); } - Builder<T> addWarning(String message) { + Builder addWarning(String message) { return addWarning(message, subject); } - Builder<T> addWarning(String message, Element element) { + Builder addWarning(String message, XElement element) { return addItem(message, WARNING, element); } - Builder<T> addWarning(String message, Element element, AnnotationMirror annotation) { + Builder addWarning(String message, XElement element, XAnnotation annotation) { return addItem(message, WARNING, element, annotation); } - Builder<T> addWarning( + Builder addWarning( String message, - Element element, - AnnotationMirror annotation, - AnnotationValue annotationValue) { + XElement element, + XAnnotation annotation, + XAnnotationValue annotationValue) { return addItem(message, WARNING, element, annotation, annotationValue); } - Builder<T> addNote(String message) { + Builder addNote(String message) { return addNote(message, subject); } - Builder<T> addNote(String message, Element element) { + Builder addNote(String message, XElement element) { return addItem(message, NOTE, element); } - Builder<T> addNote(String message, Element element, AnnotationMirror annotation) { + Builder addNote(String message, XElement element, XAnnotation annotation) { return addItem(message, NOTE, element, annotation); } - Builder<T> addNote( + Builder addNote( String message, - Element element, - AnnotationMirror annotation, - AnnotationValue annotationValue) { + XElement element, + XAnnotation annotation, + XAnnotationValue annotationValue) { return addItem(message, NOTE, element, annotation, annotationValue); } - Builder<T> addItem(String message, Kind kind, Element element) { + Builder addItem(String message, Kind kind, XElement element) { return addItem(message, kind, element, Optional.empty(), Optional.empty()); } - Builder<T> addItem(String message, Kind kind, Element element, AnnotationMirror annotation) { + Builder addItem(String message, Kind kind, XElement element, XAnnotation annotation) { return addItem(message, kind, element, Optional.of(annotation), Optional.empty()); } - Builder<T> addItem( + Builder addItem( String message, Kind kind, - Element element, - AnnotationMirror annotation, - AnnotationValue annotationValue) { + XElement element, + XAnnotation annotation, + XAnnotationValue annotationValue) { return addItem(message, kind, element, Optional.of(annotation), Optional.of(annotationValue)); } - private Builder<T> addItem( + private Builder addItem( String message, Kind kind, - Element element, - Optional<AnnotationMirror> annotation, - Optional<AnnotationValue> annotationValue) { + XElement element, + Optional<XAnnotation> annotation, + Optional<XAnnotationValue> annotationValue) { items.add( - new AutoValue_ValidationReport_Item(message, kind, element, annotation, annotationValue)); + new AutoValue_ValidationReport_Item( + message, + kind, + element, + annotation, + annotationValue)); return this; } @@ -272,14 +262,14 @@ public final class ValidationReport<T extends Element> { this.markedDirty = true; } - public Builder<T> addSubreport(ValidationReport<?> subreport) { + public Builder addSubreport(ValidationReport subreport) { subreports.add(subreport); return this; } @CheckReturnValue - public ValidationReport<T> build() { - return new ValidationReport<>(subject, items.build(), subreports.build(), markedDirty); + public ValidationReport build() { + return new ValidationReport(subject, items.build(), subreports.build(), markedDirty); } } } diff --git a/java/dagger/internal/codegen/writing/AnnotationCreatorGenerator.java b/java/dagger/internal/codegen/writing/AnnotationCreatorGenerator.java index fa3a16cac..a0699f19f 100644 --- a/java/dagger/internal/codegen/writing/AnnotationCreatorGenerator.java +++ b/java/dagger/internal/codegen/writing/AnnotationCreatorGenerator.java @@ -22,13 +22,17 @@ import static com.squareup.javapoet.TypeSpec.classBuilder; import static dagger.internal.codegen.binding.AnnotationExpression.createMethodName; import static dagger.internal.codegen.binding.AnnotationExpression.getAnnotationCreatorClassName; import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; -import static javax.lang.model.util.ElementFilter.methodsIn; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.compat.XConverters; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.squareup.javapoet.ClassName; @@ -40,15 +44,8 @@ import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.langmodel.DaggerElements; import java.util.LinkedHashSet; import java.util.Set; -import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.util.SimpleTypeVisitor6; /** * Generates classes that create annotation instances for an annotation type. The generated class @@ -77,47 +74,49 @@ import javax.lang.model.util.SimpleTypeVisitor6; * } * </pre> */ -public class AnnotationCreatorGenerator extends SourceFileGenerator<TypeElement> { +public class AnnotationCreatorGenerator extends SourceFileGenerator<XTypeElement> { private static final ClassName AUTO_ANNOTATION = ClassName.get("com.google.auto.value", "AutoAnnotation"); @Inject - AnnotationCreatorGenerator(Filer filer, DaggerElements elements, SourceVersion sourceVersion) { + AnnotationCreatorGenerator(XFiler filer, DaggerElements elements, SourceVersion sourceVersion) { super(filer, elements, sourceVersion); } @Override - public Element originatingElement(TypeElement annotationType) { + public XElement originatingElement(XTypeElement annotationType) { return annotationType; } @Override - public ImmutableList<TypeSpec.Builder> topLevelTypes(TypeElement annotationType) { - ClassName generatedTypeName = getAnnotationCreatorClassName(annotationType); + public ImmutableList<TypeSpec.Builder> topLevelTypes(XTypeElement annotationType) { + ClassName generatedTypeName = + getAnnotationCreatorClassName(XConverters.toJavac(annotationType)); TypeSpec.Builder annotationCreatorBuilder = classBuilder(generatedTypeName) .addModifiers(PUBLIC, FINAL) .addMethod(constructorBuilder().addModifiers(PRIVATE).build()); - for (TypeElement annotationElement : annotationsToCreate(annotationType)) { + for (XTypeElement annotationElement : annotationsToCreate(annotationType)) { annotationCreatorBuilder.addMethod(buildCreateMethod(generatedTypeName, annotationElement)); } return ImmutableList.of(annotationCreatorBuilder); } - private MethodSpec buildCreateMethod(ClassName generatedTypeName, TypeElement annotationElement) { - String createMethodName = createMethodName(annotationElement); + private MethodSpec buildCreateMethod( + ClassName generatedTypeName, XTypeElement annotationElement) { + String createMethodName = createMethodName(XConverters.toJavac(annotationElement)); MethodSpec.Builder createMethod = methodBuilder(createMethodName) .addAnnotation(AUTO_ANNOTATION) .addModifiers(PUBLIC, STATIC) - .returns(TypeName.get(annotationElement.asType())); + .returns(annotationElement.getType().getTypeName()); ImmutableList.Builder<CodeBlock> parameters = ImmutableList.builder(); - for (ExecutableElement annotationMember : methodsIn(annotationElement.getEnclosedElements())) { - String parameterName = annotationMember.getSimpleName().toString(); - TypeName parameterType = TypeName.get(annotationMember.getReturnType()); + for (XMethodElement annotationMember : annotationElement.getDeclaredMethods()) { + String parameterName = getSimpleName(annotationMember); + TypeName parameterType = annotationMember.getReturnType().getTypeName(); createMethod.addParameter(parameterType, parameterName); parameters.add(CodeBlock.of("$L", parameterName)); } @@ -134,30 +133,23 @@ public class AnnotationCreatorGenerator extends SourceFileGenerator<TypeElement> * Returns the annotation types for which {@code @AutoAnnotation static Foo createFoo(…)} methods * should be written. */ - protected Set<TypeElement> annotationsToCreate(TypeElement annotationElement) { + protected Set<XTypeElement> annotationsToCreate(XTypeElement annotationElement) { return nestedAnnotationElements(annotationElement, new LinkedHashSet<>()); } @CanIgnoreReturnValue - private static Set<TypeElement> nestedAnnotationElements( - TypeElement annotationElement, Set<TypeElement> annotationElements) { + private static Set<XTypeElement> nestedAnnotationElements( + XTypeElement annotationElement, Set<XTypeElement> annotationElements) { if (annotationElements.add(annotationElement)) { - for (ExecutableElement method : methodsIn(annotationElement.getEnclosedElements())) { - TRAVERSE_NESTED_ANNOTATIONS.visit(method.getReturnType(), annotationElements); + for (XMethodElement method : annotationElement.getDeclaredMethods()) { + XTypeElement returnType = method.getReturnType().getTypeElement(); + // Return type may be null if it doesn't return a type or type is not known + if (returnType != null && returnType.isAnnotationClass()) { + // Ignore the return value since this method is just an accumulator method. + nestedAnnotationElements(returnType, annotationElements); + } } } return annotationElements; } - - private static final SimpleTypeVisitor6<Void, Set<TypeElement>> TRAVERSE_NESTED_ANNOTATIONS = - new SimpleTypeVisitor6<Void, Set<TypeElement>>() { - @Override - public Void visitDeclared(DeclaredType t, Set<TypeElement> p) { - TypeElement typeElement = MoreTypes.asTypeElement(t); - if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { - nestedAnnotationElements(typeElement, p); - } - return null; - } - }; } diff --git a/java/dagger/internal/codegen/writing/AnonymousProviderCreationExpression.java b/java/dagger/internal/codegen/writing/AnonymousProviderCreationExpression.java index b1237ca75..7256fd6a6 100644 --- a/java/dagger/internal/codegen/writing/AnonymousProviderCreationExpression.java +++ b/java/dagger/internal/codegen/writing/AnonymousProviderCreationExpression.java @@ -16,12 +16,16 @@ package dagger.internal.codegen.writing; +import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import static dagger.internal.codegen.javapoet.CodeBlocks.anonymousProvider; -import static dagger.model.RequestKind.INSTANCE; +import static dagger.spi.model.RequestKind.INSTANCE; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.BindingRequest; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.javapoet.Expression; @@ -34,27 +38,33 @@ import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstan final class AnonymousProviderCreationExpression implements FrameworkInstanceCreationExpression { private final ContributionBinding binding; - private final ComponentBindingExpressions componentBindingExpressions; + private final ComponentRequestRepresentations componentRequestRepresentations; private final ClassName requestingClass; + @AssistedInject AnonymousProviderCreationExpression( - ContributionBinding binding, - ComponentBindingExpressions componentBindingExpressions, - ClassName requestingClass) { - this.binding = binding; - this.componentBindingExpressions = componentBindingExpressions; - this.requestingClass = requestingClass; + @Assisted ContributionBinding binding, + ComponentRequestRepresentations componentRequestRepresentations, + ComponentImplementation componentImplementation) { + this.binding = checkNotNull(binding); + this.componentRequestRepresentations = componentRequestRepresentations; + this.requestingClass = componentImplementation.name(); } @Override public CodeBlock creationExpression() { BindingRequest instanceExpressionRequest = bindingRequest(binding.key(), INSTANCE); Expression instanceExpression = - componentBindingExpressions.getDependencyExpression( + componentRequestRepresentations.getDependencyExpression( instanceExpressionRequest, // Not a real class name, but the actual requestingClass is an inner class within the // given class, not that class itself. requestingClass.nestedClass("Anonymous")); return anonymousProvider(instanceExpression); } + + @AssistedFactory + static interface Factory { + AnonymousProviderCreationExpression create(ContributionBinding binding); + } } diff --git a/java/dagger/internal/codegen/writing/AssistedFactoryBindingExpression.java b/java/dagger/internal/codegen/writing/AssistedFactoryBindingExpression.java deleted file mode 100644 index d90ab71f1..000000000 --- a/java/dagger/internal/codegen/writing/AssistedFactoryBindingExpression.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2020 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.writing; - -import static com.google.auto.common.MoreElements.asType; -import static com.google.auto.common.MoreTypes.asDeclared; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedFactoryMethod; -import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedFactoryParameterSpecs; - -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import dagger.internal.codegen.binding.BindingRequest; -import dagger.internal.codegen.binding.ProvisionBinding; -import dagger.internal.codegen.javapoet.Expression; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.DependencyRequest; -import dagger.model.RequestKind; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; - -/** - * A {@link dagger.internal.codegen.writing.BindingExpression} for {@link - * dagger.assisted.AssistedFactory} methods. - */ -final class AssistedFactoryBindingExpression extends SimpleInvocationBindingExpression { - private final ProvisionBinding binding; - private final ComponentBindingExpressions componentBindingExpressions; - private final DaggerElements elements; - private final DaggerTypes types; - - AssistedFactoryBindingExpression( - ProvisionBinding binding, - ComponentBindingExpressions componentBindingExpressions, - DaggerTypes types, - DaggerElements elements) { - super(binding); - this.binding = checkNotNull(binding); - this.componentBindingExpressions = checkNotNull(componentBindingExpressions); - this.elements = checkNotNull(elements); - this.types = checkNotNull(types); - } - - @Override - Expression getDependencyExpression(ClassName requestingClass) { - // An assisted factory binding should have a single request for an assisted injection type. - DependencyRequest assistedInjectionRequest = getOnlyElement(binding.provisionDependencies()); - Expression assistedInjectionExpression = - componentBindingExpressions.getDependencyExpression( - BindingRequest.bindingRequest(assistedInjectionRequest.key(), RequestKind.INSTANCE), - // This is kind of gross because the anonymous class doesn't really have a name we can - // reference. The requesting class name is really only needed to determine if we need to - // append "OwningClass.this." to the method call or not. - // TODO(bcorso): We should probably use a non-anonymous class here instead so that we - // actually have a proper class name. - requestingClass.peerClass("")); - return Expression.create( - assistedInjectionExpression.type(), - CodeBlock.of("$L", anonymousfactoryImpl(assistedInjectionExpression))); - } - - private TypeSpec anonymousfactoryImpl(Expression assistedInjectionExpression) { - TypeElement factory = asType(binding.bindingElement().get()); - DeclaredType factoryType = asDeclared(binding.key().type()); - ExecutableElement factoryMethod = assistedFactoryMethod(factory, elements); - - // We can't use MethodSpec.overriding directly because we need to control the parameter names. - MethodSpec factoryOverride = MethodSpec.overriding(factoryMethod, factoryType, types).build(); - TypeSpec.Builder builder = - TypeSpec.anonymousClassBuilder("") - .addMethod( - MethodSpec.methodBuilder(factoryMethod.getSimpleName().toString()) - .addModifiers(factoryOverride.modifiers) - .addTypeVariables(factoryOverride.typeVariables) - .returns(factoryOverride.returnType) - .addAnnotations(factoryOverride.annotations) - .addExceptions(factoryOverride.exceptions) - .addParameters(assistedFactoryParameterSpecs(binding, elements, types)) - .addStatement("return $L", assistedInjectionExpression.codeBlock()) - .build()); - - if (factory.getKind() == ElementKind.INTERFACE) { - builder.addSuperinterface(TypeName.get(factoryType)); - } else { - builder.superclass(TypeName.get(factoryType)); - } - - return builder.build(); - } -} diff --git a/java/dagger/internal/codegen/writing/AssistedFactoryRequestRepresentation.java b/java/dagger/internal/codegen/writing/AssistedFactoryRequestRepresentation.java new file mode 100644 index 000000000..485774ee8 --- /dev/null +++ b/java/dagger/internal/codegen/writing/AssistedFactoryRequestRepresentation.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 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.writing; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedFactoryMethod; +import static dagger.internal.codegen.writing.AssistedInjectionParameters.assistedFactoryParameterSpecs; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; + +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.internal.codegen.binding.Binding; +import dagger.internal.codegen.binding.BindingGraph; +import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.xprocessing.MethodSpecs; +import dagger.spi.model.DependencyRequest; +import java.util.Optional; + +/** + * A {@link dagger.internal.codegen.writing.RequestRepresentation} for {@link + * dagger.assisted.AssistedFactory} methods. + */ +final class AssistedFactoryRequestRepresentation extends RequestRepresentation { + private final ProvisionBinding binding; + private final BindingGraph graph; + private final SimpleMethodRequestRepresentation.Factory simpleMethodRequestRepresentationFactory; + private final ComponentImplementation componentImplementation; + + @AssistedInject + AssistedFactoryRequestRepresentation( + @Assisted ProvisionBinding binding, + BindingGraph graph, + ComponentImplementation componentImplementation, + SimpleMethodRequestRepresentation.Factory simpleMethodRequestRepresentationFactory) { + this.binding = checkNotNull(binding); + this.graph = graph; + this.componentImplementation = componentImplementation; + this.simpleMethodRequestRepresentationFactory = simpleMethodRequestRepresentationFactory; + } + + @Override + Expression getDependencyExpression(ClassName requestingClass) { + // An assisted factory binding should have a single request for an assisted injection type. + DependencyRequest assistedInjectionRequest = getOnlyElement(binding.provisionDependencies()); + // Get corresponding assisted injection binding. + Optional<Binding> localBinding = graph.localContributionBinding(assistedInjectionRequest.key()); + checkArgument( + localBinding.isPresent(), + "assisted factory should have a dependency on an assisted injection binding"); + Expression assistedInjectionExpression = + simpleMethodRequestRepresentationFactory + .create((ProvisionBinding) localBinding.get()) + .getDependencyExpression(requestingClass.peerClass("")); + return Expression.create( + assistedInjectionExpression.type(), + CodeBlock.of("$L", anonymousfactoryImpl(localBinding.get(), assistedInjectionExpression))); + } + + private TypeSpec anonymousfactoryImpl( + Binding assistedBinding, Expression assistedInjectionExpression) { + XTypeElement factory = asTypeElement(binding.bindingElement().get()); + XType factoryType = binding.key().type().xprocessing(); + XMethodElement factoryMethod = assistedFactoryMethod(factory); + + // We can't use MethodSpec.overriding directly because we need to control the parameter names. + MethodSpec factoryOverride = MethodSpecs.overriding(factoryMethod, factoryType).build(); + TypeSpec.Builder builder = + TypeSpec.anonymousClassBuilder("") + .addMethod( + MethodSpec.methodBuilder(getSimpleName(factoryMethod)) + .addModifiers(factoryOverride.modifiers) + .addTypeVariables(factoryOverride.typeVariables) + .returns(factoryOverride.returnType) + .addAnnotations(factoryOverride.annotations) + .addExceptions(factoryOverride.exceptions) + .addParameters( + assistedFactoryParameterSpecs( + binding, componentImplementation.shardImplementation(assistedBinding))) + .addStatement("return $L", assistedInjectionExpression.codeBlock()) + .build()); + + if (factory.isInterface()) { + builder.addSuperinterface(factoryType.getTypeName()); + } else { + builder.superclass(factoryType.getTypeName()); + } + + return builder.build(); + } + + @AssistedFactory + static interface Factory { + AssistedFactoryRequestRepresentation create(ProvisionBinding binding); + } +} diff --git a/java/dagger/internal/codegen/writing/AssistedInjectionParameters.java b/java/dagger/internal/codegen/writing/AssistedInjectionParameters.java new file mode 100644 index 000000000..9e55e031d --- /dev/null +++ b/java/dagger/internal/codegen/writing/AssistedInjectionParameters.java @@ -0,0 +1,102 @@ +/* + * 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.writing; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static com.google.common.base.Preconditions.checkArgument; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.internal.codegen.xprocessing.XElements.asConstructor; +import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; + +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XConstructorType; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.XVariableElement; +import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.ParameterSpec; +import dagger.internal.codegen.binding.AssistedInjectionAnnotations; +import dagger.internal.codegen.binding.AssistedInjectionAnnotations.AssistedFactoryMetadata; +import dagger.internal.codegen.binding.Binding; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; +import dagger.spi.model.BindingKind; +import java.util.List; + +/** Utility class for generating unique assisted parameter names for a component shard. */ +final class AssistedInjectionParameters { + /** + * Returns the list of assisted factory parameters as {@link ParameterSpec}s. + * + * <p>The type of each parameter will be the resolved type given by the binding key, and the name + * of each parameter will be the name given in the {@link + * dagger.assisted.AssistedInject}-annotated constructor. + */ + public static ImmutableList<ParameterSpec> assistedFactoryParameterSpecs( + Binding binding, ShardImplementation shardImplementation) { + checkArgument(binding.kind() == BindingKind.ASSISTED_FACTORY); + XTypeElement factory = asTypeElement(binding.bindingElement().get()); + AssistedFactoryMetadata metadata = AssistedFactoryMetadata.create(factory.getType()); + XMethodType factoryMethodType = + metadata.factoryMethod().asMemberOf(binding.key().type().xprocessing()); + return assistedParameterSpecs( + // Use the order of the parameters from the @AssistedFactory method but use the parameter + // names of the @AssistedInject constructor. + metadata.assistedFactoryAssistedParameters().stream() + .map(metadata.assistedInjectAssistedParametersMap()::get) + .collect(toImmutableList()), + factoryMethodType.getParameterTypes(), + shardImplementation); + } + + /** + * Returns the list of assisted parameters as {@link ParameterSpec}s. + * + * <p>The type of each parameter will be the resolved type given by the binding key, and the name + * of each parameter will be the name given in the {@link + * dagger.assisted.AssistedInject}-annotated constructor. + */ + public static ImmutableList<ParameterSpec> assistedParameterSpecs( + Binding binding, ShardImplementation shardImplementation) { + checkArgument(binding.kind() == BindingKind.ASSISTED_INJECTION); + XConstructorElement constructor = asConstructor(binding.bindingElement().get()); + XConstructorType constructorType = constructor.asMemberOf(binding.key().type().xprocessing()); + return assistedParameterSpecs( + constructor.getParameters(), constructorType.getParameterTypes(), shardImplementation); + } + + private static ImmutableList<ParameterSpec> assistedParameterSpecs( + List<? extends XVariableElement> paramElements, + List<XType> paramTypes, + ShardImplementation shardImplementation) { + ImmutableList.Builder<ParameterSpec> assistedParameterSpecs = ImmutableList.builder(); + for (int i = 0; i < paramElements.size(); i++) { + XVariableElement paramElement = paramElements.get(i); + XType paramType = paramTypes.get(i); + if (AssistedInjectionAnnotations.isAssistedParameter(paramElement)) { + assistedParameterSpecs.add( + ParameterSpec.builder( + paramType.getTypeName(), + shardImplementation.getUniqueFieldNameForAssistedParam(toJavac(paramElement))) + .build()); + } + } + return assistedParameterSpecs.build(); + } + + private AssistedInjectionParameters() {} +} diff --git a/java/dagger/internal/codegen/writing/BUILD b/java/dagger/internal/codegen/writing/BUILD index dcb88b387..c99a253ad 100644 --- a/java/dagger/internal/codegen/writing/BUILD +++ b/java/dagger/internal/codegen/writing/BUILD @@ -33,15 +33,16 @@ java_library( "//java/dagger/internal/codegen/javapoet", "//java/dagger/internal/codegen/kotlin", "//java/dagger/internal/codegen/langmodel", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", + "//java/dagger/internal/codegen/xprocessing", "//java/dagger/producers", "//java/dagger/spi", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/error_prone:annotations", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/jsr330_inject", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/error_prone:annotations", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/util/concurrent", + "//third_party/java/javapoet", + "//third_party/java/jsr330_inject", ], ) diff --git a/java/dagger/example/gradle/android/simple/app/src/main/java/dagger/example/gradle/android/simple/BuildModule.java b/java/dagger/internal/codegen/writing/BindingRepresentation.java index f6317c24d..cf01c5280 100644 --- a/java/dagger/example/gradle/android/simple/app/src/main/java/dagger/example/gradle/android/simple/BuildModule.java +++ b/java/dagger/internal/codegen/writing/BindingRepresentation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Dagger Authors. + * Copyright (C) 2021 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. @@ -14,18 +14,11 @@ * limitations under the License. */ -package dagger.example.gradle.android.simple; +package dagger.internal.codegen.writing; -import static android.os.Build.MODEL; +import dagger.internal.codegen.binding.BindingRequest; -import dagger.Module; -import dagger.Provides; - -@Module -final class BuildModule { - @Provides - @Model - static String provideModel() { - return MODEL; - } +/** A factory of code expressions to satisfy all kinds of requests for a binding in a component. */ +interface BindingRepresentation { + RequestRepresentation getRequestRepresentation(BindingRequest request); } diff --git a/java/dagger/internal/codegen/writing/BindingRepresentations.java b/java/dagger/internal/codegen/writing/BindingRepresentations.java new file mode 100644 index 000000000..35584743c --- /dev/null +++ b/java/dagger/internal/codegen/writing/BindingRepresentations.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 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.writing; + +import static dagger.internal.codegen.javapoet.TypeNames.DOUBLE_CHECK; +import static dagger.internal.codegen.javapoet.TypeNames.SINGLE_CHECK; + +import com.squareup.javapoet.CodeBlock; +import dagger.internal.codegen.binding.Binding; +import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; + +/** Holds common methods for BindingRepresentations. */ +final class BindingRepresentations { + static FrameworkInstanceCreationExpression scope( + Binding binding, FrameworkInstanceCreationExpression unscoped) { + return () -> + CodeBlock.of( + "$T.provider($L)", + binding.scope().get().isReusable() ? SINGLE_CHECK : DOUBLE_CHECK, + unscoped.creationExpression()); + } + + private BindingRepresentations() {} +} diff --git a/java/dagger/internal/codegen/writing/ComponentBindingExpressions.java b/java/dagger/internal/codegen/writing/ComponentBindingExpressions.java deleted file mode 100644 index dc15c998b..000000000 --- a/java/dagger/internal/codegen/writing/ComponentBindingExpressions.java +++ /dev/null @@ -1,707 +0,0 @@ -/* - * Copyright (C) 2016 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.writing; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Verify.verify; -import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; -import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; -import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; -import static dagger.internal.codegen.javapoet.TypeNames.DOUBLE_CHECK; -import static dagger.internal.codegen.javapoet.TypeNames.SINGLE_CHECK; -import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessible; -import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; -import static dagger.internal.codegen.writing.DelegateBindingExpression.isBindsScopeStrongerThanDependencyScope; -import static dagger.internal.codegen.writing.MemberSelect.staticFactoryCreation; -import static dagger.model.BindingKind.DELEGATE; -import static dagger.model.BindingKind.MULTIBOUND_MAP; -import static dagger.model.BindingKind.MULTIBOUND_SET; - -import com.google.auto.common.MoreTypes; -import com.google.common.collect.ImmutableList; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.MethodSpec; -import dagger.internal.codegen.binding.Binding; -import dagger.internal.codegen.binding.BindingGraph; -import dagger.internal.codegen.binding.BindingNode; -import dagger.internal.codegen.binding.BindingRequest; -import dagger.internal.codegen.binding.BindingType; -import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; -import dagger.internal.codegen.binding.ComponentRequirement; -import dagger.internal.codegen.binding.ContributionBinding; -import dagger.internal.codegen.binding.FrameworkType; -import dagger.internal.codegen.binding.FrameworkTypeMapper; -import dagger.internal.codegen.binding.MembersInjectionBinding; -import dagger.internal.codegen.binding.ProvisionBinding; -import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.internal.codegen.javapoet.Expression; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; -import dagger.internal.codegen.writing.MethodBindingExpression.MethodImplementationStrategy; -import dagger.model.BindingKind; -import dagger.model.DependencyRequest; -import dagger.model.Key; -import dagger.model.RequestKind; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import javax.inject.Inject; -import javax.inject.Provider; -import javax.lang.model.SourceVersion; -import javax.lang.model.type.TypeMirror; - -/** A central repository of code expressions used to access any binding available to a component. */ -@PerComponentImplementation -public final class ComponentBindingExpressions { - // TODO(dpb,ronshapiro): refactor this and ComponentRequirementExpressions into a - // HierarchicalComponentMap<K, V>, or perhaps this use a flattened ImmutableMap, built from its - // parents? If so, maybe make BindingExpression.Factory create it. - - private final Optional<ComponentBindingExpressions> parent; - private final BindingGraph graph; - private final ComponentImplementation componentImplementation; - private final ComponentImplementation topLevelComponentImplementation; - private final ComponentRequirementExpressions componentRequirementExpressions; - private final OptionalFactories optionalFactories; - private final DaggerTypes types; - private final DaggerElements elements; - private final SourceVersion sourceVersion; - private final CompilerOptions compilerOptions; - private final MembersInjectionMethods membersInjectionMethods; - private final InnerSwitchingProviders innerSwitchingProviders; - private final Map<BindingRequest, BindingExpression> expressions = new HashMap<>(); - private final KotlinMetadataUtil metadataUtil; - - @Inject - ComponentBindingExpressions( - @ParentComponent Optional<ComponentBindingExpressions> parent, - BindingGraph graph, - ComponentImplementation componentImplementation, - @TopLevel ComponentImplementation topLevelComponentImplementation, - ComponentRequirementExpressions componentRequirementExpressions, - OptionalFactories optionalFactories, - DaggerTypes types, - DaggerElements elements, - SourceVersion sourceVersion, - CompilerOptions compilerOptions, - KotlinMetadataUtil metadataUtil) { - this.parent = parent; - this.graph = graph; - this.componentImplementation = componentImplementation; - this.topLevelComponentImplementation = topLevelComponentImplementation; - this.componentRequirementExpressions = checkNotNull(componentRequirementExpressions); - this.optionalFactories = checkNotNull(optionalFactories); - this.types = checkNotNull(types); - this.elements = checkNotNull(elements); - this.sourceVersion = checkNotNull(sourceVersion); - this.compilerOptions = checkNotNull(compilerOptions); - this.membersInjectionMethods = - new MembersInjectionMethods( - componentImplementation, this, graph, elements, types, metadataUtil); - this.innerSwitchingProviders = - new InnerSwitchingProviders(componentImplementation, this, types); - this.metadataUtil = metadataUtil; - } - - /** - * Returns an expression that evaluates to the value of a binding request for a binding owned by - * this component or an ancestor. - * - * @param requestingClass the class that will contain the expression - * @throws IllegalStateException if there is no binding expression that satisfies the request - */ - public Expression getDependencyExpression(BindingRequest request, ClassName requestingClass) { - return getBindingExpression(request).getDependencyExpression(requestingClass); - } - - /** - * Equivalent to {@link #getDependencyExpression(BindingRequest, ClassName)} that is used only - * when the request is for implementation of a component method. - * - * @throws IllegalStateException if there is no binding expression that satisfies the request - */ - Expression getDependencyExpressionForComponentMethod( - BindingRequest request, - ComponentMethodDescriptor componentMethod, - ComponentImplementation componentImplementation) { - return getBindingExpression(request) - .getDependencyExpressionForComponentMethod(componentMethod, componentImplementation); - } - - /** - * Returns the {@link CodeBlock} for the method arguments used with the factory {@code create()} - * method for the given {@link ContributionBinding binding}. - */ - CodeBlock getCreateMethodArgumentsCodeBlock(ContributionBinding binding) { - return makeParametersCodeBlock(getCreateMethodArgumentsCodeBlocks(binding)); - } - - private ImmutableList<CodeBlock> getCreateMethodArgumentsCodeBlocks(ContributionBinding binding) { - ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder(); - - if (binding.requiresModuleInstance()) { - arguments.add( - componentRequirementExpressions.getExpressionDuringInitialization( - ComponentRequirement.forModule(binding.contributingModule().get().asType()), - componentImplementation.name())); - } - - binding.dependencies().stream() - .map(dependency -> frameworkRequest(binding, dependency)) - .map(request -> getDependencyExpression(request, componentImplementation.name())) - .map(Expression::codeBlock) - .forEach(arguments::add); - - return arguments.build(); - } - - private static BindingRequest frameworkRequest( - ContributionBinding binding, DependencyRequest dependency) { - // TODO(bcorso): See if we can get rid of FrameworkTypeMatcher - FrameworkType frameworkType = - FrameworkTypeMapper.forBindingType(binding.bindingType()) - .getFrameworkType(dependency.kind()); - return BindingRequest.bindingRequest(dependency.key(), frameworkType); - } - - /** - * Returns an expression that evaluates to the value of a dependency request, for passing to a - * binding method, an {@code @Inject}-annotated constructor or member, or a proxy for one. - * - * <p>If the method is a generated static {@link InjectionMethods injection method}, each - * parameter will be {@link Object} if the dependency's raw type is inaccessible. If that is the - * case for this dependency, the returned expression will use a cast to evaluate to the raw type. - * - * @param requestingClass the class that will contain the expression - */ - Expression getDependencyArgumentExpression( - DependencyRequest dependencyRequest, ClassName requestingClass) { - - TypeMirror dependencyType = dependencyRequest.key().type(); - BindingRequest bindingRequest = bindingRequest(dependencyRequest); - Expression dependencyExpression = getDependencyExpression(bindingRequest, requestingClass); - - if (dependencyRequest.kind().equals(RequestKind.INSTANCE) - && !isTypeAccessibleFrom(dependencyType, requestingClass.packageName()) - && isRawTypeAccessible(dependencyType, requestingClass.packageName())) { - return dependencyExpression.castTo(types.erasure(dependencyType)); - } - - return dependencyExpression; - } - - /** Returns the implementation of a component method. */ - public MethodSpec getComponentMethod(ComponentMethodDescriptor componentMethod) { - checkArgument(componentMethod.dependencyRequest().isPresent()); - BindingRequest request = bindingRequest(componentMethod.dependencyRequest().get()); - return MethodSpec.overriding( - componentMethod.methodElement(), - MoreTypes.asDeclared(graph.componentTypeElement().asType()), - types) - .addCode( - getBindingExpression(request) - .getComponentMethodImplementation(componentMethod, componentImplementation)) - .build(); - } - - /** Returns the {@link BindingExpression} for the given {@link BindingRequest}. */ - BindingExpression getBindingExpression(BindingRequest request) { - if (expressions.containsKey(request)) { - return expressions.get(request); - } - - Optional<Binding> optionalBinding = - graph.bindingNodes(request.key()).stream() - // Filter out nodes we don't own. - .filter(bindingNode -> bindingNode.componentPath().equals(graph.componentPath())) - // Filter out nodes that don't match the request kind - .filter( - bindingNode -> - // The binding used for the binding expression depends on the request: - // 1. MembersInjectionBinding: satisfies MEMBERS_INJECTION requests - // 2. ContributionBindings: satisfies all other requests. - request.isRequestKind(RequestKind.MEMBERS_INJECTION) - ? bindingNode.delegate().bindingType() == BindingType.MEMBERS_INJECTION - : bindingNode.delegate().bindingType() == BindingType.PROVISION - || bindingNode.delegate().bindingType() == BindingType.PRODUCTION) - .map(BindingNode::delegate) - // We expect at most one binding to match since this graph is already validated. - .collect(toOptional()); - - if (optionalBinding.isPresent()) { - BindingExpression expression = createBindingExpression(optionalBinding.get(), request); - expressions.put(request, expression); - return expression; - } - - checkArgument(parent.isPresent(), "no expression found for %s", request); - return parent.get().getBindingExpression(request); - } - - /** Creates a binding expression. */ - private BindingExpression createBindingExpression(Binding binding, BindingRequest request) { - switch (binding.bindingType()) { - case MEMBERS_INJECTION: - checkArgument(request.isRequestKind(RequestKind.MEMBERS_INJECTION)); - return new MembersInjectionBindingExpression( - (MembersInjectionBinding) binding, membersInjectionMethods); - - case PROVISION: - return provisionBindingExpression((ContributionBinding) binding, request); - - case PRODUCTION: - return productionBindingExpression((ContributionBinding) binding, request); - } - throw new AssertionError(binding); - } - - /** - * Returns a binding expression that uses a {@link javax.inject.Provider} for provision bindings - * or a {@link dagger.producers.Producer} for production bindings. - */ - private BindingExpression frameworkInstanceBindingExpression(ContributionBinding binding) { - // TODO(bcorso): Consider merging the static factory creation logic into CreationExpressions? - Optional<MemberSelect> staticMethod = - useStaticFactoryCreation(binding) ? staticFactoryCreation(binding) : Optional.empty(); - FrameworkInstanceSupplier frameworkInstanceSupplier = - staticMethod.isPresent() - ? staticMethod::get - : new FrameworkFieldInitializer( - componentImplementation, - binding, - binding.scope().isPresent() - ? scope(binding, frameworkInstanceCreationExpression(binding)) - : frameworkInstanceCreationExpression(binding)); - - switch (binding.bindingType()) { - case PROVISION: - return new ProviderInstanceBindingExpression( - binding, frameworkInstanceSupplier, types, elements); - case PRODUCTION: - return new ProducerNodeInstanceBindingExpression( - binding, frameworkInstanceSupplier, types, elements, componentImplementation); - default: - throw new AssertionError("invalid binding type: " + binding.bindingType()); - } - } - - private FrameworkInstanceCreationExpression scope( - ContributionBinding binding, FrameworkInstanceCreationExpression unscoped) { - return () -> - CodeBlock.of( - "$T.provider($L)", - binding.scope().get().isReusable() ? SINGLE_CHECK : DOUBLE_CHECK, - unscoped.creationExpression()); - } - - /** - * Returns a creation expression for a {@link javax.inject.Provider} for provision bindings or a - * {@link dagger.producers.Producer} for production bindings. - */ - private FrameworkInstanceCreationExpression frameworkInstanceCreationExpression( - ContributionBinding binding) { - switch (binding.kind()) { - case COMPONENT: - // The cast can be removed when we drop java 7 source support - return new InstanceFactoryCreationExpression( - () -> CodeBlock.of("($T) this", binding.key().type())); - - case BOUND_INSTANCE: - return instanceFactoryCreationExpression( - binding, ComponentRequirement.forBoundInstance(binding)); - - case COMPONENT_DEPENDENCY: - return instanceFactoryCreationExpression( - binding, ComponentRequirement.forDependency(binding.key().type())); - - case COMPONENT_PROVISION: - return new DependencyMethodProviderCreationExpression( - binding, - componentImplementation, - componentRequirementExpressions, - compilerOptions, - graph); - - case SUBCOMPONENT_CREATOR: - return new AnonymousProviderCreationExpression( - binding, this, componentImplementation.name()); - - case ASSISTED_FACTORY: - case ASSISTED_INJECTION: - case INJECTION: - case PROVISION: - return new InjectionOrProvisionProviderCreationExpression(binding, this); - - case COMPONENT_PRODUCTION: - return new DependencyMethodProducerCreationExpression( - binding, componentImplementation, componentRequirementExpressions, graph); - - case PRODUCTION: - return new ProducerCreationExpression(binding, this); - - case MULTIBOUND_SET: - return new SetFactoryCreationExpression(binding, componentImplementation, this, graph); - - case MULTIBOUND_MAP: - return new MapFactoryCreationExpression( - binding, componentImplementation, this, graph, elements); - - case DELEGATE: - return new DelegatingFrameworkInstanceCreationExpression( - binding, componentImplementation, this); - - case OPTIONAL: - return new OptionalFactoryInstanceCreationExpression( - optionalFactories, binding, componentImplementation, this); - - case MEMBERS_INJECTOR: - return new MembersInjectorProviderCreationExpression((ProvisionBinding) binding, this); - - default: - throw new AssertionError(binding); - } - } - - private InstanceFactoryCreationExpression instanceFactoryCreationExpression( - ContributionBinding binding, ComponentRequirement componentRequirement) { - return new InstanceFactoryCreationExpression( - binding.nullableType().isPresent(), - () -> - componentRequirementExpressions.getExpressionDuringInitialization( - componentRequirement, componentImplementation.name())); - } - - /** Returns a binding expression for a provision binding. */ - private BindingExpression provisionBindingExpression( - ContributionBinding binding, BindingRequest request) { - if (!request.requestKind().isPresent()) { - verify( - request.frameworkType().get().equals(FrameworkType.PRODUCER_NODE), - "expected a PRODUCER_NODE: %s", - request); - return producerFromProviderBindingExpression(binding); - } - RequestKind requestKind = request.requestKind().get(); - Key key = request.key(); - switch (requestKind) { - case INSTANCE: - return instanceBindingExpression(binding); - - case PROVIDER: - return providerBindingExpression(binding); - - case LAZY: - case PRODUCED: - case PROVIDER_OF_LAZY: - return new DerivedFromFrameworkInstanceBindingExpression( - key, FrameworkType.PROVIDER, requestKind, this, types); - - case PRODUCER: - return producerFromProviderBindingExpression(binding); - - case FUTURE: - return new ImmediateFutureBindingExpression(key, this, types, sourceVersion); - - case MEMBERS_INJECTION: - throw new IllegalArgumentException(); - } - - throw new AssertionError(); - } - - /** Returns a binding expression for a production binding. */ - private BindingExpression productionBindingExpression( - ContributionBinding binding, BindingRequest request) { - if (request.frameworkType().isPresent()) { - return frameworkInstanceBindingExpression(binding); - } else { - // If no FrameworkType is present, a RequestKind is guaranteed to be present. - RequestKind requestKind = request.requestKind().get(); - return new DerivedFromFrameworkInstanceBindingExpression( - request.key(), FrameworkType.PRODUCER_NODE, requestKind, this, types); - } - } - - /** - * Returns a binding expression for {@link RequestKind#PROVIDER} requests. - * - * <p>{@code @Binds} bindings that don't {@linkplain #needsCaching(ContributionBinding) need to be - * cached} can use a {@link DelegateBindingExpression}. - * - * <p>In fastInit mode, use an {@link InnerSwitchingProviders inner switching provider} unless - * that provider's case statement will simply call {@code get()} on another {@link Provider} (in - * which case, just use that Provider directly). - * - * <p>Otherwise, return a {@link FrameworkInstanceBindingExpression}. - */ - private BindingExpression providerBindingExpression(ContributionBinding binding) { - if (binding.kind().equals(DELEGATE) && !needsCaching(binding)) { - return new DelegateBindingExpression(binding, RequestKind.PROVIDER, this, types, elements); - } else if (isFastInit() - && frameworkInstanceCreationExpression(binding).useInnerSwitchingProvider() - && !(instanceBindingExpression(binding) - instanceof DerivedFromFrameworkInstanceBindingExpression)) { - return wrapInMethod( - binding, - bindingRequest(binding.key(), RequestKind.PROVIDER), - innerSwitchingProviders.newBindingExpression(binding)); - } - return frameworkInstanceBindingExpression(binding); - } - - /** - * Returns a binding expression that uses a {@link dagger.producers.Producer} field for a - * provision binding. - */ - private FrameworkInstanceBindingExpression producerFromProviderBindingExpression( - ContributionBinding binding) { - checkArgument(binding.bindingType().equals(BindingType.PROVISION)); - return new ProducerNodeInstanceBindingExpression( - binding, - new FrameworkFieldInitializer( - componentImplementation, - binding, - new ProducerFromProviderCreationExpression(binding, componentImplementation, this)), - types, - elements, - componentImplementation); - } - - /** - * Returns a binding expression for {@link RequestKind#INSTANCE} requests. - */ - private BindingExpression instanceBindingExpression(ContributionBinding binding) { - Optional<BindingExpression> maybeDirectInstanceExpression = - unscopedDirectInstanceExpression(binding); - if (maybeDirectInstanceExpression.isPresent()) { - // If this is the case where we don't need to use Provider#get() because there's no caching - // and it isn't an assisted factory, or because we're in fastInit mode (since fastInit avoids - // using Providers), we can try to use the direct expression, possibly wrapped in a method - // if necessary (e.g. it has dependencies). - if ((!needsCaching(binding) && binding.kind() != BindingKind.ASSISTED_FACTORY) - || isFastInit()) { - BindingExpression directInstanceExpression = maybeDirectInstanceExpression.get(); - // While this can't require caching in default mode, if we're in fastInit mode and we need - // caching we also need to wrap it in a method. - return directInstanceExpression.requiresMethodEncapsulation() || needsCaching(binding) - ? wrapInMethod( - binding, - bindingRequest(binding.key(), RequestKind.INSTANCE), - directInstanceExpression) - : directInstanceExpression; - } - } - return new DerivedFromFrameworkInstanceBindingExpression( - binding.key(), FrameworkType.PROVIDER, RequestKind.INSTANCE, this, types); - } - - /** - * Returns an unscoped binding expression for an {@link RequestKind#INSTANCE} that does not call - * {@code get()} on its provider, if there is one. - */ - private Optional<BindingExpression> unscopedDirectInstanceExpression( - ContributionBinding binding) { - switch (binding.kind()) { - case DELEGATE: - return Optional.of( - new DelegateBindingExpression(binding, RequestKind.INSTANCE, this, types, elements)); - - case COMPONENT: - return Optional.of( - new ComponentInstanceBindingExpression(binding, componentImplementation.name())); - - case COMPONENT_DEPENDENCY: - return Optional.of( - new ComponentRequirementBindingExpression( - binding, - ComponentRequirement.forDependency(binding.key().type()), - componentRequirementExpressions)); - - case COMPONENT_PROVISION: - return Optional.of( - new ComponentProvisionBindingExpression( - (ProvisionBinding) binding, - graph, - componentRequirementExpressions, - compilerOptions)); - - case SUBCOMPONENT_CREATOR: - return Optional.of( - new SubcomponentCreatorBindingExpression( - binding, componentImplementation.getSubcomponentCreatorSimpleName(binding.key()))); - - case MULTIBOUND_SET: - return Optional.of( - new SetBindingExpression((ProvisionBinding) binding, graph, this, types, elements)); - - case MULTIBOUND_MAP: - return Optional.of( - new MapBindingExpression((ProvisionBinding) binding, graph, this, types, elements)); - - case OPTIONAL: - return Optional.of( - new OptionalBindingExpression((ProvisionBinding) binding, this, types, sourceVersion)); - - case BOUND_INSTANCE: - return Optional.of( - new ComponentRequirementBindingExpression( - binding, - ComponentRequirement.forBoundInstance(binding), - componentRequirementExpressions)); - - case ASSISTED_FACTORY: - return Optional.of( - new AssistedFactoryBindingExpression( - (ProvisionBinding) binding, this, types, elements)); - - case ASSISTED_INJECTION: - case INJECTION: - case PROVISION: - return Optional.of( - new SimpleMethodBindingExpression( - (ProvisionBinding) binding, - compilerOptions, - this, - membersInjectionMethods, - componentRequirementExpressions, - elements, - sourceVersion, - metadataUtil)); - - case MEMBERS_INJECTOR: - return Optional.empty(); - - case MEMBERS_INJECTION: - case COMPONENT_PRODUCTION: - case PRODUCTION: - throw new IllegalArgumentException(binding.kind().toString()); - default: - throw new AssertionError("Unexpected binding kind: " + binding.kind()); - } - } - - /** - * Returns {@code true} if the binding should use the static factory creation strategy. - * - * <p>In default mode, we always use the static factory creation strategy. In fastInit mode, we - * prefer to use a SwitchingProvider instead of static factories in order to reduce class loading; - * however, we allow static factories that can reused across multiple bindings, e.g. {@code - * MapFactory} or {@code SetFactory}. - */ - private boolean useStaticFactoryCreation(ContributionBinding binding) { - return !isFastInit() - || binding.kind().equals(MULTIBOUND_MAP) - || binding.kind().equals(MULTIBOUND_SET); - } - - /** - * Returns a binding expression that uses a given one as the body of a method that users call. If - * a component provision method matches it, it will be the method implemented. If it does not - * match a component provision method and the binding is modifiable, then a new public modifiable - * binding method will be written. If the binding doesn't match a component method and is not - * modifiable, then a new private method will be written. - */ - BindingExpression wrapInMethod( - ContributionBinding binding, BindingRequest request, BindingExpression bindingExpression) { - // If we've already wrapped the expression, then use the delegate. - if (bindingExpression instanceof MethodBindingExpression) { - return bindingExpression; - } - - MethodImplementationStrategy methodImplementationStrategy = - methodImplementationStrategy(binding, request); - Optional<ComponentMethodDescriptor> matchingComponentMethod = - graph.componentDescriptor().firstMatchingComponentMethod(request); - - ComponentImplementation shard = componentImplementation.shardImplementation(binding.key()); - - // Consider the case of a request from a component method like: - // - // DaggerMyComponent extends MyComponent { - // @Overrides - // Foo getFoo() { - // <FOO_BINDING_REQUEST> - // } - // } - // - // Normally, in this case we would return a ComponentMethodBindingExpression rather than a - // PrivateMethodBindingExpression so that #getFoo() can inline the implementation rather than - // create an unnecessary private method and return that. However, with sharding we don't want to - // inline the implementation because that would defeat some of the class pool savings if those - // fields had to communicate across shards. Thus, when a key belongs to a separate shard use a - // PrivateMethodBindingExpression and put the private method in the shard. - if (matchingComponentMethod.isPresent() && componentImplementation == shard) { - ComponentMethodDescriptor componentMethod = matchingComponentMethod.get(); - return new ComponentMethodBindingExpression( - request, - binding, - methodImplementationStrategy, - bindingExpression, - componentImplementation, - componentMethod, - types); - } else { - return new PrivateMethodBindingExpression( - request, - binding, - methodImplementationStrategy, - bindingExpression, - shard, - types, - compilerOptions); - } - } - - private MethodImplementationStrategy methodImplementationStrategy( - ContributionBinding binding, BindingRequest request) { - if (isFastInit()) { - if (request.isRequestKind(RequestKind.PROVIDER)) { - return MethodImplementationStrategy.SINGLE_CHECK; - } else if (request.isRequestKind(RequestKind.INSTANCE) && needsCaching(binding)) { - return binding.scope().get().isReusable() - ? MethodImplementationStrategy.SINGLE_CHECK - : MethodImplementationStrategy.DOUBLE_CHECK; - } - } - return MethodImplementationStrategy.SIMPLE; - } - - /** - * Returns {@code true} if the component needs to make sure the provided value is cached. - * - * <p>The component needs to cache the value for scoped bindings except for {@code @Binds} - * bindings whose scope is no stronger than their delegate's. - */ - private boolean needsCaching(ContributionBinding binding) { - if (!binding.scope().isPresent()) { - return false; - } - if (binding.kind().equals(DELEGATE)) { - return isBindsScopeStrongerThanDependencyScope(binding, graph); - } - return true; - } - - private boolean isFastInit() { - return compilerOptions.fastInit( - topLevelComponentImplementation.componentDescriptor().typeElement()); - } -} diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java b/java/dagger/internal/codegen/writing/ComponentCreatorImplementationFactory.java index 66270c697..313b37921 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java +++ b/java/dagger/internal/codegen/writing/ComponentCreatorImplementationFactory.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package dagger.internal.codegen.componentgenerator; +package dagger.internal.codegen.writing; -import static com.google.auto.common.MoreTypes.asDeclared; +import static androidx.room.compiler.processing.XTypeKt.isVoid; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; -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.binding.SourceFiles.simpleVariableName; @@ -32,11 +31,12 @@ import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; @@ -50,40 +50,25 @@ import dagger.internal.codegen.binding.ComponentDescriptor; import dagger.internal.codegen.binding.ComponentRequirement; import dagger.internal.codegen.binding.ComponentRequirement.NullPolicy; import dagger.internal.codegen.javapoet.TypeNames; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.internal.codegen.writing.ComponentCreatorImplementation; -import dagger.internal.codegen.writing.ComponentImplementation; -import dagger.internal.codegen.writing.ModuleProxies; +import dagger.internal.codegen.xprocessing.MethodSpecs; +import dagger.internal.codegen.xprocessing.XElements; import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import javax.inject.Inject; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; /** Factory for creating {@link ComponentCreatorImplementation} instances. */ final class ComponentCreatorImplementationFactory { private final ComponentImplementation componentImplementation; - private final DaggerElements elements; - private final DaggerTypes types; - private final KotlinMetadataUtil metadataUtil; private final ModuleProxies moduleProxies; @Inject ComponentCreatorImplementationFactory( ComponentImplementation componentImplementation, - DaggerElements elements, - DaggerTypes types, - KotlinMetadataUtil metadataUtil, ModuleProxies moduleProxies) { this.componentImplementation = componentImplementation; - this.elements = elements; - this.types = types; - this.metadataUtil = metadataUtil; this.moduleProxies = moduleProxies; } @@ -98,34 +83,28 @@ final class ComponentCreatorImplementationFactory { Builder builder = creatorDescriptor.isPresent() - ? new BuilderForCreatorDescriptor(componentImplementation, creatorDescriptor.get()) - : new BuilderForGeneratedRootComponentBuilder(componentImplementation); + ? new BuilderForCreatorDescriptor(creatorDescriptor.get()) + : new BuilderForGeneratedRootComponentBuilder(); return Optional.of(builder.build()); } /** Base class for building a creator implementation. */ private abstract class Builder { - final ComponentImplementation componentImplementation; - final ClassName className; - final TypeSpec.Builder classBuilder; - + private final TypeSpec.Builder classBuilder = + classBuilder(componentImplementation.getCreatorName()); + private final UniqueNameSet fieldNames = new UniqueNameSet(); private ImmutableMap<ComponentRequirement, FieldSpec> fields; - Builder(ComponentImplementation componentImplementation) { - this.componentImplementation = componentImplementation; - this.className = componentImplementation.getCreatorName(); - this.classBuilder = classBuilder(className); - } - /** Builds the {@link ComponentCreatorImplementation}. */ ComponentCreatorImplementation build() { setModifiers(); setSupertype(); - this.fields = addFields(); addConstructor(); + this.fields = addFields(); addSetterMethods(); addFactoryMethod(); - return ComponentCreatorImplementation.create(classBuilder.build(), className, fields); + return ComponentCreatorImplementation.create( + classBuilder.build(), componentImplementation.getCreatorName(), fields); } /** Returns the descriptor for the component. */ @@ -168,10 +147,7 @@ final class ComponentCreatorImplementationFactory { private void setModifiers() { visibility().ifPresent(classBuilder::addModifiers); - if (!componentImplementation.isNested()) { - classBuilder.addModifiers(STATIC); - } - classBuilder.addModifiers(FINAL); + classBuilder.addModifiers(STATIC, FINAL); } /** Returns the visibility modifier the generated class should have, if any. */ @@ -181,17 +157,28 @@ final class ComponentCreatorImplementationFactory { protected abstract void setSupertype(); /** Adds a constructor for the creator type, if needed. */ - protected abstract void addConstructor(); + protected void addConstructor() { + MethodSpec.Builder constructor = MethodSpec.constructorBuilder().addModifiers(PRIVATE); + componentImplementation + .creatorComponentFields() + .forEach( + field -> { + fieldNames.claim(field.name); + classBuilder.addField(field); + constructor.addParameter(field.type, field.name); + constructor.addStatement("this.$1N = $1N", field); + }); + classBuilder.addMethod(constructor.build()); + } private ImmutableMap<ComponentRequirement, FieldSpec> addFields() { // Fields in an abstract creator class need to be visible from subclasses. - UniqueNameSet fieldNames = new UniqueNameSet(); ImmutableMap<ComponentRequirement, FieldSpec> result = Maps.toMap( Sets.intersection(neededUserSettableRequirements(), setterMethods()), requirement -> FieldSpec.builder( - TypeName.get(requirement.type()), + requirement.type().getTypeName(), fieldNames.getUniqueName(requirement.variableName()), PRIVATE) .build()); @@ -220,7 +207,8 @@ final class ComponentCreatorImplementationFactory { // to generate noop setters for impossible cases like when the requirement type // is in another package. This avoids unnecessary breakages in Dagger's generated // due to the noop setters. - if (isElementAccessibleFrom(requirement.typeElement(), className.packageName())) { + if (isElementAccessibleFrom( + requirement.typeElement(), componentImplementation.name().packageName())) { return Optional.of(noopSetterMethod(requirement)); } else { return Optional.empty(); @@ -237,7 +225,7 @@ final class ComponentCreatorImplementationFactory { method.addStatement( "this.$N = $L", fields.get(requirement), - requirement.nullPolicy(elements, metadataUtil).equals(NullPolicy.ALLOW) + requirement.nullPolicy().equals(NullPolicy.ALLOW) ? CodeBlock.of("$N", parameter) : CodeBlock.of("$T.checkNotNull($N)", Preconditions.class, parameter)); return maybeReturnThis(method); @@ -262,7 +250,7 @@ final class ComponentCreatorImplementationFactory { UnsupportedOperationException.class, String.class, "%s cannot be set because it is inherited from the enclosing component", - TypeNames.rawTypeName(TypeName.get(requirement.type()))) + TypeNames.rawTypeName(requirement.type().getTypeName())) .build(); } @@ -284,7 +272,7 @@ final class ComponentCreatorImplementationFactory { MethodSpec factoryMethod() { MethodSpec.Builder factoryMethod = factoryMethodBuilder(); factoryMethod - .returns(ClassName.get(componentDescriptor().typeElement())) + .returns(componentDescriptor().typeElement().getClassName()) .addModifiers(PUBLIC); ImmutableMap<ComponentRequirement, String> factoryMethodParameters = @@ -310,7 +298,7 @@ final class ComponentCreatorImplementationFactory { private void addNullHandlingForField( ComponentRequirement requirement, FieldSpec field, MethodSpec.Builder factoryMethod) { - switch (requirement.nullPolicy(elements, metadataUtil)) { + switch (requirement.nullPolicy()) { case NEW: checkState(requirement.kind().isModule()); factoryMethod @@ -334,7 +322,7 @@ final class ComponentCreatorImplementationFactory { private void addNullHandlingForParameter( ComponentRequirement requirement, String parameter, MethodSpec.Builder factoryMethod) { - if (!requirement.nullPolicy(elements, metadataUtil).equals(NullPolicy.ALLOW)) { + if (!requirement.nullPolicy().equals(NullPolicy.ALLOW)) { // Factory method parameters are always required unless they are a nullable // binds-instance (i.e. ALLOW) factoryMethod.addStatement("$T.checkNotNull($L)", Preconditions.class, parameter); @@ -346,23 +334,27 @@ final class ComponentCreatorImplementationFactory { private CodeBlock componentConstructorArgs( ImmutableMap<ComponentRequirement, String> factoryMethodParameters) { - return componentConstructorRequirements().stream() - .map( - requirement -> { - if (fields.containsKey(requirement)) { - return CodeBlock.of("$N", fields.get(requirement)); - } else if (factoryMethodParameters.containsKey(requirement)) { - return CodeBlock.of("$L", factoryMethodParameters.get(requirement)); - } else { - return newModuleInstance(requirement); - } - }) + return Stream.concat( + componentImplementation.creatorComponentFields().stream() + .map(field -> CodeBlock.of("$N", field)), + componentConstructorRequirements().stream() + .map( + requirement -> { + if (fields.containsKey(requirement)) { + return CodeBlock.of("$N", fields.get(requirement)); + } else if (factoryMethodParameters.containsKey(requirement)) { + return CodeBlock.of("$L", factoryMethodParameters.get(requirement)); + } else { + return newModuleInstance(requirement); + } + })) .collect(toParametersCodeBlock()); } private CodeBlock newModuleInstance(ComponentRequirement requirement) { checkArgument(requirement.kind().isModule()); // this should be guaranteed to be true here - return moduleProxies.newModuleInstance(requirement.typeElement(), className); + return moduleProxies.newModuleInstance( + requirement.typeElement(), componentImplementation.getCreatorName()); } } @@ -370,10 +362,7 @@ final class ComponentCreatorImplementationFactory { private final class BuilderForCreatorDescriptor extends Builder { final ComponentCreatorDescriptor creatorDescriptor; - BuilderForCreatorDescriptor( - ComponentImplementation componentImplementation, - ComponentCreatorDescriptor creatorDescriptor) { - super(componentImplementation); + BuilderForCreatorDescriptor(ComponentCreatorDescriptor creatorDescriptor) { this.creatorDescriptor = creatorDescriptor; } @@ -389,12 +378,14 @@ final class ComponentCreatorImplementationFactory { @Override protected void setSupertype() { - addSupertype(classBuilder, creatorDescriptor.typeElement()); + addSupertype(super.classBuilder, creatorDescriptor.typeElement()); } @Override protected void addConstructor() { - // Just use the implicit no-arg public constructor. + if (!componentImplementation.creatorComponentFields().isEmpty()) { + super.addConstructor(); + } } @Override @@ -405,18 +396,16 @@ final class ComponentCreatorImplementationFactory { @Override protected ImmutableMap<ComponentRequirement, String> factoryMethodParameters() { return ImmutableMap.copyOf( - Maps.transformValues( - creatorDescriptor.factoryParameters(), - element -> element.getSimpleName().toString())); + Maps.transformValues(creatorDescriptor.factoryParameters(), XElements::getSimpleName)); } - private DeclaredType creatorType() { - return asDeclared(creatorDescriptor.typeElement().asType()); + private XType creatorType() { + return creatorDescriptor.typeElement().getType(); } @Override protected MethodSpec.Builder factoryMethodBuilder() { - return MethodSpec.overriding(creatorDescriptor.factoryMethod(), creatorType(), types); + return MethodSpecs.overriding(creatorDescriptor.factoryMethod(), creatorType()); } private RequirementStatus requirementStatus(ComponentRequirement requirement) { @@ -447,11 +436,11 @@ final class ComponentCreatorImplementationFactory { @Override protected MethodSpec.Builder setterMethodBuilder(ComponentRequirement requirement) { - ExecutableElement supertypeMethod = creatorDescriptor.setterMethods().get(requirement); - MethodSpec.Builder method = MethodSpec.overriding(supertypeMethod, creatorType(), types); - if (!supertypeMethod.getReturnType().getKind().equals(TypeKind.VOID)) { + XMethodElement supertypeMethod = creatorDescriptor.setterMethods().get(requirement); + MethodSpec.Builder method = MethodSpecs.overriding(supertypeMethod, creatorType()); + if (!isVoid(supertypeMethod.getReturnType())) { // Take advantage of covariant returns so that we don't have to worry about type variables - method.returns(className); + method.returns(componentImplementation.getCreatorName()); } return method; } @@ -462,9 +451,6 @@ final class ComponentCreatorImplementationFactory { * does not have its own user-defined creator type (i.e. a {@code ComponentCreatorDescriptor}). */ private final class BuilderForGeneratedRootComponentBuilder extends Builder { - BuilderForGeneratedRootComponentBuilder(ComponentImplementation componentImplementation) { - super(componentImplementation); - } @Override protected ImmutableMap<ComponentRequirement, RequirementStatus> userSettableRequirements() { @@ -478,11 +464,9 @@ final class ComponentCreatorImplementationFactory { @Override protected Optional<Modifier> visibility() { - return componentImplementation - .componentDescriptor() - .typeElement() - .getModifiers() - .contains(PUBLIC) ? Optional.of(PUBLIC) : Optional.empty(); + return componentImplementation.componentDescriptor().typeElement().isPublic() + ? Optional.of(PUBLIC) + : Optional.empty(); } @Override @@ -491,11 +475,6 @@ final class ComponentCreatorImplementationFactory { } @Override - protected void addConstructor() { - classBuilder.addMethod(constructorBuilder().addModifiers(PRIVATE).build()); - } - - @Override protected ImmutableSet<ComponentRequirement> setterMethods() { return componentDescriptor().dependenciesAndConcreteModules(); } @@ -512,11 +491,11 @@ final class ComponentCreatorImplementationFactory { @Override protected MethodSpec.Builder setterMethodBuilder(ComponentRequirement requirement) { - String name = simpleVariableName(requirement.typeElement()); + String name = simpleVariableName(requirement.typeElement().getClassName()); return methodBuilder(name) .addModifiers(PUBLIC) - .addParameter(TypeName.get(requirement.type()), name) - .returns(className); + .addParameter(requirement.type().getTypeName(), name) + .returns(componentImplementation.getCreatorName()); } } diff --git a/java/dagger/internal/codegen/writing/ComponentImplementation.java b/java/dagger/internal/codegen/writing/ComponentImplementation.java index a09620e87..f458a9a94 100644 --- a/java/dagger/internal/codegen/writing/ComponentImplementation.java +++ b/java/dagger/internal/codegen/writing/ComponentImplementation.java @@ -16,68 +16,129 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static com.google.auto.common.MoreTypes.asDeclared; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +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.binding.ComponentCreatorKind.BUILDER; +import static dagger.internal.codegen.base.ComponentCreatorKind.BUILDER; +import static dagger.internal.codegen.binding.SourceFiles.simpleVariableName; +import static dagger.internal.codegen.extension.DaggerStreams.instancesOf; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; +import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; +import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings; +import static dagger.internal.codegen.javapoet.CodeBlocks.parameterNames; +import static dagger.internal.codegen.langmodel.Accessibility.isProtectedMemberOf; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; +import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.COMPONENT_METHOD; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.producers.CancellationPolicy.Propagation.PROPAGATE; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; +import static javax.lang.model.element.Modifier.STATIC; +import static javax.tools.Diagnostic.Kind.ERROR; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.compat.XConverters; +import com.google.auto.common.MoreElements; +import com.google.auto.common.MoreTypes; +import com.google.common.base.Function; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.collect.MultimapBuilder; -import com.squareup.javapoet.AnnotationSpec; +import com.google.common.collect.Sets; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; +import dagger.internal.Preconditions; +import dagger.internal.codegen.base.ComponentCreatorKind; import dagger.internal.codegen.base.UniqueNameSet; +import dagger.internal.codegen.binding.Binding; import dagger.internal.codegen.binding.BindingGraph; +import dagger.internal.codegen.binding.BindingNode; import dagger.internal.codegen.binding.BindingRequest; import dagger.internal.codegen.binding.ComponentCreatorDescriptor; -import dagger.internal.codegen.binding.ComponentCreatorKind; import dagger.internal.codegen.binding.ComponentDescriptor; +import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; import dagger.internal.codegen.binding.ComponentRequirement; import dagger.internal.codegen.binding.KeyVariableNamer; +import dagger.internal.codegen.binding.MethodSignature; import dagger.internal.codegen.compileroption.CompilerOptions; +import dagger.internal.codegen.javapoet.CodeBlocks; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.javapoet.TypeSpecs; -import dagger.model.Key; -import dagger.model.RequestKind; +import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.spi.model.BindingGraph.Node; +import dagger.spi.model.Key; +import dagger.spi.model.RequestKind; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; -import java.util.LinkedHashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; /** The implementation of a component type. */ +@PerComponentImplementation public final class ComponentImplementation { + /** A factory for creating a {@link ComponentImplementation}. */ + public interface ChildComponentImplementationFactory { + /** Creates a {@link ComponentImplementation} for the given {@code childGraph}. */ + ComponentImplementation create(BindingGraph childGraph); + } + + /** Compiler Modes. */ + public enum CompilerMode { + DEFAULT, + FAST_INIT, + EXPERIMENTAL_MERGED_MODE; + + public boolean isFastInit() { + return this == CompilerMode.FAST_INIT; + } + + public boolean isExperimentalMergedMode() { + return this == CompilerMode.EXPERIMENTAL_MERGED_MODE; + } + } + /** A type of field that this component can contain. */ public enum FieldSpecKind { /** A field for a component shard. */ - COMPONENT_SHARD, + COMPONENT_SHARD_FIELD, /** A field required by the component, e.g. module instances. */ COMPONENT_REQUIREMENT_FIELD, - /** - * A field for the lock and cached value for {@linkplain PrivateMethodBindingExpression - * private-method scoped bindings}. - */ - PRIVATE_METHOD_SCOPED_FIELD, - /** A framework field for type T, e.g. {@code Provider<T>}. */ FRAMEWORK_FIELD, @@ -114,8 +175,7 @@ public final class ComponentImplementation { * The {@link dagger.producers.internal.CancellationListener#onProducerFutureCancelled(boolean)} * method for a production component. */ - CANCELLATION_LISTENER_METHOD, - ; + CANCELLATION_LISTENER_METHOD } /** A type of nested class that this component can contain. */ @@ -129,134 +189,237 @@ public final class ComponentImplementation { /** A provider class for a component provision. */ COMPONENT_PROVISION_FACTORY, + /** A class for a component shard. */ + COMPONENT_SHARD_TYPE, + /** A class for the subcomponent or subcomponent builder. */ SUBCOMPONENT } - private ComponentImplementation currentShard = this; - private final Map<Key, ComponentImplementation> shardsByKey = new HashMap<>(); - private final Optional<ComponentImplementation> shardOwner; + /** + * Returns the {@link ShardImplementation} for each binding in this graph. + * + * <p>Each shard contains approximately {@link CompilerOptions#keysPerComponentShard()} bindings. + * + * <p>If more than 1 shard is needed, we iterate the strongly connected nodes to make sure of two + * things: 1) bindings are put in shards in reverse topological order (i.e., bindings in Shard{i} + * do not depend on bindings in Shard{i+j}) and 2) bindings belonging to the same cycle are put in + * the same shard. These two guarantees allow us to initialize each shard in a well defined order. + */ + private static ImmutableMap<Binding, ShardImplementation> createShardsByBinding( + ShardImplementation componentShard, BindingGraph graph, CompilerOptions compilerOptions) { + ImmutableList<ImmutableList<Binding>> partitions = bindingPartitions(graph, compilerOptions); + ImmutableMap.Builder<Binding, ShardImplementation> builder = ImmutableMap.builder(); + for (int i = 0; i < partitions.size(); i++) { + ShardImplementation shard = i == 0 ? componentShard : componentShard.createShard("Shard" + i); + partitions.get(i).forEach(binding -> builder.put(binding, shard)); + } + return builder.build(); + } + + private static ImmutableList<ImmutableList<Binding>> bindingPartitions( + BindingGraph graph, CompilerOptions compilerOptions) { + int bindingsPerShard = compilerOptions.keysPerComponentShard(graph.componentTypeElement()); + int maxPartitions = (graph.localBindingNodes().size() / bindingsPerShard) + 1; + if (maxPartitions <= 1) { + return ImmutableList.of( + graph.localBindingNodes().stream().map(BindingNode::delegate).collect(toImmutableList())); + } + + // Iterate through all SCCs in order until all bindings local to this component are partitioned. + List<Binding> currPartition = new ArrayList<>(bindingsPerShard); + ImmutableList.Builder<ImmutableList<Binding>> partitions = + ImmutableList.builderWithExpectedSize(maxPartitions); + for (ImmutableSet<Node> nodes : graph.topLevelBindingGraph().stronglyConnectedNodes()) { + nodes.stream() + .flatMap(instancesOf(BindingNode.class)) + .filter(bindingNode -> bindingNode.componentPath().equals(graph.componentPath())) + .map(BindingNode::delegate) + .forEach(currPartition::add); + if (currPartition.size() >= bindingsPerShard) { + partitions.add(ImmutableList.copyOf(currPartition)); + currPartition = new ArrayList<>(bindingsPerShard); + } + } + if (!currPartition.isEmpty()) { + partitions.add(ImmutableList.copyOf(currPartition)); + } + return partitions.build(); + } + + /** The boolean parameter of the onProducerFutureCancelled method. */ + public static final ParameterSpec MAY_INTERRUPT_IF_RUNNING_PARAM = + ParameterSpec.builder(boolean.class, "mayInterruptIfRunning").build(); + + private static final String CANCELLATION_LISTENER_METHOD_NAME = "onProducerFutureCancelled"; + + /** + * How many statements per {@code initialize()} or {@code onProducerFutureCancelled()} method + * before they get partitioned. + */ + private static final int STATEMENTS_PER_METHOD = 100; + + private final ShardImplementation componentShard; + private final ImmutableMap<Binding, ShardImplementation> shardsByBinding; + private final Map<ShardImplementation, FieldSpec> shardFieldsByImplementation = new HashMap<>(); + private final List<CodeBlock> shardInitializations = new ArrayList<>(); + private final List<CodeBlock> shardCancellations = new ArrayList<>(); + private final Optional<ComponentImplementation> parent; + private final ChildComponentImplementationFactory childComponentImplementationFactory; + private final Provider<ComponentRequestRepresentations> bindingExpressionsProvider; + private final Provider<ComponentCreatorImplementationFactory> + componentCreatorImplementationFactoryProvider; private final BindingGraph graph; - private final ClassName name; - private final TypeSpec.Builder component; - private final SubcomponentNames subcomponentNames; - private final CompilerOptions compilerOptions; - private final CodeBlock externalReferenceBlock; - private final UniqueNameSet componentFieldNames = new UniqueNameSet(); - private final UniqueNameSet componentMethodNames = new UniqueNameSet(); - private final List<CodeBlock> initializations = new ArrayList<>(); - private final List<CodeBlock> componentRequirementInitializations = new ArrayList<>(); - private final Map<ComponentRequirement, String> componentRequirementParameterNames = - new HashMap<>(); - private final Set<Key> cancellableProducerKeys = new LinkedHashSet<>(); - private final ListMultimap<FieldSpecKind, FieldSpec> fieldSpecsMap = - MultimapBuilder.enumKeys(FieldSpecKind.class).arrayListValues().build(); - private final ListMultimap<MethodSpecKind, MethodSpec> methodSpecsMap = - MultimapBuilder.enumKeys(MethodSpecKind.class).arrayListValues().build(); - private final ListMultimap<TypeSpecKind, TypeSpec> typeSpecsMap = - MultimapBuilder.enumKeys(TypeSpecKind.class).arrayListValues().build(); - private final List<Supplier<TypeSpec>> typeSuppliers = new ArrayList<>(); - - private ComponentImplementation( + private final ComponentNames componentNames; + private final DaggerElements elements; + private final DaggerTypes types; + private final ImmutableMap<ComponentImplementation, FieldSpec> componentFieldsByImplementation; + private final XMessager messager; + private final CompilerMode compilerMode; + + @Inject + ComponentImplementation( + @ParentComponent Optional<ComponentImplementation> parent, + ChildComponentImplementationFactory childComponentImplementationFactory, + // Inject as Provider<> to prevent a cycle. + Provider<ComponentRequestRepresentations> bindingExpressionsProvider, + Provider<ComponentCreatorImplementationFactory> componentCreatorImplementationFactoryProvider, BindingGraph graph, - ClassName name, - SubcomponentNames subcomponentNames, - CompilerOptions compilerOptions) { + ComponentNames componentNames, + CompilerOptions compilerOptions, + DaggerElements elements, + DaggerTypes types, + XMessager messager) { + this.parent = parent; + this.childComponentImplementationFactory = childComponentImplementationFactory; + this.bindingExpressionsProvider = bindingExpressionsProvider; + this.componentCreatorImplementationFactoryProvider = + componentCreatorImplementationFactoryProvider; this.graph = graph; - this.name = name; - this.component = classBuilder(name); - this.subcomponentNames = subcomponentNames; - this.shardOwner = Optional.empty(); - this.externalReferenceBlock = CodeBlock.of("$T.this", name); - this.compilerOptions = compilerOptions; + this.componentNames = componentNames; + this.elements = elements; + this.types = types; + + // The first group of keys belong to the component itself. We call this the componentShard. + this.componentShard = new ShardImplementation(componentNames.get(graph.componentPath())); + + // Claim the method names for all local and inherited methods on the component type. + elements + .getLocalAndInheritedMethods(toJavac(graph.componentTypeElement())) + .forEach(method -> componentShard.componentMethodNames.claim(method.getSimpleName())); + + // Create the shards for this component, indexed by binding. + this.shardsByBinding = createShardsByBinding(componentShard, graph, compilerOptions); + + // Create and claim the fields for this and all ancestor components stored as fields. + this.componentFieldsByImplementation = + createComponentFieldsByImplementation(this, compilerOptions); + this.messager = messager; + XTypeElement typeElement = rootComponentImplementation().componentDescriptor().typeElement(); + this.compilerMode = + compilerOptions.fastInit(typeElement) + ? CompilerMode.FAST_INIT + : (compilerOptions.experimentalMergedMode(typeElement) + ? CompilerMode.EXPERIMENTAL_MERGED_MODE + : CompilerMode.DEFAULT); + } + + /** + * Returns the shard for a given {@link Binding}. + * + * <p>Each set of {@link CompilerOptions#keysPerShard()} will get its own shard instance. + */ + public ShardImplementation shardImplementation(Binding binding) { + checkState(shardsByBinding.containsKey(binding), "No shard in %s for: %s", name(), binding); + return shardsByBinding.get(binding); } - private ComponentImplementation(ComponentImplementation shardOwner, ClassName shardName) { - this.graph = shardOwner.graph; - this.name = shardName; - this.component = classBuilder(shardName); - this.subcomponentNames = shardOwner.subcomponentNames; - this.compilerOptions = shardOwner.compilerOptions; - this.shardOwner = Optional.of(shardOwner); - String fieldName = UPPER_CAMEL.to(LOWER_CAMEL, name.simpleName()); - String uniqueFieldName = shardOwner.getUniqueFieldName(fieldName); - this.externalReferenceBlock = CodeBlock.of("$T.this.$N", shardOwner.name, uniqueFieldName); - shardOwner.addTypeSupplier(() -> generate().build()); - shardOwner.addField( - FieldSpecKind.COMPONENT_SHARD, - FieldSpec.builder(name, uniqueFieldName, PRIVATE, FINAL) - .initializer("new $T()", name) - .build()); + /** Returns the root {@link ComponentImplementation}. */ + ComponentImplementation rootComponentImplementation() { + return parent.map(ComponentImplementation::rootComponentImplementation).orElse(this); } - /** Returns a component implementation for a top-level component. */ - public static ComponentImplementation topLevelComponentImplementation( - BindingGraph graph, - ClassName name, - SubcomponentNames subcomponentNames, - CompilerOptions compilerOptions) { - return new ComponentImplementation(graph, name, subcomponentNames, compilerOptions); + /** Returns a reference to this implementation when called from a different class. */ + public CodeBlock componentFieldReference() { + // TODO(bcorso): This currently relies on all requesting classes having a reference to the + // component with the same name, which is kind of sketchy. Try to think of a better way that + // can accomodate the component missing in some classes if it's not used. + return CodeBlock.of("$N", componentFieldsByImplementation.get(this)); } - /** Returns a component implementation that is a child of the current implementation. */ - public ComponentImplementation childComponentImplementation(BindingGraph graph) { - checkState(!shardOwner.isPresent(), "Shards cannot create child components."); - ClassName childName = getSubcomponentName(graph.componentDescriptor()); - return new ComponentImplementation(graph, childName, subcomponentNames, compilerOptions); + /** Returns the fields for all components in the component path. */ + public ImmutableList<FieldSpec> componentFields() { + return ImmutableList.copyOf(componentFieldsByImplementation.values()); } - /** Returns a component implementation that is a shard of the current implementation. */ - public ComponentImplementation shardImplementation(Key key) { - checkState(!shardOwner.isPresent(), "Shards cannot create other shards."); - if (!shardsByKey.containsKey(key)) { - int keysPerShard = compilerOptions.keysPerComponentShard(graph.componentTypeElement()); - if (!shardsByKey.isEmpty() && shardsByKey.size() % keysPerShard == 0) { - ClassName shardName = name.nestedClass("Shard" + shardsByKey.size() / keysPerShard); - currentShard = new ComponentImplementation(this, shardName); - } - shardsByKey.put(key, currentShard); - } - return shardsByKey.get(key); + /** Returns the fields for all components in the component path except the current component. */ + public ImmutableList<FieldSpec> creatorComponentFields() { + return componentFieldsByImplementation.entrySet().stream() + .filter(entry -> !this.equals(entry.getKey())) + .map(Map.Entry::getValue) + .collect(toImmutableList()); } - /** Returns a reference to this compenent when called from a class nested in this component. */ - public CodeBlock externalReferenceBlock() { - return externalReferenceBlock; + private static ImmutableMap<ComponentImplementation, FieldSpec> + createComponentFieldsByImplementation( + ComponentImplementation componentImplementation, CompilerOptions compilerOptions) { + checkArgument( + componentImplementation.componentShard != null, + "The component shard must be set before computing the component fields."); + ImmutableList.Builder<ComponentImplementation> builder = ImmutableList.builder(); + for (ComponentImplementation curr = componentImplementation; + curr != null; + curr = curr.parent.orElse(null)) { + builder.add(curr); + } + // For better readability when adding these fields/parameters to generated code, we collect the + // component implementations in reverse order so that parents appear before children. + return builder.build().reverse().stream() + .collect( + toImmutableMap( + componentImpl -> componentImpl, + componentImpl -> { + ClassName component = + componentImpl.graph.componentPath().currentComponent().className(); + ClassName fieldType = componentImpl.name(); + String fieldName = + componentImpl.isNested() + ? simpleVariableName(componentImpl.name()) + : simpleVariableName(component); + FieldSpec.Builder field = FieldSpec.builder(fieldType, fieldName, PRIVATE, FINAL); + componentImplementation.componentShard.componentFieldNames.claim(fieldName); + + return field.build(); + })); + } + /** Returns the shard representing the {@link ComponentImplementation} itself. */ + public ShardImplementation getComponentShard() { + return componentShard; } - // TODO(ronshapiro): see if we can remove this method and instead inject it in the objects that - // need it. /** Returns the binding graph for the component being generated. */ public BindingGraph graph() { - return graph; + return componentShard.graph(); } /** Returns the descriptor for the component being generated. */ public ComponentDescriptor componentDescriptor() { - return graph.componentDescriptor(); + return componentShard.componentDescriptor(); } /** Returns the name of the component. */ public ClassName name() { - return name; + return componentShard.name; } - /** Returns whether or not the implementation is nested within another class. */ - public boolean isNested() { - return name.enclosingClassName() != null; + /** Returns if the current compile mode is fast init. */ + public CompilerMode compilerMode() { + return compilerMode; } - /** - * Returns the kind of this component's creator. - * - * @throws IllegalStateException if the component has no creator - */ - private ComponentCreatorKind creatorKind() { - checkState(componentDescriptor().hasCreator()); - return componentDescriptor() - .creatorDescriptor() - .map(ComponentCreatorDescriptor::kind) - .orElse(BUILDER); + /** Returns whether or not the implementation is nested within another class. */ + private boolean isNested() { + return name().enclosingClassName() != null; } /** @@ -264,172 +427,666 @@ public final class ComponentImplementation { * generated class unless this is a top-level component, in which case it will be nested. */ public ClassName getCreatorName() { - return isNested() - ? name.peerClass(subcomponentNames.getCreatorName(componentDescriptor())) - : name.nestedClass(creatorKind().typeName()); + return componentNames.getCreatorName(graph.componentPath()); } - /** Returns the name of the nested implementation class for a child component. */ - private ClassName getSubcomponentName(ComponentDescriptor childDescriptor) { - checkArgument( - componentDescriptor().childComponents().contains(childDescriptor), - "%s is not a child component of %s", - childDescriptor.typeElement(), - componentDescriptor().typeElement()); - // TODO(erichang): Hacky fix to shorten the suffix if we're too deeply - // nested to save on file name length. 2 chosen arbitrarily. - String suffix = name.simpleNames().size() > 2 ? "I" : "Impl"; - return name.nestedClass(subcomponentNames.get(childDescriptor) + suffix); + /** Generates the component and returns the resulting {@link TypeSpec}. */ + public TypeSpec generate() { + return componentShard.generate(); } /** - * Returns the simple name of the creator implementation class for the given subcomponent creator - * {@link Key}. + * The implementation of a shard. + * + * <p>The purpose of a shard is to allow a component implemenation to be split into multiple + * classes, where each class owns the creation logic for a set of keys. Sharding is useful for + * large component implementations, where a single component implementation class can reach size + * limitations, such as the constant pool size. + * + * <p>When generating the actual sources, the creation logic within the first instance of {@link + * ShardImplementation} will go into the component implementation class itself (e.g. {@code + * MySubcomponentImpl}). Each subsequent instance of {@link ShardImplementation} will generate a + * nested "shard" class within the component implementation (e.g. {@code + * MySubcomponentImpl.Shard1}, {@code MySubcomponentImpl.Shard2}, etc). */ - String getSubcomponentCreatorSimpleName(Key key) { - return subcomponentNames.getCreatorName(key); - } + public final class ShardImplementation { + private final ClassName name; + private final UniqueNameSet componentFieldNames = new UniqueNameSet(); + private final UniqueNameSet componentMethodNames = new UniqueNameSet(); + private final UniqueNameSet componentClassNames = new UniqueNameSet(); + private final UniqueNameSet assistedParamNames = new UniqueNameSet(); + private final List<CodeBlock> initializations = new ArrayList<>(); + private final Map<Key, CodeBlock> cancellations = new LinkedHashMap<>(); + private final Map<VariableElement, String> uniqueAssistedName = new LinkedHashMap<>(); + private final List<CodeBlock> componentRequirementInitializations = new ArrayList<>(); + private final ImmutableMap<ComponentRequirement, ParameterSpec> constructorParameters; + private final ListMultimap<FieldSpecKind, FieldSpec> fieldSpecsMap = + MultimapBuilder.enumKeys(FieldSpecKind.class).arrayListValues().build(); + private final ListMultimap<MethodSpecKind, MethodSpec> methodSpecsMap = + MultimapBuilder.enumKeys(MethodSpecKind.class).arrayListValues().build(); + private final ListMultimap<TypeSpecKind, TypeSpec> typeSpecsMap = + MultimapBuilder.enumKeys(TypeSpecKind.class).arrayListValues().build(); + private final List<Supplier<TypeSpec>> typeSuppliers = new ArrayList<>(); + private boolean initialized = false; // This is used for initializing assistedParamNames. - /** Returns {@code true} if {@code type} is accessible from the generated component. */ - boolean isTypeAccessible(TypeMirror type) { - return isTypeAccessibleFrom(type, name.packageName()); - } + private ShardImplementation(ClassName name) { + this.name = name; + if (graph.componentDescriptor().isProduction()) { + claimMethodName(CANCELLATION_LISTENER_METHOD_NAME); + } - /** Adds the given super type to the component. */ - public void addSupertype(TypeElement supertype) { - TypeSpecs.addSupertype(component, supertype); - } + // Build the map of constructor parameters for this shard and claim the field names to prevent + // collisions between the constructor parameters and fields. + constructorParameters = + constructorRequirements(graph).stream() + .collect( + toImmutableMap( + requirement -> requirement, + requirement -> + ParameterSpec.builder( + requirement.type().getTypeName(), + getUniqueFieldName(requirement.variableName() + "Param")) + .build())); + } - // TODO(dpb): Consider taking FieldSpec, and returning identical FieldSpec with unique name? - /** Adds the given field to the component. */ - public void addField(FieldSpecKind fieldKind, FieldSpec fieldSpec) { - fieldSpecsMap.put(fieldKind, fieldSpec); - } + private ShardImplementation createShard(String shardName) { + checkState(isComponentShard(), "Only the componentShard can create other shards."); + return new ShardImplementation(name.nestedClass(shardName)); + } - // TODO(dpb): Consider taking MethodSpec, and returning identical MethodSpec with unique name? - /** Adds the given method to the component. */ - public void addMethod(MethodSpecKind methodKind, MethodSpec methodSpec) { - methodSpecsMap.put(methodKind, methodSpec); - } + /** Returns the {@link ComponentImplementation} that owns this shard. */ + public ComponentImplementation getComponentImplementation() { + return ComponentImplementation.this; + } - /** Adds the given annotation to the component. */ - public void addAnnotation(AnnotationSpec annotation) { - component.addAnnotation(annotation); - } + /** + * Returns {@code true} if this shard represents the component implementation rather than a + * separate {@code Shard} class. + */ + public boolean isComponentShard() { + return this == componentShard; + } - /** Adds the given type to the component. */ - public void addType(TypeSpecKind typeKind, TypeSpec typeSpec) { - typeSpecsMap.put(typeKind, typeSpec); - } + /** Returns the fields for all components in the component path by component implementation. */ + public ImmutableMap<ComponentImplementation, FieldSpec> componentFieldsByImplementation() { + return componentFieldsByImplementation; + } - /** Adds a {@link Supplier} for the SwitchingProvider for the component. */ - void addTypeSupplier(Supplier<TypeSpec> typeSpecSupplier) { - typeSuppliers.add(typeSpecSupplier); - } + /** Returns a reference to this implementation when called from a different class. */ + public CodeBlock shardFieldReference() { + if (!isComponentShard() && !shardFieldsByImplementation.containsKey(this)) { + // Add the shard if this is the first time it's requested by something. + String shardFieldName = + componentShard.getUniqueFieldName(UPPER_CAMEL.to(LOWER_CAMEL, name.simpleName())); + FieldSpec shardField = FieldSpec.builder(name, shardFieldName, PRIVATE).build(); - /** Adds the given code block to the initialize methods of the component. */ - void addInitialization(CodeBlock codeBlock) { - initializations.add(codeBlock); - } + shardFieldsByImplementation.put(this, shardField); + } + // TODO(bcorso): This currently relies on all requesting classes having a reference to the + // component with the same name, which is kind of sketchy. Try to think of a better way that + // can accomodate the component missing in some classes if it's not used. + return isComponentShard() + ? componentFieldReference() + : CodeBlock.of("$L.$N", componentFieldReference(), shardFieldsByImplementation.get(this)); + } - /** Adds the given code block that initializes a {@link ComponentRequirement}. */ - void addComponentRequirementInitialization(CodeBlock codeBlock) { - componentRequirementInitializations.add(codeBlock); - } + // TODO(ronshapiro): see if we can remove this method and instead inject it in the objects that + // need it. + /** Returns the binding graph for the component being generated. */ + public BindingGraph graph() { + return graph; + } - /** - * Marks the given key of a producer as one that should have a cancellation statement in the - * cancellation listener method of the component. - */ - void addCancellableProducerKey(Key key) { - cancellableProducerKeys.add(key); - } + /** Returns the descriptor for the component being generated. */ + public ComponentDescriptor componentDescriptor() { + return graph.componentDescriptor(); + } - /** Returns a new, unique field name for the component based on the given name. */ - String getUniqueFieldName(String name) { - return componentFieldNames.getUniqueName(name); - } + /** Returns the name of the component. */ + public ClassName name() { + return name; + } - /** Returns a new, unique method name for the component based on the given name. */ - public String getUniqueMethodName(String name) { - return componentMethodNames.getUniqueName(name); - } + /** + * Returns the name of the creator implementation class for the given subcomponent creator + * {@link Key}. + */ + ClassName getSubcomponentCreatorSimpleName(Key creatorKey) { + return componentNames.getSubcomponentCreatorName(graph.componentPath(), creatorKey); + } - /** Returns a new, unique method name for a getter method for the given request. */ - String getUniqueMethodName(BindingRequest request) { - return uniqueMethodName(request, KeyVariableNamer.name(request.key())); - } + /** + * Returns an accessible type for this shard implementation, returns Object if the type is not + * accessible. + * + * <p>This method checks accessibility for public types and package private types, and it also + * checks protected types' accessibility. + */ + TypeMirror accessibleType(TypeMirror type) { + // Returns the original type if the type is accessible from this shard, or returns original + // type's raw type if only its raw type is accessible. Otherwise, returns Object. + TypeMirror castedType = types.accessibleType(type, name()); + // Previous check marks protected type as inaccessible, so a second check is needed to check + // if the type is protected type and accessible. + if (TypeName.get(castedType).equals(TypeName.OBJECT) && isTypeAccessible(type)) { + castedType = type; + } + return castedType; + } - private String uniqueMethodName(BindingRequest request, String bindingName) { - // This name is intentionally made to match the name for fields in fastInit - // in order to reduce the constant pool size. b/162004246 - String baseMethodName = bindingName - + (request.isRequestKind(RequestKind.INSTANCE) - ? "" - : UPPER_UNDERSCORE.to(UPPER_CAMEL, request.kindName())); - return getUniqueMethodName(baseMethodName); - } + /** + * Returns {@code true} if {@code type} is accessible from the generated component. + * + * <p>This method checks accessibility for public types and package private types, and it also + * checks protected types' accessibility. + */ + boolean isTypeAccessible(TypeMirror type) { + if (isTypeAccessibleFrom(type, name.packageName())) { + return true; + } + // Check if the type is protected and accessible from current component. + if (type instanceof DeclaredType + && isProtectedMemberOf( + MoreTypes.asDeclared(type), + getComponentImplementation().componentDescriptor().typeElement())) { + return true; + } + return false; + } - /** - * Gets the parameter name to use for the given requirement for this component, starting with the - * given base name if no parameter name has already been selected for the requirement. - */ - public String getParameterName(ComponentRequirement requirement, String baseName) { - return componentRequirementParameterNames.computeIfAbsent( - requirement, r -> getUniqueFieldName(baseName)); - } + // TODO(dpb): Consider taking FieldSpec, and returning identical FieldSpec with unique name? + /** Adds the given field to the component. */ + public void addField(FieldSpecKind fieldKind, FieldSpec fieldSpec) { + fieldSpecsMap.put(fieldKind, fieldSpec); + } - /** Claims a new method name for the component. Does nothing if method name already exists. */ - public void claimMethodName(CharSequence name) { - componentMethodNames.claim(name); - } + // TODO(dpb): Consider taking MethodSpec, and returning identical MethodSpec with unique name? + /** Adds the given method to the component. */ + public void addMethod(MethodSpecKind methodKind, MethodSpec methodSpec) { + methodSpecsMap.put(methodKind, methodSpec); + } - /** Returns the list of {@link CodeBlock}s that need to go in the initialize method. */ - public ImmutableList<CodeBlock> getInitializations() { - return ImmutableList.copyOf(initializations); - } + /** Adds the given type to the component. */ + public void addType(TypeSpecKind typeKind, TypeSpec typeSpec) { + typeSpecsMap.put(typeKind, typeSpec); + } - /** - * Returns a list of {@link CodeBlock}s for initializing {@link ComponentRequirement}s. - * - * <p>These initializations are kept separate from {@link #getInitializations()} because they must - * be executed before the initializations of any framework instance initializations in a - * superclass implementation that may depend on the instances. We cannot use the same strategy - * that we use for framework instances (i.e. wrap in a {@link dagger.internal.DelegateFactory} or - * {@link dagger.producers.internal.DelegateProducer} since the types of these initialized fields - * have no interface type that we can write a proxy for. - */ - // TODO(cgdecker): can these be inlined with getInitializations() now that we've turned down - // ahead-of-time subcomponents? - public ImmutableList<CodeBlock> getComponentRequirementInitializations() { - return ImmutableList.copyOf(componentRequirementInitializations); - } + /** Adds a {@link Supplier} for the SwitchingProvider for the component. */ + void addTypeSupplier(Supplier<TypeSpec> typeSpecSupplier) { + typeSuppliers.add(typeSpecSupplier); + } - /** - * Returns the list of producer {@link Key}s that need cancellation statements in the cancellation - * listener method. - */ - public ImmutableList<Key> getCancellableProducerKeys() { - return ImmutableList.copyOf(cancellableProducerKeys); - } + /** Adds the given code block to the initialize methods of the component. */ + void addInitialization(CodeBlock codeBlock) { + initializations.add(codeBlock); + } + + /** Adds the given code block that initializes a {@link ComponentRequirement}. */ + void addComponentRequirementInitialization(CodeBlock codeBlock) { + componentRequirementInitializations.add(codeBlock); + } + + /** + * Adds the given cancellation statement to the cancellation listener method of the component. + */ + void addCancellation(Key key, CodeBlock codeBlock) { + // Store cancellations by key to avoid adding the same cancellation twice. + cancellations.putIfAbsent(key, codeBlock); + } + + /** Returns a new, unique field name for the component based on the given name. */ + String getUniqueFieldName(String name) { + return componentFieldNames.getUniqueName(name); + } + + String getUniqueAssistedParamName(String name) { + if (!initialized) { + // Assisted params will be used in switching provider, so they can't conflict with component + // field names in switching provider. {@link UniqueNameSet#getUniqueName} will add the + // component field names to the unique set if it does not exists. If the name already exists + // in the set, then a dedupe will be performed automatically on the passed in name, and the + // newly created unique name will then be added to the set. + componentFieldsByImplementation() + .values() + .forEach(fieldSpec -> assistedParamNames.getUniqueName(fieldSpec.name)); + initialized = true; + } + return assistedParamNames.getUniqueName(name); + } + + public String getUniqueFieldNameForAssistedParam(VariableElement element) { + if (uniqueAssistedName.containsKey(element)) { + return uniqueAssistedName.get(element); + } + String name = getUniqueAssistedParamName(element.getSimpleName().toString()); + uniqueAssistedName.put(element, name); + return name; + } + + /** Returns a new, unique nested class name for the component based on the given name. */ + public String getUniqueMethodName(String name) { + return componentMethodNames.getUniqueName(name); + } + + /** Returns a new, unique method name for a getter method for the given request. */ + String getUniqueMethodName(BindingRequest request) { + return uniqueMethodName(request, KeyVariableNamer.name(request.key())); + } + + /** Returns a new, unique method name for the component based on the given name. */ + public String getUniqueClassName(String name) { + return componentClassNames.getUniqueName(name); + } + + private String uniqueMethodName(BindingRequest request, String bindingName) { + // This name is intentionally made to match the name for fields in fastInit + // in order to reduce the constant pool size. b/162004246 + String baseMethodName = + bindingName + + (request.isRequestKind(RequestKind.INSTANCE) + ? "" + : UPPER_UNDERSCORE.to(UPPER_CAMEL, request.kindName())); + return getUniqueMethodName(baseMethodName); + } + + /** + * Gets the parameter name to use for the given requirement for this component, starting with + * the given base name if no parameter name has already been selected for the requirement. + */ + public String getParameterName(ComponentRequirement requirement) { + return constructorParameters.get(requirement).name; + } + + /** Claims a new method name for the component. Does nothing if method name already exists. */ + public void claimMethodName(CharSequence name) { + componentMethodNames.claim(name); + } + + /** Generates the component and returns the resulting {@link TypeSpec.Builder}. */ + private TypeSpec generate() { + TypeSpec.Builder builder = classBuilder(name); + + if (isComponentShard()) { + TypeSpecs.addSupertype(builder, graph.componentTypeElement()); + addCreator(); + addFactoryMethods(); + addInterfaceMethods(); + addChildComponents(); + addShards(); + } + + addConstructorAndInitializationMethods(); + + if (graph.componentDescriptor().isProduction()) { + if (isComponentShard() || !cancellations.isEmpty()) { + TypeSpecs.addSupertype( + builder, elements.getTypeElement(TypeNames.CANCELLATION_LISTENER.canonicalName())); + addCancellationListenerImplementation(); + } + } + + modifiers().forEach(builder::addModifiers); + fieldSpecsMap.asMap().values().forEach(builder::addFields); + methodSpecsMap.asMap().values().forEach(builder::addMethods); + typeSpecsMap.asMap().values().forEach(builder::addTypes); + typeSuppliers.stream().map(Supplier::get).forEach(builder::addType); + return builder.build(); + } + + private ImmutableSet<Modifier> modifiers() { + if (!isComponentShard()) { + // TODO(bcorso): Consider making shards static and unnested too? + return ImmutableSet.of(PRIVATE, FINAL); + } else if (isNested()) { + return ImmutableSet.of(PRIVATE, STATIC, FINAL); + } + return graph.componentTypeElement().isPublic() + // TODO(ronshapiro): perhaps all generated components should be non-public? + ? ImmutableSet.of(PUBLIC, FINAL) + : ImmutableSet.of(FINAL); + } + + private void addCreator() { + componentCreatorImplementationFactoryProvider + .get() + .create() + .map(ComponentCreatorImplementation::spec) + .ifPresent( + creator -> + rootComponentImplementation() + .getComponentShard() + .addType(TypeSpecKind.COMPONENT_CREATOR, creator)); + } - /** Generates the component and returns the resulting {@link TypeSpec.Builder}. */ - public TypeSpec.Builder generate() { - modifiers().forEach(component::addModifiers); - fieldSpecsMap.asMap().values().forEach(component::addFields); - methodSpecsMap.asMap().values().forEach(component::addMethods); - typeSpecsMap.asMap().values().forEach(component::addTypes); - typeSuppliers.stream().map(Supplier::get).forEach(component::addType); - return component; + private void addFactoryMethods() { + if (parent.isPresent()) { + graph + .factoryMethod() + .map(XConverters::toJavac) + .ifPresent(this::createSubcomponentFactoryMethod); + } else { + createRootComponentFactoryMethod(); + } + } + + private void createRootComponentFactoryMethod() { + checkState(!parent.isPresent()); + // Top-level components have a static method that returns a builder or factory for the + // component. If the user defined a @Component.Builder or @Component.Factory, an + // implementation of their type is returned. Otherwise, an autogenerated Builder type is + // returned. + // TODO(cgdecker): Replace this abomination with a small class? + // Better yet, change things so that an autogenerated builder type has a descriptor of sorts + // just like a user-defined creator type. + ComponentCreatorKind creatorKind; + ClassName creatorType; + String factoryMethodName; + boolean noArgFactoryMethod; + Optional<ComponentCreatorDescriptor> creatorDescriptor = + graph.componentDescriptor().creatorDescriptor(); + if (creatorDescriptor.isPresent()) { + ComponentCreatorDescriptor descriptor = creatorDescriptor.get(); + creatorKind = descriptor.kind(); + creatorType = descriptor.typeElement().getClassName(); + factoryMethodName = getSimpleName(descriptor.factoryMethod()); + noArgFactoryMethod = descriptor.factoryParameters().isEmpty(); + } else { + creatorKind = BUILDER; + creatorType = getCreatorName(); + factoryMethodName = "build"; + noArgFactoryMethod = true; + } + validateMethodNameDoesNotOverrideGeneratedCreator(creatorKind.methodName()); + claimMethodName(creatorKind.methodName()); + MethodSpec creatorFactoryMethod = + methodBuilder(creatorKind.methodName()) + .addModifiers(PUBLIC, STATIC) + .returns(creatorType) + .addStatement("return new $T()", getCreatorName()) + .build(); + addMethod(MethodSpecKind.BUILDER_METHOD, creatorFactoryMethod); + if (noArgFactoryMethod && canInstantiateAllRequirements()) { + validateMethodNameDoesNotOverrideGeneratedCreator("create"); + claimMethodName("create"); + addMethod( + MethodSpecKind.BUILDER_METHOD, + methodBuilder("create") + .returns(graph.componentTypeElement().getClassName()) + .addModifiers(PUBLIC, STATIC) + .addStatement("return new $L().$L()", creatorKind.typeName(), factoryMethodName) + .build()); + } + } + + private void validateMethodNameDoesNotOverrideGeneratedCreator(String creatorName) { + // Check if there is any client added method has the same signature as generated creatorName. + MoreElements.getAllMethods(toJavac(graph.componentTypeElement()), types, elements).stream() + .filter(method -> method.getSimpleName().contentEquals(creatorName)) + .filter(method -> method.getParameters().isEmpty()) + .filter(method -> !method.getModifiers().contains(Modifier.STATIC)) + .forEach( + (ExecutableElement method) -> + messager.printMessage( + ERROR, + String.format( + "Cannot override generated method: %s.%s()", + method.getEnclosingElement().getSimpleName(), method.getSimpleName()))); + } + + /** {@code true} if all of the graph's required dependencies can be automatically constructed */ + private boolean canInstantiateAllRequirements() { + return !Iterables.any( + graph.componentRequirements(), ComponentRequirement::requiresAPassedInstance); + } + + private void createSubcomponentFactoryMethod(ExecutableElement factoryMethod) { + checkState(parent.isPresent()); + Collection<ParameterSpec> params = + Maps.transformValues( + graph.factoryMethodParameters(), + parameter -> ParameterSpec.get(toJavac(parameter))) + .values(); + DeclaredType parentType = + asDeclared(toJavac(parent.get().graph().componentTypeElement()).asType()); + MethodSpec.Builder method = MethodSpec.overriding(factoryMethod, parentType, types); + params.forEach( + param -> method.addStatement("$T.checkNotNull($N)", Preconditions.class, param)); + method.addStatement( + "return new $T($L)", + name(), + parameterNames( + ImmutableList.<ParameterSpec>builder() + .addAll( + creatorComponentFields().stream() + .map(field -> ParameterSpec.builder(field.type, field.name).build()) + .collect(toImmutableList())) + .addAll(params) + .build())); + + parent.get().getComponentShard().addMethod(COMPONENT_METHOD, method.build()); + } + + private void addInterfaceMethods() { + // Each component method may have been declared by several supertypes. We want to implement + // only one method for each distinct signature. + XType componentType = graph.componentTypeElement().getType(); + Set<MethodSignature> signatures = Sets.newHashSet(); + for (ComponentMethodDescriptor method : graph.componentDescriptor().entryPointMethods()) { + if (signatures.add(MethodSignature.forComponentMethod(method, componentType))) { + addMethod(COMPONENT_METHOD, bindingExpressionsProvider.get().getComponentMethod(method)); + } + } + } + + private void addChildComponents() { + for (BindingGraph subgraph : graph.subgraphs()) { + rootComponentImplementation() + .getComponentShard() + .addType( + TypeSpecKind.SUBCOMPONENT, + childComponentImplementationFactory.create(subgraph).generate()); + } + } + + private void addShards() { + // Generate all shards and add them to this component implementation. + for (ShardImplementation shard : ImmutableSet.copyOf(shardsByBinding.values())) { + if (shardFieldsByImplementation.containsKey(shard)) { + addField(FieldSpecKind.COMPONENT_SHARD_FIELD, shardFieldsByImplementation.get(shard)); + TypeSpec shardTypeSpec = shard.generate(); + addType(TypeSpecKind.COMPONENT_SHARD_TYPE, shardTypeSpec); + } + } + } + + /** Creates and adds the constructor and methods needed for initializing the component. */ + private void addConstructorAndInitializationMethods() { + MethodSpec.Builder constructor = constructorBuilder().addModifiers(PRIVATE); + ImmutableList<ParameterSpec> parameters = constructorParameters.values().asList(); + + if (isComponentShard()) { + // Add a constructor parameter and initialization for each component field. We initialize + // these fields immediately so that we don't need to be pass them to each initialize method + // and shard constructor. + componentFieldsByImplementation() + .forEach( + (componentImplementation, field) -> { + if (componentImplementation.equals(ComponentImplementation.this)) { + // For the self-referenced component field, + // just initialize it in the initializer. + addField( + FieldSpecKind.COMPONENT_REQUIREMENT_FIELD, + field.toBuilder().initializer("this").build()); + } else { + addField(FieldSpecKind.COMPONENT_REQUIREMENT_FIELD, field); + constructor.addStatement("this.$1N = $1N", field); + constructor.addParameter(field.type, field.name); + } + }); + constructor.addCode(CodeBlocks.concat(componentRequirementInitializations)); + } + constructor.addParameters(parameters); + + // TODO(cgdecker): It's not the case that each initialize() method has need for all of the + // given parameters. In some cases, those parameters may have already been assigned to fields + // which could be referenced instead. In other cases, an initialize method may just not need + // some of the parameters because the set of initializations in that partition does not + // include any reference to them. Right now, the Dagger code has no way of getting that + // information because, among other things, componentImplementation.getImplementations() just + // returns a bunch of CodeBlocks with no semantic information. Additionally, we may not know + // yet whether a field will end up needing to be created for a specific requirement, and we + // don't want to create a field that ends up only being used during initialization. + CodeBlock args = parameterNames(parameters); + ImmutableList<MethodSpec> initializationMethods = + createPartitionedMethods( + "initialize", + // TODO(bcorso): Rather than passing in all of the constructor parameters, keep track + // of which parameters are used during initialization and only pass those. This could + // be useful for FastInit, where most of the initializations are just calling + // SwitchingProvider with no parameters. + makeFinal(parameters), + initializations, + methodName -> + methodBuilder(methodName) + /* TODO(gak): Strictly speaking, we only need the suppression here if we are + * also initializing a raw field in this method, but the structure of this + * code makes it awkward to pass that bit through. This will be cleaned up + * when we no longer separate fields and initialization as we do now. */ + .addAnnotation(suppressWarnings(UNCHECKED))); + + for (MethodSpec initializationMethod : initializationMethods) { + constructor.addStatement("$N($L)", initializationMethod, args); + addMethod(MethodSpecKind.INITIALIZE_METHOD, initializationMethod); + } + + if (isComponentShard()) { + constructor.addCode(CodeBlocks.concat(shardInitializations)); + } else { + // This initialization is called from the componentShard, so we need to use those args. + CodeBlock componentArgs = + parameterNames(componentShard.constructorParameters.values().asList()); + FieldSpec shardField = shardFieldsByImplementation.get(this); + shardInitializations.add(CodeBlock.of("$N = new $T($L);", shardField, name, componentArgs)); + } + + addMethod(MethodSpecKind.CONSTRUCTOR, constructor.build()); + } + + private void addCancellationListenerImplementation() { + MethodSpec.Builder methodBuilder = + methodBuilder(CANCELLATION_LISTENER_METHOD_NAME) + .addModifiers(PUBLIC) + .addAnnotation(Override.class) + .addParameter(MAY_INTERRUPT_IF_RUNNING_PARAM); + + // Reversing should order cancellations starting from entry points and going down to leaves + // rather than the other way around. This shouldn't really matter but seems *slightly* + // preferable because: + // When a future that another future depends on is cancelled, that cancellation will propagate + // up the future graph toward the entry point. Cancelling in reverse order should ensure that + // everything that depends on a particular node has already been cancelled when that node is + // cancelled, so there's no need to propagate. Otherwise, when we cancel a leaf node, it might + // propagate through most of the graph, making most of the cancel calls that follow in the + // onProducerFutureCancelled method do nothing. + if (isComponentShard()) { + methodBuilder.addCode( + CodeBlocks.concat(ImmutableList.copyOf(shardCancellations).reverse())); + } else if (!cancellations.isEmpty()) { + shardCancellations.add( + CodeBlock.of( + "$N.$N($N);", + shardFieldsByImplementation.get(this), + CANCELLATION_LISTENER_METHOD_NAME, + MAY_INTERRUPT_IF_RUNNING_PARAM)); + } + + ImmutableList<CodeBlock> cancellationStatements = + ImmutableList.copyOf(cancellations.values()).reverse(); + if (cancellationStatements.size() < STATEMENTS_PER_METHOD) { + methodBuilder.addCode(CodeBlocks.concat(cancellationStatements)).build(); + } else { + ImmutableList<MethodSpec> cancelProducersMethods = + createPartitionedMethods( + "cancelProducers", + ImmutableList.of(MAY_INTERRUPT_IF_RUNNING_PARAM), + cancellationStatements, + methodName -> methodBuilder(methodName).addModifiers(PRIVATE)); + for (MethodSpec cancelProducersMethod : cancelProducersMethods) { + methodBuilder.addStatement( + "$N($N)", cancelProducersMethod, MAY_INTERRUPT_IF_RUNNING_PARAM); + addMethod(MethodSpecKind.CANCELLATION_LISTENER_METHOD, cancelProducersMethod); + } + } + + if (isComponentShard()) { + cancelParentStatement().ifPresent(methodBuilder::addCode); + } + + addMethod(MethodSpecKind.CANCELLATION_LISTENER_METHOD, methodBuilder.build()); + } + + private Optional<CodeBlock> cancelParentStatement() { + if (!shouldPropagateCancellationToParent()) { + return Optional.empty(); + } + return Optional.of( + CodeBlock.builder() + .addStatement( + "$L.$N($N)", + parent.get().componentFieldReference(), + CANCELLATION_LISTENER_METHOD_NAME, + MAY_INTERRUPT_IF_RUNNING_PARAM) + .build()); + } + + private boolean shouldPropagateCancellationToParent() { + return parent.isPresent() + && parent + .get() + .componentDescriptor() + .cancellationPolicy() + .map(policy -> policy.fromSubcomponents().equals(PROPAGATE)) + .orElse(false); + } + + /** + * Creates one or more methods, all taking the given {@code parameters}, which partition the + * given list of {@code statements} among themselves such that no method has more than {@code + * STATEMENTS_PER_METHOD} statements in it and such that the returned methods, if called in + * order, will execute the {@code statements} in the given order. + */ + private ImmutableList<MethodSpec> createPartitionedMethods( + String methodName, + Iterable<ParameterSpec> parameters, + List<CodeBlock> statements, + Function<String, MethodSpec.Builder> methodBuilderCreator) { + return Lists.partition(statements, STATEMENTS_PER_METHOD).stream() + .map( + partition -> + methodBuilderCreator + .apply(getUniqueMethodName(methodName)) + .addModifiers(PRIVATE) + .addParameters(parameters) + .addCode(CodeBlocks.concat(partition)) + .build()) + .collect(toImmutableList()); + } } - private ImmutableSet<Modifier> modifiers() { - if (isNested()) { - return ImmutableSet.of(PRIVATE, FINAL); + private static ImmutableList<ComponentRequirement> constructorRequirements(BindingGraph graph) { + if (graph.componentDescriptor().hasCreator()) { + return graph.componentRequirements().asList(); + } else if (graph.factoryMethod().isPresent()) { + return graph.factoryMethodParameters().keySet().asList(); + } else { + throw new AssertionError( + "Expected either a component creator or factory method but found neither."); } - return graph.componentTypeElement().getModifiers().contains(PUBLIC) - // TODO(ronshapiro): perhaps all generated components should be non-public? - ? ImmutableSet.of(PUBLIC, FINAL) - : ImmutableSet.of(FINAL); + } + + private static ImmutableList<ParameterSpec> makeFinal(List<ParameterSpec> parameters) { + return parameters.stream() + .map(param -> param.toBuilder().addModifiers(FINAL).build()) + .collect(toImmutableList()); } } diff --git a/java/dagger/internal/codegen/writing/ComponentInstanceBindingExpression.java b/java/dagger/internal/codegen/writing/ComponentInstanceRequestRepresentation.java index 9fa10c6e0..441ee83b2 100644 --- a/java/dagger/internal/codegen/writing/ComponentInstanceBindingExpression.java +++ b/java/dagger/internal/codegen/writing/ComponentInstanceRequestRepresentation.java @@ -18,26 +18,35 @@ package dagger.internal.codegen.writing; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.javapoet.Expression; /** A binding expression for the instance of the component itself, i.e. {@code this}. */ -final class ComponentInstanceBindingExpression extends SimpleInvocationBindingExpression { - private final ClassName componentName; +final class ComponentInstanceRequestRepresentation extends RequestRepresentation { + private final ComponentImplementation componentImplementation; private final ContributionBinding binding; - ComponentInstanceBindingExpression(ContributionBinding binding, ClassName componentName) { - super(binding); + @AssistedInject + ComponentInstanceRequestRepresentation( + @Assisted ContributionBinding binding, ComponentImplementation componentImplementation) { + this.componentImplementation = componentImplementation; this.binding = binding; - this.componentName = componentName; } @Override Expression getDependencyExpression(ClassName requestingClass) { return Expression.create( - binding.key().type(), - componentName.equals(requestingClass) + binding.key().type().java(), + componentImplementation.name().equals(requestingClass) ? CodeBlock.of("this") - : CodeBlock.of("$T.this", componentName)); + : componentImplementation.componentFieldReference()); + } + + @AssistedFactory + static interface Factory { + ComponentInstanceRequestRepresentation create(ContributionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/ComponentMethodBindingExpression.java b/java/dagger/internal/codegen/writing/ComponentMethodRequestRepresentation.java index 7fa9aa3ed..77e313f0f 100644 --- a/java/dagger/internal/codegen/writing/ComponentMethodBindingExpression.java +++ b/java/dagger/internal/codegen/writing/ComponentMethodRequestRepresentation.java @@ -17,13 +17,13 @@ package dagger.internal.codegen.writing; import static com.google.common.base.Preconditions.checkNotNull; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; -import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; -import dagger.internal.codegen.binding.BindingRequest; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; -import dagger.internal.codegen.binding.ContributionBinding; -import dagger.internal.codegen.javapoet.Expression; import dagger.internal.codegen.langmodel.DaggerTypes; import javax.lang.model.type.TypeMirror; @@ -32,61 +32,58 @@ import javax.lang.model.type.TypeMirror; * * <p>Dependents of this binding expression will just call the component method. */ -final class ComponentMethodBindingExpression extends MethodBindingExpression { +final class ComponentMethodRequestRepresentation extends MethodRequestRepresentation { + private final RequestRepresentation wrappedRequestRepresentation; private final ComponentImplementation componentImplementation; private final ComponentMethodDescriptor componentMethod; + private final DaggerTypes types; - ComponentMethodBindingExpression( - BindingRequest request, - ContributionBinding binding, - MethodImplementationStrategy methodImplementationStrategy, - BindingExpression wrappedBindingExpression, + @AssistedInject + ComponentMethodRequestRepresentation( + @Assisted RequestRepresentation wrappedRequestRepresentation, + @Assisted ComponentMethodDescriptor componentMethod, ComponentImplementation componentImplementation, - ComponentMethodDescriptor componentMethod, DaggerTypes types) { - super( - request, - binding, - methodImplementationStrategy, - wrappedBindingExpression, - componentImplementation, - types); - this.componentImplementation = checkNotNull(componentImplementation); + super(componentImplementation.getComponentShard(), types); + this.wrappedRequestRepresentation = checkNotNull(wrappedRequestRepresentation); this.componentMethod = checkNotNull(componentMethod); + this.componentImplementation = componentImplementation; + this.types = types; } @Override protected CodeBlock getComponentMethodImplementation( ComponentMethodDescriptor componentMethod, ComponentImplementation component) { // There could be several methods on the component for the same request key and kind. - // Only one should use the BindingMethodImplementation; the others can delegate that one. So - // use methodImplementation.body() only if componentMethod equals the method for this instance. - + // Only one should use the BindingMethodImplementation; the others can delegate that one. // Separately, the method might be defined on a supertype that is also a supertype of some // parent component. In that case, the same ComponentMethodDescriptor will be used to add a CMBE // for the parent and the child. Only the parent's should use the BindingMethodImplementation; // the child's can delegate to the parent. So use methodImplementation.body() only if // componentName equals the component for this instance. return componentMethod.equals(this.componentMethod) && component.equals(componentImplementation) - ? methodBodyForComponentMethod(componentMethod) + ? CodeBlock.of( + "return $L;", + wrappedRequestRepresentation + .getDependencyExpressionForComponentMethod(componentMethod, componentImplementation) + .codeBlock()) : super.getComponentMethodImplementation(componentMethod, component); } @Override - Expression getDependencyExpression(ClassName requestingClass) { - // If a component method returns a primitive, update the expression's type which might be boxed. - Expression expression = super.getDependencyExpression(requestingClass); - TypeMirror methodReturnType = componentMethod.methodElement().getReturnType(); - return methodReturnType.getKind().isPrimitive() - ? Expression.create(methodReturnType, expression.codeBlock()) - : expression; + protected CodeBlock methodCall() { + return CodeBlock.of("$N()", getSimpleName(componentMethod.methodElement())); } @Override - protected void addMethod() {} + protected TypeMirror returnType() { + return componentMethod.resolvedReturnType(types); + } - @Override - protected String methodName() { - return componentMethod.methodElement().getSimpleName().toString(); + @AssistedFactory + static interface Factory { + ComponentMethodRequestRepresentation create( + RequestRepresentation wrappedRequestRepresentation, + ComponentMethodDescriptor componentMethod); } } diff --git a/java/dagger/internal/codegen/writing/ComponentNames.java b/java/dagger/internal/codegen/writing/ComponentNames.java new file mode 100644 index 000000000..d53a0f4c8 --- /dev/null +++ b/java/dagger/internal/codegen/writing/ComponentNames.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2015 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.writing; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static dagger.internal.codegen.binding.SourceFiles.classFileName; +import static dagger.internal.codegen.extension.DaggerCollectors.onlyElement; +import static java.lang.String.format; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimaps; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.base.ComponentCreatorKind; +import dagger.internal.codegen.base.UniqueNameSet; +import dagger.internal.codegen.binding.BindingGraph; +import dagger.internal.codegen.binding.ComponentCreatorDescriptor; +import dagger.internal.codegen.binding.ComponentDescriptor; +import dagger.internal.codegen.binding.KeyFactory; +import dagger.spi.model.ComponentPath; +import dagger.spi.model.Key; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.inject.Inject; + +/** + * Holds the unique simple names for all components, keyed by their {@link ComponentPath} and {@link + * Key} of the subcomponent builder. + */ +public final class ComponentNames { + /** Returns the class name for the root component. */ + public static ClassName getRootComponentClassName(ComponentDescriptor componentDescriptor) { + checkState(!componentDescriptor.isSubcomponent()); + ClassName componentName = componentDescriptor.typeElement().getClassName(); + return ClassName.get(componentName.packageName(), "Dagger" + classFileName(componentName)); + } + + private static final Splitter QUALIFIED_NAME_SPLITTER = Splitter.on('.'); + + private final ClassName rootName; + private final ImmutableMap<ComponentPath, String> namesByPath; + private final ImmutableMap<ComponentPath, String> creatorNamesByPath; + private final ImmutableMultimap<Key, ComponentPath> pathsByCreatorKey; + + @Inject + ComponentNames(@TopLevel BindingGraph graph, KeyFactory keyFactory) { + this.rootName = getRootComponentClassName(graph.componentDescriptor()); + this.namesByPath = namesByPath(graph); + this.creatorNamesByPath = creatorNamesByPath(namesByPath, graph); + this.pathsByCreatorKey = pathsByCreatorKey(keyFactory, graph); + } + + /** Returns the simple component name for the given {@link ComponentDescriptor}. */ + ClassName get(ComponentPath componentPath) { + return componentPath.atRoot() + ? rootName + : rootName.nestedClass(namesByPath.get(componentPath) + "Impl"); + } + + /** + * Returns the component descriptor for the component with the given subcomponent creator {@link + * Key}. + */ + ClassName getSubcomponentCreatorName(ComponentPath componentPath, Key creatorKey) { + checkArgument(pathsByCreatorKey.containsKey(creatorKey)); + // First, find the subcomponent path corresponding to the subcomponent creator key. + // The key may correspond to multiple paths, so we need to find the one under this component. + ComponentPath subcomponentPath = + pathsByCreatorKey.get(creatorKey).stream() + .filter(path -> path.parent().equals(componentPath)) + .collect(onlyElement()); + return getCreatorName(subcomponentPath); + } + + /** + * Returns the simple name for the subcomponent creator implementation for the given {@link + * ComponentDescriptor}. + */ + ClassName getCreatorName(ComponentPath componentPath) { + checkArgument(creatorNamesByPath.containsKey(componentPath)); + return rootName.nestedClass(creatorNamesByPath.get(componentPath)); + } + + private static ImmutableMap<ComponentPath, String> creatorNamesByPath( + ImmutableMap<ComponentPath, String> namesByPath, BindingGraph graph) { + ImmutableMap.Builder<ComponentPath, String> builder = ImmutableMap.builder(); + graph + .componentDescriptorsByPath() + .forEach( + (componentPath, componentDescriptor) -> { + if (componentPath.atRoot()) { + ComponentCreatorKind creatorKind = + componentDescriptor + .creatorDescriptor() + .map(ComponentCreatorDescriptor::kind) + .orElse(ComponentCreatorKind.BUILDER); + builder.put(componentPath, creatorKind.typeName()); + } else if (componentDescriptor.creatorDescriptor().isPresent()) { + ComponentCreatorDescriptor creatorDescriptor = + componentDescriptor.creatorDescriptor().get(); + String componentName = namesByPath.get(componentPath); + builder.put(componentPath, componentName + creatorDescriptor.kind().typeName()); + } + }); + return builder.build(); + } + + private static ImmutableMap<ComponentPath, String> namesByPath(BindingGraph graph) { + Map<ComponentPath, String> componentPathsBySimpleName = new LinkedHashMap<>(); + Multimaps.index(graph.componentDescriptorsByPath().keySet(), ComponentNames::simpleName) + .asMap() + .values() + .stream() + .map(ComponentNames::disambiguateConflictingSimpleNames) + .forEach(componentPathsBySimpleName::putAll); + componentPathsBySimpleName.remove(graph.componentPath()); + return ImmutableMap.copyOf(componentPathsBySimpleName); + } + + private static ImmutableMultimap<Key, ComponentPath> pathsByCreatorKey( + KeyFactory keyFactory, BindingGraph graph) { + ImmutableMultimap.Builder<Key, ComponentPath> builder = ImmutableMultimap.builder(); + graph + .componentDescriptorsByPath() + .forEach( + (componentPath, componentDescriptor) -> { + if (componentDescriptor.creatorDescriptor().isPresent()) { + Key creatorKey = + keyFactory.forSubcomponentCreator( + componentDescriptor.creatorDescriptor().get().typeElement().getType()); + builder.put(creatorKey, componentPath); + } + }); + return builder.build(); + } + + private static ImmutableMap<ComponentPath, String> disambiguateConflictingSimpleNames( + Collection<ComponentPath> componentsWithConflictingNames) { + // If there's only 1 component there's nothing to disambiguate so return the simple name. + if (componentsWithConflictingNames.size() == 1) { + ComponentPath componentPath = Iterables.getOnlyElement(componentsWithConflictingNames); + return ImmutableMap.of(componentPath, simpleName(componentPath)); + } + + // There are conflicting simple names, so disambiguate them with a unique prefix. + // We keep them small to fix https://github.com/google/dagger/issues/421. + UniqueNameSet nameSet = new UniqueNameSet(); + ImmutableMap.Builder<ComponentPath, String> uniqueNames = ImmutableMap.builder(); + for (ComponentPath componentPath : componentsWithConflictingNames) { + String simpleName = simpleName(componentPath); + String basePrefix = uniquingPrefix(componentPath); + uniqueNames.put( + componentPath, format("%s_%s", nameSet.getUniqueName(basePrefix), simpleName)); + } + return uniqueNames.build(); + } + + private static String simpleName(ComponentPath componentPath) { + return componentPath.currentComponent().className().simpleName(); + } + + /** Returns a prefix that could make the component's simple name more unique. */ + private static String uniquingPrefix(ComponentPath componentPath) { + ClassName component = componentPath.currentComponent().className(); + + if (component.enclosingClassName() != null) { + return CharMatcher.javaLowerCase().removeFrom(component.enclosingClassName().simpleName()); + } + + // Not in a normally named class. Prefix with the initials of the elements leading here. + Iterator<String> pieces = QUALIFIED_NAME_SPLITTER.split(component.canonicalName()).iterator(); + StringBuilder b = new StringBuilder(); + + while (pieces.hasNext()) { + String next = pieces.next(); + if (pieces.hasNext()) { + b.append(next.charAt(0)); + } + } + + // Note that a top level class in the root package will be prefixed "$_". + return b.length() > 0 ? b.toString() : "$"; + } +} diff --git a/java/dagger/internal/codegen/writing/ComponentProvisionBindingExpression.java b/java/dagger/internal/codegen/writing/ComponentProvisionRequestRepresentation.java index 53914b669..31eecb250 100644 --- a/java/dagger/internal/codegen/writing/ComponentProvisionBindingExpression.java +++ b/java/dagger/internal/codegen/writing/ComponentProvisionRequestRepresentation.java @@ -16,10 +16,13 @@ package dagger.internal.codegen.writing; -import static com.google.common.base.Preconditions.checkNotNull; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.Preconditions; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ComponentRequirement; @@ -28,36 +31,48 @@ import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.javapoet.Expression; /** A binding expression for component provision methods. */ -final class ComponentProvisionBindingExpression extends SimpleInvocationBindingExpression { +final class ComponentProvisionRequestRepresentation extends RequestRepresentation { private final ProvisionBinding binding; private final BindingGraph bindingGraph; private final ComponentRequirementExpressions componentRequirementExpressions; private final CompilerOptions compilerOptions; + private final boolean isExperimentalMergedMode; - ComponentProvisionBindingExpression( - ProvisionBinding binding, + @AssistedInject + ComponentProvisionRequestRepresentation( + @Assisted ProvisionBinding binding, BindingGraph bindingGraph, + ComponentImplementation componentImplementation, ComponentRequirementExpressions componentRequirementExpressions, CompilerOptions compilerOptions) { - super(binding); this.binding = binding; - this.bindingGraph = checkNotNull(bindingGraph); - this.componentRequirementExpressions = checkNotNull(componentRequirementExpressions); - this.compilerOptions = checkNotNull(compilerOptions); + this.bindingGraph = bindingGraph; + this.componentRequirementExpressions = componentRequirementExpressions; + this.compilerOptions = compilerOptions; + this.isExperimentalMergedMode = + componentImplementation.compilerMode().isExperimentalMergedMode(); } @Override Expression getDependencyExpression(ClassName requestingClass) { + CodeBlock componentDependency = + isExperimentalMergedMode + ? CodeBlock.of("(($T) dependencies[0])", componentRequirement().type().getTypeName()) + : getComponentRequirementExpression(requestingClass); CodeBlock invocation = CodeBlock.of( "$L.$L()", - componentRequirementExpressions.getExpression(componentRequirement(), requestingClass), - binding.bindingElement().get().getSimpleName()); + componentDependency, + toJavac(binding.bindingElement().get()).getSimpleName()); return Expression.create( - binding.contributedPrimitiveType().orElse(binding.key().type()), + binding.contributedPrimitiveType().orElse(binding.key().type().xprocessing()), maybeCheckForNull(binding, compilerOptions, invocation)); } + CodeBlock getComponentRequirementExpression(ClassName requestingClass) { + return componentRequirementExpressions.getExpression(componentRequirement(), requestingClass); + } + private ComponentRequirement componentRequirement() { return bindingGraph .componentDescriptor() @@ -70,4 +85,9 @@ final class ComponentProvisionBindingExpression extends SimpleInvocationBindingE ? CodeBlock.of("$T.checkNotNullFromComponent($L)", Preconditions.class, invocation) : invocation; } + + @AssistedFactory + static interface Factory { + ComponentProvisionRequestRepresentation create(ProvisionBinding binding); + } } diff --git a/java/dagger/internal/codegen/writing/ComponentRequestRepresentations.java b/java/dagger/internal/codegen/writing/ComponentRequestRepresentations.java new file mode 100644 index 000000000..324cef675 --- /dev/null +++ b/java/dagger/internal/codegen/writing/ComponentRequestRepresentations.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2016 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.writing; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; +import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; +import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; +import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessible; +import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; + +import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import dagger.internal.codegen.binding.Binding; +import dagger.internal.codegen.binding.BindingGraph; +import dagger.internal.codegen.binding.BindingRequest; +import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; +import dagger.internal.codegen.binding.ComponentRequirement; +import dagger.internal.codegen.binding.ContributionBinding; +import dagger.internal.codegen.binding.FrameworkType; +import dagger.internal.codegen.binding.FrameworkTypeMapper; +import dagger.internal.codegen.binding.MembersInjectionBinding; +import dagger.internal.codegen.binding.ProductionBinding; +import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.xprocessing.MethodSpecs; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.RequestKind; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import javax.inject.Inject; +import javax.lang.model.type.TypeMirror; + +/** A central repository of code expressions used to access any binding available to a component. */ +@PerComponentImplementation +public final class ComponentRequestRepresentations { + // TODO(dpb,ronshapiro): refactor this and ComponentRequirementExpressions into a + // HierarchicalComponentMap<K, V>, or perhaps this use a flattened ImmutableMap, built from its + // parents? If so, maybe make RequestRepresentation.Factory create it. + + private final Optional<ComponentRequestRepresentations> parent; + private final BindingGraph graph; + private final ComponentImplementation componentImplementation; + private final ComponentRequirementExpressions componentRequirementExpressions; + private final MembersInjectionBindingRepresentation.Factory + membersInjectionBindingRepresentationFactory; + private final ProvisionBindingRepresentation.Factory provisionBindingRepresentationFactory; + private final ProductionBindingRepresentation.Factory productionBindingRepresentationFactory; + private final ExperimentalSwitchingProviderDependencyRepresentation.Factory + experimentalSwitchingProviderDependencyRepresentationFactory; + private final DaggerTypes types; + private final Map<Binding, BindingRepresentation> representations = new HashMap<>(); + private final Map<Binding, ExperimentalSwitchingProviderDependencyRepresentation> + experimentalSwitchingProviderDependencyRepresentations = new HashMap<>(); + + @Inject + ComponentRequestRepresentations( + @ParentComponent Optional<ComponentRequestRepresentations> parent, + BindingGraph graph, + ComponentImplementation componentImplementation, + ComponentRequirementExpressions componentRequirementExpressions, + MembersInjectionBindingRepresentation.Factory membersInjectionBindingRepresentationFactory, + ProvisionBindingRepresentation.Factory provisionBindingRepresentationFactory, + ProductionBindingRepresentation.Factory productionBindingRepresentationFactory, + ExperimentalSwitchingProviderDependencyRepresentation.Factory + experimentalSwitchingProviderDependencyRepresentationFactory, + DaggerTypes types) { + this.parent = parent; + this.graph = graph; + this.componentImplementation = componentImplementation; + this.membersInjectionBindingRepresentationFactory = + membersInjectionBindingRepresentationFactory; + this.provisionBindingRepresentationFactory = provisionBindingRepresentationFactory; + this.productionBindingRepresentationFactory = productionBindingRepresentationFactory; + this.experimentalSwitchingProviderDependencyRepresentationFactory = + experimentalSwitchingProviderDependencyRepresentationFactory; + this.componentRequirementExpressions = checkNotNull(componentRequirementExpressions); + this.types = types; + } + + /** + * Returns an expression that evaluates to the value of a binding request for a binding owned by + * this component or an ancestor. + * + * @param requestingClass the class that will contain the expression + * @throws IllegalStateException if there is no binding expression that satisfies the request + */ + public Expression getDependencyExpression(BindingRequest request, ClassName requestingClass) { + return getRequestRepresentation(request).getDependencyExpression(requestingClass); + } + + /** + * Equivalent to {@link #getDependencyExpression(BindingRequest, ClassName)} that is used only + * when the request is for implementation of a component method. + * + * @throws IllegalStateException if there is no binding expression that satisfies the request + */ + Expression getDependencyExpressionForComponentMethod( + BindingRequest request, + ComponentMethodDescriptor componentMethod, + ComponentImplementation componentImplementation) { + return getRequestRepresentation(request) + .getDependencyExpressionForComponentMethod(componentMethod, componentImplementation); + } + + /** + * Returns the {@link CodeBlock} for the method arguments used with the factory {@code create()} + * method for the given {@link ContributionBinding binding}. + */ + CodeBlock getCreateMethodArgumentsCodeBlock( + ContributionBinding binding, ClassName requestingClass) { + return makeParametersCodeBlock(getCreateMethodArgumentsCodeBlocks(binding, requestingClass)); + } + + private ImmutableList<CodeBlock> getCreateMethodArgumentsCodeBlocks( + ContributionBinding binding, ClassName requestingClass) { + ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder(); + + if (binding.requiresModuleInstance()) { + arguments.add( + componentRequirementExpressions.getExpressionDuringInitialization( + ComponentRequirement.forModule(binding.contributingModule().get().getType()), + requestingClass)); + } + + binding.dependencies().stream() + .map(dependency -> frameworkRequest(binding, dependency)) + .map(request -> getDependencyExpression(request, requestingClass)) + .map(Expression::codeBlock) + .forEach(arguments::add); + + return arguments.build(); + } + + private static BindingRequest frameworkRequest( + ContributionBinding binding, DependencyRequest dependency) { + // TODO(bcorso): See if we can get rid of FrameworkTypeMatcher + FrameworkType frameworkType = + FrameworkTypeMapper.forBindingType(binding.bindingType()) + .getFrameworkType(dependency.kind()); + return BindingRequest.bindingRequest(dependency.key(), frameworkType); + } + + /** + * Returns an expression that evaluates to the value of a dependency request, for passing to a + * binding method, an {@code @Inject}-annotated constructor or member, or a proxy for one. + * + * <p>If the method is a generated static {@link InjectionMethods injection method}, each + * parameter will be {@link Object} if the dependency's raw type is inaccessible. If that is the + * case for this dependency, the returned expression will use a cast to evaluate to the raw type. + * + * @param requestingClass the class that will contain the expression + */ + Expression getDependencyArgumentExpression( + DependencyRequest dependencyRequest, ClassName requestingClass) { + + TypeMirror dependencyType = dependencyRequest.key().type().java(); + BindingRequest bindingRequest = bindingRequest(dependencyRequest); + Expression dependencyExpression = getDependencyExpression(bindingRequest, requestingClass); + + if (dependencyRequest.kind().equals(RequestKind.INSTANCE) + && !isTypeAccessibleFrom(dependencyType, requestingClass.packageName()) + && isRawTypeAccessible(dependencyType, requestingClass.packageName())) { + return dependencyExpression.castTo(types.erasure(dependencyType)); + } + + return dependencyExpression; + } + + /** Returns the implementation of a component method. */ + public MethodSpec getComponentMethod(ComponentMethodDescriptor componentMethod) { + checkArgument(componentMethod.dependencyRequest().isPresent()); + BindingRequest request = bindingRequest(componentMethod.dependencyRequest().get()); + return MethodSpecs.overriding( + componentMethod.methodElement(), graph.componentTypeElement().getType()) + .addCode( + getRequestRepresentation(request) + .getComponentMethodImplementation(componentMethod, componentImplementation)) + .build(); + } + + /** Returns the {@link RequestRepresentation} for the given {@link BindingRequest}. */ + RequestRepresentation getRequestRepresentation(BindingRequest request) { + Optional<Binding> localBinding = + request.isRequestKind(RequestKind.MEMBERS_INJECTION) + ? graph.localMembersInjectionBinding(request.key()) + : graph.localContributionBinding(request.key()); + + if (localBinding.isPresent()) { + return getBindingRepresentation(localBinding.get()).getRequestRepresentation(request); + } + + checkArgument(parent.isPresent(), "no expression found for %s", request); + return parent.get().getRequestRepresentation(request); + } + + private BindingRepresentation getBindingRepresentation(Binding binding) { + return reentrantComputeIfAbsent( + representations, binding, this::getBindingRepresentationUncached); + } + + private BindingRepresentation getBindingRepresentationUncached(Binding binding) { + switch (binding.bindingType()) { + case MEMBERS_INJECTION: + return membersInjectionBindingRepresentationFactory.create( + (MembersInjectionBinding) binding); + case PROVISION: + return provisionBindingRepresentationFactory.create((ProvisionBinding) binding); + case PRODUCTION: + return productionBindingRepresentationFactory.create((ProductionBinding) binding); + } + throw new AssertionError(); + } + + /** + * Returns an {@link ExperimentalSwitchingProviderDependencyRepresentation} for the requested + * binding to satisfy dependency requests on it from experimental switching providers. Cannot be + * used for Members Injection requests. + */ + ExperimentalSwitchingProviderDependencyRepresentation + getExperimentalSwitchingProviderDependencyRepresentation(BindingRequest request) { + checkState( + componentImplementation.compilerMode().isExperimentalMergedMode(), + "Compiler mode should be experimentalMergedMode!"); + Optional<Binding> localBinding = graph.localContributionBinding(request.key()); + + if (localBinding.isPresent()) { + return reentrantComputeIfAbsent( + experimentalSwitchingProviderDependencyRepresentations, + localBinding.get(), + binding -> + experimentalSwitchingProviderDependencyRepresentationFactory.create( + (ProvisionBinding) binding)); + } + + checkArgument(parent.isPresent(), "no expression found for %s", request); + return parent.get().getExperimentalSwitchingProviderDependencyRepresentation(request); + } +} diff --git a/java/dagger/internal/codegen/writing/ComponentRequirementExpression.java b/java/dagger/internal/codegen/writing/ComponentRequirementExpression.java index 13008b8eb..cdfdf26c5 100644 --- a/java/dagger/internal/codegen/writing/ComponentRequirementExpression.java +++ b/java/dagger/internal/codegen/writing/ComponentRequirementExpression.java @@ -22,8 +22,8 @@ import dagger.internal.codegen.binding.ComponentRequirement; /** * A factory for expressions of {@link ComponentRequirement}s in the generated component. This is - * <em>not</em> a {@link BindingExpression}, since {@link ComponentRequirement}s do not have a - * {@link dagger.model.Key}. See {@link ComponentRequirementBindingExpression} for binding + * <em>not</em> a {@link RequestRepresentation}, since {@link ComponentRequirement}s do not have a + * {@link dagger.spi.model.Key}. See {@link ComponentRequirementRequestRepresentation} for binding * expressions that are themselves a component requirement. */ interface ComponentRequirementExpression { diff --git a/java/dagger/internal/codegen/writing/ComponentRequirementExpressions.java b/java/dagger/internal/codegen/writing/ComponentRequirementExpressions.java index 653a7a25a..2e9e5f1fd 100644 --- a/java/dagger/internal/codegen/writing/ComponentRequirementExpressions.java +++ b/java/dagger/internal/codegen/writing/ComponentRequirementExpressions.java @@ -23,6 +23,7 @@ import static dagger.internal.codegen.writing.ComponentImplementation.FieldSpecK import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.base.Supplier; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; @@ -31,11 +32,11 @@ import com.squareup.javapoet.TypeName; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ComponentRequirement; import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; import java.util.HashMap; import java.util.Map; import java.util.Optional; import javax.inject.Inject; -import javax.lang.model.element.TypeElement; /** * A central repository of expressions used to access any {@link ComponentRequirement} available to @@ -44,7 +45,7 @@ import javax.lang.model.element.TypeElement; @PerComponentImplementation public final class ComponentRequirementExpressions { - // TODO(dpb,ronshapiro): refactor this and ComponentBindingExpressions into a + // TODO(dpb,ronshapiro): refactor this and ComponentRequestRepresentations into a // HierarchicalComponentMap<K, V>, or perhaps this use a flattened ImmutableMap, built from its // parents? If so, maybe make ComponentRequirementExpression.Factory create it. @@ -52,10 +53,9 @@ public final class ComponentRequirementExpressions { private final Map<ComponentRequirement, ComponentRequirementExpression> componentRequirementExpressions = new HashMap<>(); private final BindingGraph graph; - private final ComponentImplementation componentImplementation; + private final ShardImplementation componentShard; private final ModuleProxies moduleProxies; - // TODO(ronshapiro): give ComponentImplementation a graph() method @Inject ComponentRequirementExpressions( @ParentComponent Optional<ComponentRequirementExpressions> parent, @@ -65,7 +65,8 @@ public final class ComponentRequirementExpressions { ModuleProxies moduleProxies) { this.parent = parent; this.graph = graph; - this.componentImplementation = componentImplementation; + // All component requirements go in the componentShard. + this.componentShard = componentImplementation.getComponentShard(); this.moduleProxies = moduleProxies; } @@ -78,6 +79,19 @@ public final class ComponentRequirementExpressions { return getExpression(componentRequirement).getExpression(requestingClass); } + private ComponentRequirementExpression getExpression(ComponentRequirement componentRequirement) { + if (graph.componentRequirements().contains(componentRequirement)) { + return componentRequirementExpressions.computeIfAbsent( + componentRequirement, this::createExpression); + } + if (parent.isPresent()) { + return parent.get().getExpression(componentRequirement); + } + + throw new IllegalStateException( + "no component requirement expression found for " + componentRequirement); + } + /** * Returns an expression for the {@code componentRequirement} to be used only within {@code * initialize()} methods, where the component constructor parameters are available. @@ -90,58 +104,26 @@ public final class ComponentRequirementExpressions { return getExpression(componentRequirement).getExpressionDuringInitialization(requestingClass); } - ComponentRequirementExpression getExpression(ComponentRequirement componentRequirement) { - if (graph.componentRequirements().contains(componentRequirement)) { - return componentRequirementExpressions.computeIfAbsent( - componentRequirement, this::createField); - } - if (parent.isPresent()) { - return parent.get().getExpression(componentRequirement); - } - - throw new IllegalStateException( - "no component requirement expression found for " + componentRequirement); - } - /** Returns a field for a {@link ComponentRequirement}. */ - private ComponentRequirementExpression createField(ComponentRequirement requirement) { - if (componentImplementation.componentDescriptor().hasCreator()) { - return new ComponentParameterField(requirement, componentImplementation, Optional.empty()); - } else if (graph.factoryMethod().isPresent() - && graph.factoryMethodParameters().containsKey(requirement)) { - String parameterName = - graph.factoryMethodParameters().get(requirement).getSimpleName().toString(); - return new ComponentParameterField( - requirement, componentImplementation, Optional.of(parameterName)); + private ComponentRequirementExpression createExpression(ComponentRequirement requirement) { + if (componentShard.componentDescriptor().hasCreator() + || (graph.factoryMethod().isPresent() + && graph.factoryMethodParameters().containsKey(requirement))) { + return new ComponentParameterField(requirement); } else if (requirement.kind().isModule()) { - return new InstantiableModuleField(requirement, componentImplementation); + return new InstantiableModuleField(requirement); } else { throw new AssertionError( - String.format("Can't create %s in %s", requirement, componentImplementation.name())); + String.format("Can't create %s in %s", requirement, componentShard.name())); } } - private abstract static class AbstractField implements ComponentRequirementExpression { + private abstract class AbstractField implements ComponentRequirementExpression { final ComponentRequirement componentRequirement; - final ComponentImplementation componentImplementation; - final String fieldName; - private final Supplier<MemberSelect> field = memoize(this::addField); + private final Supplier<MemberSelect> field = memoize(this::createField); - private AbstractField( - ComponentRequirement componentRequirement, - ComponentImplementation componentImplementation) { + private AbstractField(ComponentRequirement componentRequirement) { this.componentRequirement = checkNotNull(componentRequirement); - this.componentImplementation = checkNotNull(componentImplementation); - // Note: The field name is being claimed eagerly here even though we don't know at this point - // whether or not the requirement will even need a field. This is done because: - // A) ComponentParameterField wants to ensure that it doesn't give the parameter the same name - // as any field in the component, which requires that it claim a "field name" for itself - // when naming the parameter. - // B) The parameter name may be needed before the field name is. - // C) We want to prefer giving the best name to the field rather than the parameter given its - // wider scope. - this.fieldName = - componentImplementation.getUniqueFieldName(componentRequirement.variableName()); } @Override @@ -149,16 +131,13 @@ public final class ComponentRequirementExpressions { return field.get().getExpressionFor(requestingClass); } - private MemberSelect addField() { - FieldSpec field = createField(); - componentImplementation.addField(COMPONENT_REQUIREMENT_FIELD, field); - componentImplementation.addComponentRequirementInitialization(fieldInitialization(field)); - return MemberSelect.localField(componentImplementation.name(), fieldName); - } - - private FieldSpec createField() { - return FieldSpec.builder(TypeName.get(componentRequirement.type()), fieldName, PRIVATE, FINAL) - .build(); + private MemberSelect createField() { + String fieldName = componentShard.getUniqueFieldName(componentRequirement.variableName()); + TypeName fieldType = componentRequirement.type().getTypeName(); + FieldSpec field = FieldSpec.builder(fieldType, fieldName, PRIVATE, FINAL).build(); + componentShard.addField(COMPONENT_REQUIREMENT_FIELD, field); + componentShard.addComponentRequirementInitialization(fieldInitialization(field)); + return MemberSelect.localField(componentShard, fieldName); } /** Returns the {@link CodeBlock} that initializes the component field during construction. */ @@ -170,11 +149,10 @@ public final class ComponentRequirementExpressions { * instantiated by the component (i.e. a static class with a no-arg constructor). */ private final class InstantiableModuleField extends AbstractField { - private final TypeElement moduleElement; + private final XTypeElement moduleElement; - private InstantiableModuleField( - ComponentRequirement module, ComponentImplementation componentImplementation) { - super(module, componentImplementation); + InstantiableModuleField(ComponentRequirement module) { + super(module); checkArgument(module.kind().isModule()); this.moduleElement = module.typeElement(); } @@ -184,7 +162,7 @@ public final class ComponentRequirementExpressions { return CodeBlock.of( "this.$N = $L;", componentField, - moduleProxies.newModuleInstance(moduleElement, componentImplementation.name())); + moduleProxies.newModuleInstance(moduleElement, componentShard.name())); } } @@ -192,29 +170,17 @@ public final class ComponentRequirementExpressions { * A {@link ComponentRequirementExpression} for {@link ComponentRequirement}s that are passed in * as parameters to the component's constructor. */ - private static final class ComponentParameterField extends AbstractField { + private final class ComponentParameterField extends AbstractField { private final String parameterName; - private ComponentParameterField( - ComponentRequirement componentRequirement, - ComponentImplementation componentImplementation, - Optional<String> name) { - super(componentRequirement, componentImplementation); - // Get the name that the component implementation will use for its parameter for the - // requirement. If the given name is different than the name of the field created for the - // requirement (as may be the case when the parameter name is derived from a user-written - // factory method parameter), just use that as the base name for the parameter. Otherwise, - // append "Param" to the end of the name to differentiate. - // In either case, componentImplementation.getParameterName() will ensure that the final name - // that is used is not the same name as any field in the component even if there's something - // weird where the component actually has fields named, say, "foo" and "fooParam". - String baseName = name.filter(n -> !n.equals(fieldName)).orElse(fieldName + "Param"); - this.parameterName = componentImplementation.getParameterName(componentRequirement, baseName); + ComponentParameterField(ComponentRequirement module) { + super(module); + this.parameterName = componentShard.getParameterName(componentRequirement); } @Override public CodeBlock getExpressionDuringInitialization(ClassName requestingClass) { - if (componentImplementation.name().equals(requestingClass)) { + if (componentShard.name().equals(requestingClass)) { return CodeBlock.of("$L", parameterName); } else { // requesting this component requirement during initialization of a child component requires diff --git a/java/dagger/internal/codegen/writing/ComponentRequirementBindingExpression.java b/java/dagger/internal/codegen/writing/ComponentRequirementRequestRepresentation.java index 299f27974..f9e8f1479 100644 --- a/java/dagger/internal/codegen/writing/ComponentRequirementBindingExpression.java +++ b/java/dagger/internal/codegen/writing/ComponentRequirementRequestRepresentation.java @@ -16,7 +16,12 @@ package dagger.internal.codegen.writing; +import static com.google.common.base.Preconditions.checkNotNull; + import com.squareup.javapoet.ClassName; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ComponentRequirement; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.javapoet.Expression; @@ -26,16 +31,16 @@ import dagger.internal.codegen.javapoet.Expression; * {@linkplain dagger.Component#dependencies() component} and {@linkplain * dagger.producers.ProductionComponent#dependencies() production component dependencies}. */ -final class ComponentRequirementBindingExpression extends SimpleInvocationBindingExpression { +final class ComponentRequirementRequestRepresentation extends RequestRepresentation { private final ComponentRequirement componentRequirement; private final ComponentRequirementExpressions componentRequirementExpressions; - ComponentRequirementBindingExpression( - ContributionBinding binding, - ComponentRequirement componentRequirement, + @AssistedInject + ComponentRequirementRequestRepresentation( + @Assisted ContributionBinding binding, + @Assisted ComponentRequirement componentRequirement, ComponentRequirementExpressions componentRequirementExpressions) { - super(binding); - this.componentRequirement = componentRequirement; + this.componentRequirement = checkNotNull(componentRequirement); this.componentRequirementExpressions = componentRequirementExpressions; } @@ -45,4 +50,10 @@ final class ComponentRequirementBindingExpression extends SimpleInvocationBindin componentRequirement.type(), componentRequirementExpressions.getExpression(componentRequirement, requestingClass)); } + + @AssistedFactory + static interface Factory { + ComponentRequirementRequestRepresentation create( + ContributionBinding binding, ComponentRequirement componentRequirement); + } } diff --git a/java/dagger/internal/codegen/writing/DelegateBindingExpression.java b/java/dagger/internal/codegen/writing/DelegateRequestRepresentation.java index 336113264..59aa39492 100644 --- a/java/dagger/internal/codegen/writing/DelegateBindingExpression.java +++ b/java/dagger/internal/codegen/writing/DelegateRequestRepresentation.java @@ -16,15 +16,20 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.RequestKinds.requestType; import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; -import static dagger.model.BindingKind.DELEGATE; +import static dagger.spi.model.BindingKind.DELEGATE; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.Binding; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.BindsTypeChecker; @@ -32,27 +37,28 @@ import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.javapoet.Expression; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.RequestKind; +import dagger.spi.model.RequestKind; import javax.lang.model.type.TypeMirror; -/** A {@link dagger.internal.codegen.writing.BindingExpression} for {@code @Binds} methods. */ -final class DelegateBindingExpression extends BindingExpression { +/** A {@link dagger.internal.codegen.writing.RequestRepresentation} for {@code @Binds} methods. */ +final class DelegateRequestRepresentation extends RequestRepresentation { private final ContributionBinding binding; private final RequestKind requestKind; - private final ComponentBindingExpressions componentBindingExpressions; + private final ComponentRequestRepresentations componentRequestRepresentations; private final DaggerTypes types; private final BindsTypeChecker bindsTypeChecker; - DelegateBindingExpression( - ContributionBinding binding, - RequestKind requestKind, - ComponentBindingExpressions componentBindingExpressions, + @AssistedInject + DelegateRequestRepresentation( + @Assisted ContributionBinding binding, + @Assisted RequestKind requestKind, + ComponentRequestRepresentations componentRequestRepresentations, DaggerTypes types, DaggerElements elements) { this.binding = checkNotNull(binding); this.requestKind = checkNotNull(requestKind); - this.componentBindingExpressions = checkNotNull(componentBindingExpressions); - this.types = checkNotNull(types); + this.componentRequestRepresentations = componentRequestRepresentations; + this.types = types; this.bindsTypeChecker = new BindsTypeChecker(types, elements); } @@ -73,14 +79,14 @@ final class DelegateBindingExpression extends BindingExpression { @Override Expression getDependencyExpression(ClassName requestingClass) { Expression delegateExpression = - componentBindingExpressions.getDependencyExpression( + componentRequestRepresentations.getDependencyExpression( bindingRequest(getOnlyElement(binding.dependencies()).key(), requestKind), requestingClass); - TypeMirror contributedType = binding.contributedType(); + TypeMirror contributedType = toJavac(binding.contributedType()); switch (requestKind) { case INSTANCE: - return instanceRequiresCast(delegateExpression, requestingClass) + return instanceRequiresCast(binding, delegateExpression, requestingClass, bindsTypeChecker) ? delegateExpression.castTo(contributedType) : delegateExpression; default: @@ -89,12 +95,17 @@ final class DelegateBindingExpression extends BindingExpression { } } - private boolean instanceRequiresCast(Expression delegateExpression, ClassName requestingClass) { + static boolean instanceRequiresCast( + ContributionBinding binding, + Expression delegateExpression, + ClassName requestingClass, + BindsTypeChecker bindsTypeChecker) { // delegateExpression.type() could be Object if expression is satisfied with a raw // Provider's get() method. + TypeMirror contributedType = toJavac(binding.contributedType()); return !bindsTypeChecker.isAssignable( - delegateExpression.type(), binding.contributedType(), binding.contributionType()) - && isTypeAccessibleFrom(binding.contributedType(), requestingClass.packageName()); + delegateExpression.type(), contributedType, binding.contributionType()) + && isTypeAccessibleFrom(contributedType, requestingClass.packageName()); } /** @@ -110,7 +121,13 @@ final class DelegateBindingExpression extends BindingExpression { if (types.isAssignable(delegateExpression.type(), desiredType)) { return delegateExpression; } - return delegateExpression.castTo(types.erasure(desiredType)); + Expression castedExpression = delegateExpression.castTo(types.erasure(desiredType)); + // Casted raw type provider expression has to be wrapped parentheses, otherwise there + // will be an error when DerivedFromFrameworkInstanceRequestRepresentation appends a `get()` to + // it. + // TODO(wanyingd): change the logic to only add parenthesis when necessary. + return Expression.create( + castedExpression.type(), CodeBlock.of("($L)", castedExpression.codeBlock())); } private enum ScopeKind { @@ -130,4 +147,9 @@ final class DelegateBindingExpression extends BindingExpression { return this.ordinal() > other.ordinal(); } } + + @AssistedFactory + static interface Factory { + DelegateRequestRepresentation create(ContributionBinding binding, RequestKind requestKind); + } } diff --git a/java/dagger/internal/codegen/writing/DelegatingFrameworkInstanceCreationExpression.java b/java/dagger/internal/codegen/writing/DelegatingFrameworkInstanceCreationExpression.java index a7f9556d9..096d1090e 100644 --- a/java/dagger/internal/codegen/writing/DelegatingFrameworkInstanceCreationExpression.java +++ b/java/dagger/internal/codegen/writing/DelegatingFrameworkInstanceCreationExpression.java @@ -21,10 +21,14 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ContributionBinding; +import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.javapoet.CodeBlocks; import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; -import dagger.model.DependencyRequest; +import dagger.spi.model.DependencyRequest; /** A framework instance creation expression for a {@link dagger.Binds @Binds} binding. */ final class DelegatingFrameworkInstanceCreationExpression @@ -32,26 +36,33 @@ final class DelegatingFrameworkInstanceCreationExpression private final ContributionBinding binding; private final ComponentImplementation componentImplementation; - private final ComponentBindingExpressions componentBindingExpressions; + private final ComponentRequestRepresentations componentRequestRepresentations; + @AssistedInject DelegatingFrameworkInstanceCreationExpression( - ContributionBinding binding, + @Assisted ContributionBinding binding, ComponentImplementation componentImplementation, - ComponentBindingExpressions componentBindingExpressions) { + ComponentRequestRepresentations componentRequestRepresentations, + CompilerOptions compilerOptions) { this.binding = checkNotNull(binding); - this.componentImplementation = checkNotNull(componentImplementation); - this.componentBindingExpressions = checkNotNull(componentBindingExpressions); + this.componentImplementation = componentImplementation; + this.componentRequestRepresentations = componentRequestRepresentations; } @Override public CodeBlock creationExpression() { DependencyRequest dependency = getOnlyElement(binding.dependencies()); return CodeBlocks.cast( - componentBindingExpressions + componentRequestRepresentations .getDependencyExpression( bindingRequest(dependency.key(), binding.frameworkType()), - componentImplementation.name()) + componentImplementation.shardImplementation(binding).name()) .codeBlock(), - binding.frameworkType().frameworkClass()); + binding.frameworkType().frameworkClassName()); + } + + @AssistedFactory + static interface Factory { + DelegatingFrameworkInstanceCreationExpression create(ContributionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/DependencyMethodProducerCreationExpression.java b/java/dagger/internal/codegen/writing/DependencyMethodProducerCreationExpression.java index 5ac1e8f6b..64a689ea9 100644 --- a/java/dagger/internal/codegen/writing/DependencyMethodProducerCreationExpression.java +++ b/java/dagger/internal/codegen/writing/DependencyMethodProducerCreationExpression.java @@ -16,6 +16,7 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Preconditions.checkNotNull; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.anonymousClassBuilder; @@ -25,10 +26,12 @@ import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; -import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.TypeName; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ComponentRequirement; import dagger.internal.codegen.binding.ContributionBinding; @@ -47,15 +50,16 @@ final class DependencyMethodProducerCreationExpression private final ComponentRequirementExpressions componentRequirementExpressions; private final BindingGraph graph; + @AssistedInject DependencyMethodProducerCreationExpression( - ContributionBinding binding, + @Assisted ContributionBinding binding, ComponentImplementation componentImplementation, ComponentRequirementExpressions componentRequirementExpressions, BindingGraph graph) { this.binding = checkNotNull(binding); - this.componentImplementation = checkNotNull(componentImplementation); - this.componentRequirementExpressions = checkNotNull(componentRequirementExpressions); - this.graph = checkNotNull(graph); + this.componentImplementation = componentImplementation; + this.componentRequirementExpressions = componentRequirementExpressions; + this.graph = graph; } @Override @@ -64,7 +68,7 @@ final class DependencyMethodProducerCreationExpression graph.componentDescriptor().getDependencyThatDefinesMethod(binding.bindingElement().get()); FieldSpec dependencyField = FieldSpec.builder( - ClassName.get(dependency.typeElement()), dependency.variableName(), PRIVATE, FINAL) + dependency.typeElement().getClassName(), dependency.variableName(), PRIVATE, FINAL) .initializer( componentRequirementExpressions.getExpressionDuringInitialization( dependency, @@ -79,7 +83,7 @@ final class DependencyMethodProducerCreationExpression componentImplementation.name().nestedClass("Anonymous"))) .build(); // TODO(b/70395982): Explore using a private static type instead of an anonymous class. - TypeName keyType = TypeName.get(binding.key().type()); + TypeName keyType = TypeName.get(binding.key().type().java()); return CodeBlock.of( "$L", anonymousClassBuilder("") @@ -93,8 +97,13 @@ final class DependencyMethodProducerCreationExpression .addStatement( "return $N.$L()", dependencyField, - binding.bindingElement().get().getSimpleName()) + toJavac(binding.bindingElement().get()).getSimpleName()) .build()) .build()); } + + @AssistedFactory + static interface Factory { + DependencyMethodProducerCreationExpression create(ContributionBinding binding); + } } diff --git a/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java b/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java index af5d45e66..8ea3742ab 100644 --- a/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java +++ b/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java @@ -16,29 +16,35 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.XElementKt.isMethod; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; 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.javapoet.TypeNames.providerOf; import static dagger.internal.codegen.writing.ComponentImplementation.TypeSpecKind.COMPONENT_PROVISION_FACTORY; +import static dagger.internal.codegen.xprocessing.XElements.asMethod; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XMethodElement; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ComponentRequirement; -import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.compileroption.CompilerOptions; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; -import javax.lang.model.element.Element; /** * A {@link javax.inject.Provider} creation expression for a provision method on a component's @@ -48,23 +54,29 @@ import javax.lang.model.element.Element; final class DependencyMethodProviderCreationExpression implements FrameworkInstanceCreationExpression { - private final ComponentImplementation componentImplementation; + private final ShardImplementation shardImplementation; private final ComponentRequirementExpressions componentRequirementExpressions; private final CompilerOptions compilerOptions; private final BindingGraph graph; - private final ContributionBinding binding; + private final ProvisionBinding binding; + private final XMethodElement provisionMethod; + @AssistedInject DependencyMethodProviderCreationExpression( - ContributionBinding binding, + @Assisted ProvisionBinding binding, ComponentImplementation componentImplementation, ComponentRequirementExpressions componentRequirementExpressions, CompilerOptions compilerOptions, BindingGraph graph) { this.binding = checkNotNull(binding); - this.componentImplementation = checkNotNull(componentImplementation); - this.componentRequirementExpressions = checkNotNull(componentRequirementExpressions); - this.compilerOptions = checkNotNull(compilerOptions); - this.graph = checkNotNull(graph); + this.shardImplementation = componentImplementation.shardImplementation(binding); + this.componentRequirementExpressions = componentRequirementExpressions; + this.compilerOptions = compilerOptions; + this.graph = graph; + + checkArgument(binding.bindingElement().isPresent()); + checkArgument(isMethod(binding.bindingElement().get())); + provisionMethod = asMethod(binding.bindingElement().get()); } @Override @@ -76,13 +88,12 @@ final class DependencyMethodProviderCreationExpression // using .class & String.format -- but that wouldn't be the whole story. // What should we do? CodeBlock invocation = - ComponentProvisionBindingExpression.maybeCheckForNull( - (ProvisionBinding) binding, + ComponentProvisionRequestRepresentation.maybeCheckForNull( + binding, compilerOptions, - CodeBlock.of( - "$N.$N()", dependency().variableName(), provisionMethod().getSimpleName())); - ClassName dependencyClassName = ClassName.get(dependency().typeElement()); - TypeName keyType = TypeName.get(binding.key().type()); + CodeBlock.of("$N.$N()", dependency().variableName(), getSimpleName(provisionMethod))); + ClassName dependencyClassName = dependency().typeElement().getClassName(); + TypeName keyType = binding.key().type().xprocessing().getTypeName(); MethodSpec.Builder getMethod = methodBuilder("get") .addAnnotation(Override.class) @@ -90,11 +101,23 @@ final class DependencyMethodProviderCreationExpression .returns(keyType) .addStatement("return $L", invocation); if (binding.nullableType().isPresent()) { - getMethod.addAnnotation(ClassName.get(MoreTypes.asTypeElement(binding.nullableType().get()))); + getMethod.addAnnotation(binding.nullableType().get().getTypeElement().getClassName()); } - componentImplementation.addType( + + // We need to use the componentShard here since the generated type is static and shards are + // not static classes so it can't be nested inside the shard. + ShardImplementation componentShard = + shardImplementation.getComponentImplementation().getComponentShard(); + ClassName factoryClassName = + componentShard + .name() + .nestedClass( + dependency().typeElement().getQualifiedName().replace('.', '_') + + "_" + + getSimpleName(provisionMethod)); + componentShard.addType( COMPONENT_PROVISION_FACTORY, - classBuilder(factoryClassName()) + classBuilder(factoryClassName) .addSuperinterface(providerOf(keyType)) .addModifiers(PRIVATE, STATIC, FINAL) .addField(dependencyClassName, dependency().variableName(), PRIVATE, FINAL) @@ -107,24 +130,17 @@ final class DependencyMethodProviderCreationExpression .build()); return CodeBlock.of( "new $T($L)", - factoryClassName(), + factoryClassName, componentRequirementExpressions.getExpressionDuringInitialization( - dependency(), componentImplementation.name())); - } - - private ClassName factoryClassName() { - String factoryName = - ClassName.get(dependency().typeElement()).toString().replace('.', '_') - + "_" - + binding.bindingElement().get().getSimpleName(); - return componentImplementation.name().nestedClass(factoryName); + dependency(), shardImplementation.name())); } private ComponentRequirement dependency() { - return graph.componentDescriptor().getDependencyThatDefinesMethod(provisionMethod()); + return graph.componentDescriptor().getDependencyThatDefinesMethod(provisionMethod); } - private Element provisionMethod() { - return binding.bindingElement().get(); + @AssistedFactory + static interface Factory { + DependencyMethodProviderCreationExpression create(ProvisionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceBindingExpression.java b/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceBindingExpression.java deleted file mode 100644 index 6e5dca84e..000000000 --- a/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceBindingExpression.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2018 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.writing; - -import static com.google.common.base.Preconditions.checkNotNull; -import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; - -import com.squareup.javapoet.ClassName; -import dagger.internal.codegen.binding.BindingRequest; -import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; -import dagger.internal.codegen.binding.FrameworkType; -import dagger.internal.codegen.javapoet.Expression; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Key; -import dagger.model.RequestKind; - -/** A binding expression that depends on a framework instance. */ -final class DerivedFromFrameworkInstanceBindingExpression extends BindingExpression { - - private final BindingRequest frameworkRequest; - private final RequestKind requestKind; - private final FrameworkType frameworkType; - private final ComponentBindingExpressions componentBindingExpressions; - private final DaggerTypes types; - - DerivedFromFrameworkInstanceBindingExpression( - Key key, - FrameworkType frameworkType, - RequestKind requestKind, - ComponentBindingExpressions componentBindingExpressions, - DaggerTypes types) { - this.frameworkRequest = bindingRequest(key, frameworkType); - this.requestKind = checkNotNull(requestKind); - this.frameworkType = checkNotNull(frameworkType); - this.componentBindingExpressions = checkNotNull(componentBindingExpressions); - this.types = checkNotNull(types); - } - - @Override - Expression getDependencyExpression(ClassName requestingClass) { - return frameworkType.to( - requestKind, - componentBindingExpressions.getDependencyExpression(frameworkRequest, requestingClass), - types); - } - - @Override - Expression getDependencyExpressionForComponentMethod( - ComponentMethodDescriptor componentMethod, ComponentImplementation component) { - Expression frameworkInstance = - componentBindingExpressions.getDependencyExpressionForComponentMethod( - frameworkRequest, componentMethod, component); - return frameworkType.to(requestKind, frameworkInstance, types); - } -} diff --git a/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceRequestRepresentation.java b/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceRequestRepresentation.java new file mode 100644 index 000000000..79895e9a0 --- /dev/null +++ b/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceRequestRepresentation.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2018 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.writing; + +import static com.google.common.base.Preconditions.checkNotNull; +import static dagger.internal.codegen.writing.DelegateRequestRepresentation.instanceRequiresCast; + +import com.squareup.javapoet.ClassName; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.internal.codegen.binding.BindsTypeChecker; +import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; +import dagger.internal.codegen.binding.ContributionBinding; +import dagger.internal.codegen.binding.FrameworkType; +import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.spi.model.BindingKind; +import dagger.spi.model.RequestKind; + +/** A binding expression that depends on a framework instance. */ +final class DerivedFromFrameworkInstanceRequestRepresentation extends RequestRepresentation { + private final ContributionBinding binding; + private final RequestRepresentation frameworkRequestRepresentation; + private final RequestKind requestKind; + private final FrameworkType frameworkType; + private final DaggerTypes types; + private final BindsTypeChecker bindsTypeChecker; + + @AssistedInject + DerivedFromFrameworkInstanceRequestRepresentation( + @Assisted ContributionBinding binding, + @Assisted RequestRepresentation frameworkRequestRepresentation, + @Assisted RequestKind requestKind, + @Assisted FrameworkType frameworkType, + DaggerTypes types, + DaggerElements elements) { + this.binding = binding; + this.frameworkRequestRepresentation = checkNotNull(frameworkRequestRepresentation); + this.requestKind = requestKind; + this.frameworkType = checkNotNull(frameworkType); + this.types = types; + this.bindsTypeChecker = new BindsTypeChecker(types, elements); + } + + @Override + Expression getDependencyExpression(ClassName requestingClass) { + Expression expression = + frameworkType.to( + requestKind, + frameworkRequestRepresentation.getDependencyExpression(requestingClass), + types); + return requiresTypeCast(expression, requestingClass) + ? expression.castTo(binding.contributedType()) + : expression; + } + + @Override + Expression getDependencyExpressionForComponentMethod( + ComponentMethodDescriptor componentMethod, ComponentImplementation component) { + Expression expression = + frameworkType.to( + requestKind, + frameworkRequestRepresentation.getDependencyExpressionForComponentMethod( + componentMethod, component), + types); + return requiresTypeCast(expression, component.name()) + ? expression.castTo(binding.contributedType()) + : expression; + } + + private boolean requiresTypeCast(Expression expression, ClassName requestingClass) { + return binding.kind().equals(BindingKind.DELEGATE) + && requestKind.equals(RequestKind.INSTANCE) + && instanceRequiresCast(binding, expression, requestingClass, bindsTypeChecker); + } + + @AssistedFactory + static interface Factory { + DerivedFromFrameworkInstanceRequestRepresentation create( + ContributionBinding binding, + RequestRepresentation frameworkRequestRepresentation, + RequestKind requestKind, + FrameworkType frameworkType); + } +} diff --git a/java/dagger/internal/codegen/writing/DirectInstanceBindingRepresentation.java b/java/dagger/internal/codegen/writing/DirectInstanceBindingRepresentation.java new file mode 100644 index 000000000..c90d81348 --- /dev/null +++ b/java/dagger/internal/codegen/writing/DirectInstanceBindingRepresentation.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2021 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.writing; + +import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; +import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.internal.codegen.binding.BindingGraph; +import dagger.internal.codegen.binding.BindingRequest; +import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; +import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; +import dagger.spi.model.RequestKind; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** Returns request representation based on a direct instance expression. */ +final class DirectInstanceBindingRepresentation { + private final ProvisionBinding binding; + private final BindingGraph graph; + private final ComponentImplementation componentImplementation; + private final ComponentMethodRequestRepresentation.Factory + componentMethodRequestRepresentationFactory; + private final ImmediateFutureRequestRepresentation.Factory + immediateFutureRequestRepresentationFactory; + private final PrivateMethodRequestRepresentation.Factory + privateMethodRequestRepresentationFactory; + private final UnscopedDirectInstanceRequestRepresentationFactory + unscopedDirectInstanceRequestRepresentationFactory; + private final Map<BindingRequest, RequestRepresentation> requestRepresentations = new HashMap<>(); + + @AssistedInject + DirectInstanceBindingRepresentation( + @Assisted ProvisionBinding binding, + BindingGraph graph, + ComponentImplementation componentImplementation, + ComponentMethodRequestRepresentation.Factory componentMethodRequestRepresentationFactory, + ImmediateFutureRequestRepresentation.Factory immediateFutureRequestRepresentationFactory, + PrivateMethodRequestRepresentation.Factory privateMethodRequestRepresentationFactory, + UnscopedDirectInstanceRequestRepresentationFactory + unscopedDirectInstanceRequestRepresentationFactory) { + this.binding = binding; + this.graph = graph; + this.componentImplementation = componentImplementation; + this.componentMethodRequestRepresentationFactory = componentMethodRequestRepresentationFactory; + this.immediateFutureRequestRepresentationFactory = immediateFutureRequestRepresentationFactory; + this.privateMethodRequestRepresentationFactory = privateMethodRequestRepresentationFactory; + this.unscopedDirectInstanceRequestRepresentationFactory = + unscopedDirectInstanceRequestRepresentationFactory; + } + + public RequestRepresentation getRequestRepresentation(BindingRequest request) { + return reentrantComputeIfAbsent( + requestRepresentations, request, this::getRequestRepresentationUncached); + } + + private RequestRepresentation getRequestRepresentationUncached(BindingRequest request) { + switch (request.requestKind()) { + case INSTANCE: + return requiresMethodEncapsulation(binding) + ? wrapInMethod(unscopedDirectInstanceRequestRepresentationFactory.create(binding)) + : unscopedDirectInstanceRequestRepresentationFactory.create(binding); + + case FUTURE: + return immediateFutureRequestRepresentationFactory.create( + getRequestRepresentation(bindingRequest(binding.key(), RequestKind.INSTANCE)), + binding.key().type().java()); + + default: + throw new AssertionError( + String.format("Invalid binding request kind: %s", request.requestKind())); + } + } + + /** + * Returns a binding expression that uses a given one as the body of a method that users call. If + * a component provision method matches it, it will be the method implemented. If it does not + * match a component provision method and the binding is modifiable, then a new public modifiable + * binding method will be written. If the binding doesn't match a component method and is not + * modifiable, then a new private method will be written. + */ + RequestRepresentation wrapInMethod(RequestRepresentation bindingExpression) { + // If we've already wrapped the expression, then use the delegate. + if (bindingExpression instanceof MethodRequestRepresentation) { + return bindingExpression; + } + + BindingRequest request = bindingRequest(binding.key(), RequestKind.INSTANCE); + Optional<ComponentMethodDescriptor> matchingComponentMethod = + graph.componentDescriptor().firstMatchingComponentMethod(request); + + ShardImplementation shardImplementation = componentImplementation.shardImplementation(binding); + + // Consider the case of a request from a component method like: + // + // DaggerMyComponent extends MyComponent { + // @Overrides + // Foo getFoo() { + // <FOO_BINDING_REQUEST> + // } + // } + // + // Normally, in this case we would return a ComponentMethodRequestRepresentation rather than a + // PrivateMethodRequestRepresentation so that #getFoo() can inline the implementation rather + // than + // create an unnecessary private method and return that. However, with sharding we don't want to + // inline the implementation because that would defeat some of the class pool savings if those + // fields had to communicate across shards. Thus, when a key belongs to a separate shard use a + // PrivateMethodRequestRepresentation and put the private method in the shard. + if (matchingComponentMethod.isPresent() && shardImplementation.isComponentShard()) { + ComponentMethodDescriptor componentMethod = matchingComponentMethod.get(); + return componentMethodRequestRepresentationFactory.create(bindingExpression, componentMethod); + } else { + return privateMethodRequestRepresentationFactory.create(request, binding, bindingExpression); + } + } + + private static boolean requiresMethodEncapsulation(ProvisionBinding binding) { + switch (binding.kind()) { + case COMPONENT: + case COMPONENT_PROVISION: + case SUBCOMPONENT_CREATOR: + case COMPONENT_DEPENDENCY: + case MULTIBOUND_SET: + case MULTIBOUND_MAP: + case BOUND_INSTANCE: + case ASSISTED_FACTORY: + case ASSISTED_INJECTION: + case INJECTION: + case PROVISION: + // These binding kinds satify a binding request with a component method or a private + // method when the requested binding has dependencies. The method will wrap the logic of + // creating the binding instance. Without the encapsulation, we might see many levels of + // nested instance creation code in a single statement to satisfy all dependencies of a + // binding request. + return !binding.dependencies().isEmpty(); + case MEMBERS_INJECTOR: + case PRODUCTION: + case COMPONENT_PRODUCTION: + case OPTIONAL: + case DELEGATE: + case MEMBERS_INJECTION: + return false; + } + throw new AssertionError(String.format("No such binding kind: %s", binding.kind())); + } + + @AssistedFactory + static interface Factory { + DirectInstanceBindingRepresentation create(ProvisionBinding binding); + } +} diff --git a/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviderDependencyRepresentation.java b/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviderDependencyRepresentation.java new file mode 100644 index 000000000..817f76b87 --- /dev/null +++ b/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviderDependencyRepresentation.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2021 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.writing; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessible; +import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; + +import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.internal.codegen.base.ContributionType; +import dagger.internal.codegen.binding.BindsTypeChecker; +import dagger.internal.codegen.binding.FrameworkType; +import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; +import dagger.spi.model.BindingKind; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.RequestKind; +import javax.lang.model.type.TypeMirror; + +/** + * Returns type casted expressions to satisfy dependency requests from experimental switching + * providers. + */ +final class ExperimentalSwitchingProviderDependencyRepresentation { + private final ProvisionBinding binding; + private final ShardImplementation shardImplementation; + private final BindsTypeChecker bindsTypeChecker; + private final DaggerTypes types; + private final DaggerElements elements; + private final TypeMirror type; + + @AssistedInject + ExperimentalSwitchingProviderDependencyRepresentation( + @Assisted ProvisionBinding binding, + ComponentImplementation componentImplementation, + DaggerTypes types, + DaggerElements elements) { + this.binding = binding; + this.shardImplementation = componentImplementation.shardImplementation(binding); + this.types = types; + this.elements = elements; + this.bindsTypeChecker = new BindsTypeChecker(types, elements); + this.type = + isDelegateSetValuesBinding() + ? types.erasure(elements.getTypeElement(TypeNames.COLLECTION).asType()) + : toJavac(binding.contributedType()); + } + + Expression getDependencyExpression(RequestKind requestKind, ProvisionBinding requestingBinding) { + int index = findIndexOfDependency(requestingBinding); + TypeMirror frameworkType = + types.getDeclaredType(elements.getTypeElement(FrameworkType.PROVIDER.frameworkClassName())); + Expression expression = + FrameworkType.PROVIDER.to( + requestKind, + Expression.create( + frameworkType, CodeBlock.of("(($T) dependencies[$L])", frameworkType, index)), + types); + if (usesExplicitTypeCast(expression, requestKind)) { + return expression.castTo(type); + } + if (usesErasedTypeCast(requestKind)) { + return expression.castTo(types.erasure(type)); + } + return expression; + } + + private int findIndexOfDependency(ProvisionBinding requestingBinding) { + return requestingBinding.dependencies().stream() + .map(DependencyRequest::key) + .collect(toImmutableList()) + .indexOf(binding.key()) + + (requestingBinding.requiresModuleInstance() + && requestingBinding.contributingModule().isPresent() + ? 1 + : 0); + } + + private boolean isDelegateSetValuesBinding() { + return binding.kind().equals(BindingKind.DELEGATE) + && binding.contributionType().equals(ContributionType.SET_VALUES); + } + + private boolean usesExplicitTypeCast(Expression expression, RequestKind requestKind) { + // If the type is accessible, we can directly cast the expression use the type. + return requestKind.equals(RequestKind.INSTANCE) + && !bindsTypeChecker.isAssignable(expression.type(), type, binding.contributionType()) + && isTypeAccessibleFrom(type, shardImplementation.name().packageName()); + } + + private boolean usesErasedTypeCast(RequestKind requestKind) { + // If a type has inaccessible type arguments, then cast to raw type. + return requestKind.equals(RequestKind.INSTANCE) + && !isTypeAccessibleFrom(type, shardImplementation.name().packageName()) + && isRawTypeAccessible(type, shardImplementation.name().packageName()); + } + + @AssistedFactory + static interface Factory { + ExperimentalSwitchingProviderDependencyRepresentation create(ProvisionBinding binding); + } +} diff --git a/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviders.java b/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviders.java new file mode 100644 index 000000000..679be6b41 --- /dev/null +++ b/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviders.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2022 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.writing; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.getLast; +import static com.google.common.collect.Iterables.getOnlyElement; +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.javapoet.AnnotationSpecs.Suppression.UNCHECKED; +import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings; +import static dagger.internal.codegen.javapoet.TypeNames.providerOf; +import static javax.lang.model.element.Modifier.FINAL; +import static javax.lang.model.element.Modifier.PRIVATE; +import static javax.lang.model.element.Modifier.PUBLIC; +import static javax.lang.model.element.Modifier.STATIC; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeVariableName; +import dagger.internal.codegen.base.UniqueNameSet; +import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.javapoet.CodeBlocks; +import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; +import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; +import dagger.spi.model.Key; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; +import javax.inject.Inject; +import javax.lang.model.type.TypeMirror; + +/** + * Keeps track of all provider expression requests for a component. + * + * <p>The provider expression request will be satisfied by a single generated {@code Provider} class + * that can provide instances for all types by switching on an id. + */ +@PerComponentImplementation +final class ExperimentalSwitchingProviders { + /** + * 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; + private static final TypeVariableName T = TypeVariableName.get("T"); + + /** + * 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. + */ + private final Map<Key, SwitchingProviderBuilder> switchingProviderBuilders = + new LinkedHashMap<>(); + + private final ShardImplementation componentShard; + private final DaggerTypes types; + private final UniqueNameSet switchingProviderNames = new UniqueNameSet(); + private final ComponentRequestRepresentations componentRequestRepresentations; + private final ComponentImplementation componentImplementation; + + @Inject + ExperimentalSwitchingProviders( + ComponentImplementation componentImplementation, + ComponentRequestRepresentations componentRequestRepresentations, + DaggerTypes types) { + this.componentShard = checkNotNull(componentImplementation).getComponentShard(); + this.componentRequestRepresentations = componentRequestRepresentations; + this.componentImplementation = componentImplementation; + this.types = checkNotNull(types); + } + + /** Returns the framework instance creation expression for an inner switching provider class. */ + FrameworkInstanceCreationExpression newFrameworkInstanceCreationExpression( + ProvisionBinding binding, RequestRepresentation unscopedInstanceRequestRepresentation) { + return new FrameworkInstanceCreationExpression() { + @Override + public CodeBlock creationExpression() { + return switchingProviderBuilders + .computeIfAbsent(binding.key(), key -> getSwitchingProviderBuilder()) + .getNewInstanceCodeBlock(binding, unscopedInstanceRequestRepresentation); + } + }; + } + + private SwitchingProviderBuilder getSwitchingProviderBuilder() { + if (switchingProviderBuilders.size() % MAX_CASES_PER_CLASS == 0) { + String name = switchingProviderNames.getUniqueName("SwitchingProvider"); + // TODO(wanyingd): move Switching Providers and injection methods to Shard classes to avoid + // exceeding component class constant pool limit. + SwitchingProviderBuilder switchingProviderBuilder = + new SwitchingProviderBuilder(componentShard.name().nestedClass(name)); + componentShard.addTypeSupplier(switchingProviderBuilder::build); + return switchingProviderBuilder; + } + return getLast(switchingProviderBuilders.values()); + } + + // TODO(bcorso): Consider just merging this class with ExperimentalSwitchingProviders. + private final class SwitchingProviderBuilder { + // Keep the switch cases ordered by switch id. The switch Ids are assigned in pre-order + // traversal, but the switch cases are assigned in post-order traversal of the binding graph. + private final Map<Integer, CodeBlock> switchCases = new TreeMap<>(); + private final Map<Key, Integer> switchIds = new HashMap<>(); + private final ClassName switchingProviderType; + + SwitchingProviderBuilder(ClassName switchingProviderType) { + this.switchingProviderType = checkNotNull(switchingProviderType); + } + + private CodeBlock getNewInstanceCodeBlock( + ProvisionBinding binding, RequestRepresentation unscopedInstanceRequestRepresentation) { + Key key = binding.key(); + if (!switchIds.containsKey(key)) { + int switchId = switchIds.size(); + switchIds.put(key, switchId); + switchCases.put( + switchId, createSwitchCaseCodeBlock(key, unscopedInstanceRequestRepresentation)); + } + + ShardImplementation shardImplementation = + componentImplementation.shardImplementation(binding); + CodeBlock switchingProviderDependencies; + switch (binding.kind()) { + // TODO(wanyingd): there might be a better way to get component requirement information + // without using unscopedInstanceRequestRepresentation. + case COMPONENT_PROVISION: + switchingProviderDependencies = + ((ComponentProvisionRequestRepresentation) unscopedInstanceRequestRepresentation) + .getComponentRequirementExpression(shardImplementation.name()); + break; + case SUBCOMPONENT_CREATOR: + switchingProviderDependencies = + ((SubcomponentCreatorRequestRepresentation) unscopedInstanceRequestRepresentation) + .getDependencyExpressionArguments(); + break; + case MULTIBOUND_SET: + case MULTIBOUND_MAP: + case OPTIONAL: + case INJECTION: + case PROVISION: + case ASSISTED_FACTORY: + // Arguments built in the order of module reference, provision dependencies and members + // injection dependencies + switchingProviderDependencies = + componentRequestRepresentations.getCreateMethodArgumentsCodeBlock( + binding, shardImplementation.name()); + break; + default: + throw new IllegalArgumentException("Unexpected binding kind: " + binding.kind()); + } + + TypeMirror castedType = + shardImplementation.accessibleType(toJavac(binding.contributedType())); + return CodeBlock.of( + "new $T<$L>($L)", + switchingProviderType, + // Add the type parameter explicitly when the binding is scoped because Java can't resolve + // the type when wrapped. For example, the following will error: + // fooProvider = DoubleCheck.provider(new SwitchingProvider<>(1)); + CodeBlock.of("$T", castedType), + switchingProviderDependencies.isEmpty() + ? CodeBlock.of("$L", switchIds.get(key)) + : CodeBlock.of("$L, $L", switchIds.get(key), switchingProviderDependencies)); + } + + private CodeBlock createSwitchCaseCodeBlock( + Key key, RequestRepresentation unscopedInstanceRequestRepresentation) { + // TODO(bcorso): Try to delay calling getDependencyExpression() until we are writing out the + // SwitchingProvider because calling it here makes FrameworkFieldInitializer think there's a + // cycle when initializing ExperimentalSwitchingProviders which adds an unnecessary + // DelegateFactory. + CodeBlock instanceCodeBlock = + unscopedInstanceRequestRepresentation + .getDependencyExpression(switchingProviderType) + .box(types) + .codeBlock(); + + return CodeBlock.builder() + // TODO(bcorso): Is there something else more useful than the key? + .add("case $L: // $L \n", switchIds.get(key), key) + .addStatement("return ($T) $L", T, instanceCodeBlock) + .build(); + } + + private TypeSpec build() { + TypeSpec.Builder builder = + classBuilder(switchingProviderType) + .addModifiers(PRIVATE, FINAL, STATIC) + .addTypeVariable(T) + .addSuperinterface(providerOf(T)) + .addMethods(getMethods()); + + // The SwitchingProvider constructor lists switch id first and then the dependency array. + MethodSpec.Builder constructor = MethodSpec.constructorBuilder(); + builder.addField(TypeName.INT, "id", PRIVATE, FINAL); + constructor.addParameter(TypeName.INT, "id").addStatement("this.id = id"); + // Pass in provision dependencies and members injection dependencies. + builder.addField(Object[].class, "dependencies", FINAL, PRIVATE); + constructor + .addParameter(Object[].class, "dependencies") + .addStatement("this.dependencies = dependencies") + .varargs(true); + + return builder.addMethod(constructor.build()).build(); + } + + private ImmutableList<MethodSpec> getMethods() { + ImmutableList<CodeBlock> switchCodeBlockPartitions = switchCodeBlockPartitions(); + if (switchCodeBlockPartitions.size() == 1) { + // There are less than MAX_CASES_PER_SWITCH cases, so no need for extra get methods. + return ImmutableList.of( + methodBuilder("get") + .addModifiers(PUBLIC) + .addAnnotation(suppressWarnings(UNCHECKED)) + .addAnnotation(Override.class) + .returns(T) + .addCode(getOnlyElement(switchCodeBlockPartitions)) + .build()); + } + + // This is the main public "get" method that will route to private getter methods. + MethodSpec.Builder routerMethod = + methodBuilder("get") + .addModifiers(PUBLIC) + .addAnnotation(Override.class) + .returns(T) + .beginControlFlow("switch (id / $L)", MAX_CASES_PER_SWITCH); + + ImmutableList.Builder<MethodSpec> getMethods = ImmutableList.builder(); + for (int i = 0; i < switchCodeBlockPartitions.size(); i++) { + MethodSpec method = + methodBuilder("get" + i) + .addModifiers(PRIVATE) + .addAnnotation(suppressWarnings(UNCHECKED)) + .returns(T) + .addCode(switchCodeBlockPartitions.get(i)) + .build(); + getMethods.add(method); + routerMethod.addStatement("case $L: return $N()", i, method); + } + + routerMethod.addStatement("default: throw new $T(id)", AssertionError.class).endControlFlow(); + + return getMethods.add(routerMethod.build()).build(); + } + + private ImmutableList<CodeBlock> switchCodeBlockPartitions() { + return Lists.partition(ImmutableList.copyOf(switchCases.values()), MAX_CASES_PER_SWITCH) + .stream() + .map( + partitionCases -> + CodeBlock.builder() + .beginControlFlow("switch (id)") + .add(CodeBlocks.concat(partitionCases)) + .addStatement("default: throw new $T(id)", AssertionError.class) + .endControlFlow() + .build()) + .collect(toImmutableList()); + } + } +} diff --git a/java/dagger/internal/codegen/writing/FactoryGenerator.java b/java/dagger/internal/codegen/writing/FactoryGenerator.java index 03ad27cbb..ea83778b0 100644 --- a/java/dagger/internal/codegen/writing/FactoryGenerator.java +++ b/java/dagger/internal/codegen/writing/FactoryGenerator.java @@ -17,35 +17,41 @@ package dagger.internal.codegen.writing; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.Maps.transformValues; 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.binding.AssistedInjectionAnnotations.assistedParameters; -import static dagger.internal.codegen.binding.ContributionBinding.FactoryCreationStrategy.DELEGATE; -import static dagger.internal.codegen.binding.ContributionBinding.FactoryCreationStrategy.SINGLETON_INSTANCE; import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementTypeVariableNames; import static dagger.internal.codegen.binding.SourceFiles.frameworkFieldUsages; import static dagger.internal.codegen.binding.SourceFiles.frameworkTypeUsageStatement; import static dagger.internal.codegen.binding.SourceFiles.generateBindingFieldsForDependencies; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; import static dagger.internal.codegen.binding.SourceFiles.parameterizedGeneratedTypeNameForBinding; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.internal.codegen.extension.DaggerStreams.presentValues; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings; import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; import static dagger.internal.codegen.javapoet.TypeNames.factoryOf; import static dagger.internal.codegen.writing.GwtCompatibility.gwtIncompatibleAnnotation; -import static dagger.model.BindingKind.PROVISION; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.spi.model.BindingKind.INJECTION; +import static dagger.spi.model.BindingKind.PROVISION; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.compat.XConverters; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; +import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; @@ -56,22 +62,27 @@ import com.squareup.javapoet.TypeSpec; import dagger.internal.Factory; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.base.UniqueNameSet; +import dagger.internal.codegen.binding.Binding; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.compileroption.CompilerOptions; -import dagger.internal.codegen.javapoet.CodeBlocks; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod; import dagger.internal.codegen.writing.InjectionMethods.ProvisionMethod; -import dagger.model.BindingKind; -import dagger.model.DependencyRequest; +import dagger.spi.model.BindingKind; +import dagger.spi.model.DaggerAnnotation; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; +import dagger.spi.model.Scope; import java.util.List; +import java.util.Map; import java.util.Optional; -import javax.annotation.processing.Filer; +import java.util.stream.Stream; import javax.inject.Inject; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; /** * Generates {@link Factory} implementations from {@link ProvisionBinding} instances for {@link @@ -84,7 +95,7 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding @Inject FactoryGenerator( - Filer filer, + XFiler filer, SourceVersion sourceVersion, DaggerTypes types, DaggerElements elements, @@ -97,7 +108,7 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding } @Override - public Element originatingElement(ProvisionBinding binding) { + public XElement originatingElement(ProvisionBinding binding) { // we only create factories for bindings that have a binding element return binding.bindingElement().get(); } @@ -108,7 +119,7 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding checkArgument(!binding.unresolved().isPresent()); checkArgument(binding.bindingElement().isPresent()); - if (binding.factoryCreationStrategy().equals(DELEGATE)) { + if (binding.kind() == BindingKind.DELEGATE) { return ImmutableList.of(); } @@ -121,6 +132,13 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding .addModifiers(PUBLIC, FINAL) .addTypeVariables(bindingTypeElementTypeVariableNames(binding)); + if (binding.kind() == BindingKind.INJECTION + || binding.kind() == BindingKind.ASSISTED_INJECTION + || binding.kind() == BindingKind.PROVISION) { + factoryBuilder.addAnnotation(scopeMetadataAnnotation(binding)); + factoryBuilder.addAnnotation(qualifierMetadataAnnotation(binding)); + } + factoryTypeName(binding).ifPresent(factoryBuilder::addSuperinterface); addConstructorAndFields(binding, factoryBuilder); factoryBuilder.addMethod(getMethod(binding)); @@ -133,7 +151,7 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding } private void addConstructorAndFields(ProvisionBinding binding, TypeSpec.Builder factoryBuilder) { - if (binding.factoryCreationStrategy().equals(SINGLETON_INSTANCE)) { + if (FactoryCreationStrategy.of(binding) == FactoryCreationStrategy.SINGLETON_INSTANCE) { return; } // TODO(bcorso): Make the constructor private? @@ -157,7 +175,7 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding private Optional<ParameterSpec> moduleParameter(ProvisionBinding binding) { if (binding.requiresModuleInstance()) { // TODO(bcorso, dpb): Should this use contributingModule()? - TypeName type = TypeName.get(binding.bindingTypeElement().get().asType()); + TypeName type = binding.bindingTypeElement().get().getType().getTypeName(); return Optional.of(ParameterSpec.builder(type, "module").build()); } return Optional.empty(); @@ -167,13 +185,16 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding UniqueNameSet uniqueFieldNames = new UniqueNameSet(); // TODO(bcorso, dpb): Add a test for the case when a Factory parameter is named "module". moduleParameter(binding).ifPresent(module -> uniqueFieldNames.claim(module.name)); - return ImmutableMap.copyOf( - transformValues( - generateBindingFieldsForDependencies(binding), - field -> + // We avoid Maps.transformValues here because it would implicitly depend on the order in which + // the transform function is evaluated on each entry in the map. + ImmutableMap.Builder<DependencyRequest, FieldSpec> builder = ImmutableMap.builder(); + generateBindingFieldsForDependencies(binding).forEach( + (dependency, field) -> + builder.put(dependency, FieldSpec.builder( field.type(), uniqueFieldNames.getUniqueName(field.name()), PRIVATE, FINAL) .build())); + return builder.build(); } private void addCreateMethod(ProvisionBinding binding, TypeSpec.Builder factoryBuilder) { @@ -186,7 +207,7 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding .returns(parameterizedGeneratedTypeNameForBinding(binding)) .addTypeVariables(bindingTypeElementTypeVariableNames(binding)); - switch (binding.factoryCreationStrategy()) { + switch (FactoryCreationStrategy.of(binding)) { case SINGLETON_INSTANCE: FieldSpec.Builder instanceFieldBuilder = FieldSpec.builder( @@ -223,28 +244,36 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding } private MethodSpec getMethod(ProvisionBinding binding) { + UniqueNameSet uniqueFieldNames = new UniqueNameSet(); + ImmutableMap<DependencyRequest, FieldSpec> frameworkFields = frameworkFields(binding); + frameworkFields.values().forEach(field -> uniqueFieldNames.claim(field.name)); + Map<VariableElement, ParameterSpec> assistedParameters = + assistedParameters(binding).stream() + .collect( + toImmutableMap( + XConverters::toJavac, + element -> + ParameterSpec.builder( + element.getType().getTypeName(), + uniqueFieldNames.getUniqueName(getSimpleName(element))) + .build())); TypeName providedTypeName = providedTypeName(binding); MethodSpec.Builder getMethod = methodBuilder("get") .addModifiers(PUBLIC) .returns(providedTypeName) - .addParameters( - // The 'get' method for an assisted injection type takes in the assisted parameters. - assistedParameters(binding).stream() - .map(ParameterSpec::get) - .collect(toImmutableList())); + .addParameters(assistedParameters.values()); if (factoryTypeName(binding).isPresent()) { getMethod.addAnnotation(Override.class); } - - ImmutableMap<DependencyRequest, FieldSpec> frameworkFields = frameworkFields(binding); CodeBlock invokeNewInstance = ProvisionMethod.invoke( binding, request -> frameworkTypeUsageStatement( CodeBlock.of("$N", frameworkFields.get(request)), request.kind()), + param -> assistedParameters.get(param).name, generatedClassNameForBinding(binding), moduleParameter(binding).map(module -> CodeBlock.of("$N", module)), compilerOptions, @@ -253,7 +282,9 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding if (binding.kind().equals(PROVISION)) { binding .nullableType() - .ifPresent(nullableType -> CodeBlocks.addAnnotation(getMethod, nullableType)); + .map(XType::getTypeElement) + .map(XTypeElement::getClassName) + .ifPresent(getMethod::addAnnotation); getMethod.addStatement("return $L", invokeNewInstance); } else if (!binding.injectionSites().isEmpty()) { CodeBlock instance = CodeBlock.of("instance"); @@ -264,7 +295,7 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding binding.injectionSites(), generatedClassNameForBinding(binding), instance, - binding.key().type(), + binding.key().type().java(), frameworkFieldUsages(binding.dependencies(), frameworkFields)::get, types, metadataUtil)) @@ -275,8 +306,33 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding return getMethod.build(); } + private AnnotationSpec scopeMetadataAnnotation(ProvisionBinding binding) { + AnnotationSpec.Builder builder = AnnotationSpec.builder(TypeNames.SCOPE_METADATA); + binding.scope() + .map(Scope::scopeAnnotation) + .map(DaggerAnnotation::className) + .map(ClassName::canonicalName) + .ifPresent(scopeCanonicalName -> builder.addMember("value", "$S", scopeCanonicalName)); + return builder.build(); + } + + private AnnotationSpec qualifierMetadataAnnotation(ProvisionBinding binding) { + AnnotationSpec.Builder builder = AnnotationSpec.builder(TypeNames.QUALIFIER_METADATA); + // Collect all qualifiers on the binding itself or its dependencies + Stream.concat( + Stream.of(binding.key()), + binding.provisionDependencies().stream().map(DependencyRequest::key)) + .map(Key::qualifier) + .flatMap(presentValues()) + .map(DaggerAnnotation::className) + .map(ClassName::canonicalName) + .distinct() + .forEach(qualifier -> builder.addMember("value", "$S", qualifier)); + return builder.build(); + } + private static TypeName providedTypeName(ProvisionBinding binding) { - return TypeName.get(binding.contributedType()); + return binding.contributedType().getTypeName(); } private static Optional<TypeName> factoryTypeName(ProvisionBinding binding) { @@ -288,4 +344,31 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding private static ParameterSpec toParameter(FieldSpec field) { return ParameterSpec.builder(field.type, field.name).build(); } + + /** The strategy for getting an instance of a factory for a {@link Binding}. */ + private enum FactoryCreationStrategy { + /** The factory class is a single instance. */ + SINGLETON_INSTANCE, + /** The factory must be created by calling the constructor. */ + CLASS_CONSTRUCTOR; + + static FactoryCreationStrategy of(Binding binding) { + switch (binding.kind()) { + case DELEGATE: + throw new AssertionError("Delegate bindings don't have a factory."); + case PROVISION: + return binding.dependencies().isEmpty() && !binding.requiresModuleInstance() + ? SINGLETON_INSTANCE + : CLASS_CONSTRUCTOR; + case INJECTION: + case MULTIBOUND_SET: + case MULTIBOUND_MAP: + return binding.dependencies().isEmpty() + ? SINGLETON_INSTANCE + : CLASS_CONSTRUCTOR; + default: + return CLASS_CONSTRUCTOR; + } + } + } } diff --git a/java/dagger/internal/codegen/writing/FrameworkFieldInitializer.java b/java/dagger/internal/codegen/writing/FrameworkFieldInitializer.java index c17ff8e53..811866bcd 100644 --- a/java/dagger/internal/codegen/writing/FrameworkFieldInitializer.java +++ b/java/dagger/internal/codegen/writing/FrameworkFieldInitializer.java @@ -34,8 +34,9 @@ import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.FrameworkField; import dagger.internal.codegen.javapoet.AnnotationSpecs; import dagger.internal.codegen.javapoet.TypeNames; -import dagger.model.BindingKind; -import dagger.producers.internal.DelegateProducer; +import dagger.internal.codegen.writing.ComponentImplementation.CompilerMode; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; +import dagger.spi.model.BindingKind; import java.util.Optional; /** @@ -59,23 +60,12 @@ class FrameworkFieldInitializer implements FrameworkInstanceSupplier { default Optional<ClassName> alternativeFrameworkClass() { return Optional.empty(); } - - /** - * Returns {@code true} if instead of using {@link #creationExpression()} to create a framework - * instance, a case in {@link InnerSwitchingProviders} should be created for this binding. - */ - // TODO(ronshapiro): perhaps this isn't the right approach. Instead of saying "Use - // SetFactory.EMPTY because you will only get 1 class for all types of bindings that use - // SetFactory", maybe we should still use an inner switching provider but the same switching - // provider index for all cases. - default boolean useInnerSwitchingProvider() { - return true; - } } - private final ComponentImplementation componentImplementation; + private final ShardImplementation shardImplementation; private final ContributionBinding binding; private final FrameworkInstanceCreationExpression frameworkInstanceCreationExpression; + private final CompilerMode compilerMode; private FieldSpec fieldSpec; private InitializationState fieldInitializationState = InitializationState.UNINITIALIZED; @@ -83,8 +73,9 @@ class FrameworkFieldInitializer implements FrameworkInstanceSupplier { ComponentImplementation componentImplementation, ContributionBinding binding, FrameworkInstanceCreationExpression frameworkInstanceCreationExpression) { - this.componentImplementation = checkNotNull(componentImplementation); this.binding = checkNotNull(binding); + this.compilerMode = componentImplementation.compilerMode(); + this.shardImplementation = checkNotNull(componentImplementation).shardImplementation(binding); this.frameworkInstanceCreationExpression = checkNotNull(frameworkInstanceCreationExpression); } @@ -95,14 +86,14 @@ class FrameworkFieldInitializer implements FrameworkInstanceSupplier { @Override public final MemberSelect memberSelect() { initializeField(); - return MemberSelect.localField(componentImplementation.name(), checkNotNull(fieldSpec).name); + return MemberSelect.localField(shardImplementation, checkNotNull(fieldSpec).name); } /** Adds the field and its initialization code to the component. */ private void initializeField() { switch (fieldInitializationState) { case UNINITIALIZED: - // Change our state in case we are recursively invoked via initializeBindingExpression + // Change our state in case we are recursively invoked via initializeRequestRepresentation fieldInitializationState = InitializationState.INITIALIZING; CodeBlock.Builder codeBuilder = CodeBlock.builder(); CodeBlock fieldInitialization = frameworkInstanceCreationExpression.creationExpression(); @@ -114,16 +105,24 @@ class FrameworkFieldInitializer implements FrameworkInstanceSupplier { } else { codeBuilder.add(initCode); } - componentImplementation.addInitialization(codeBuilder.build()); + shardImplementation.addInitialization(codeBuilder.build()); fieldInitializationState = InitializationState.INITIALIZED; break; case INITIALIZING: - // We were recursively invoked, so create a delegate factory instead + fieldSpec = getOrCreateField(); + // We were recursively invoked, so create a delegate factory instead to break the loop. + // However, because SwitchingProvider takes no dependencies, even if they are recursively + // invoked, we don't need to delegate it since there is no dependency cycle. + if (FrameworkInstanceKind.from(binding, compilerMode) + .equals(FrameworkInstanceKind.SWITCHING_PROVIDER)) { + break; + } + fieldInitializationState = InitializationState.DELEGATED; - componentImplementation.addInitialization( - CodeBlock.of("this.$N = new $T<>();", getOrCreateField(), delegateType())); + shardImplementation.addInitialization( + CodeBlock.of("this.$N = new $T<>();", fieldSpec, delegateType())); break; case DELEGATED: @@ -140,7 +139,7 @@ class FrameworkFieldInitializer implements FrameworkInstanceSupplier { if (fieldSpec != null) { return fieldSpec; } - boolean useRawType = !componentImplementation.isTypeAccessible(binding.key().type()); + boolean useRawType = !shardImplementation.isTypeAccessible(binding.key().type().java()); FrameworkField contributionBindingField = FrameworkField.forBinding( binding, frameworkInstanceCreationExpression.alternativeFrameworkClass()); @@ -152,7 +151,7 @@ class FrameworkFieldInitializer implements FrameworkInstanceSupplier { // An assisted injection factory doesn't extend Provider, so we reference the generated // factory type directly (i.e. Foo_Factory<T> instead of Provider<Foo<T>>). TypeName[] typeParameters = - MoreTypes.asDeclared(binding.key().type()).getTypeArguments().stream() + MoreTypes.asDeclared(binding.key().type().java()).getTypeArguments().stream() .map(TypeName::get) .toArray(TypeName[]::new); fieldType = @@ -163,20 +162,20 @@ class FrameworkFieldInitializer implements FrameworkInstanceSupplier { FieldSpec.Builder contributionField = FieldSpec.builder( - fieldType, componentImplementation.getUniqueFieldName(contributionBindingField.name())); + fieldType, shardImplementation.getUniqueFieldName(contributionBindingField.name())); contributionField.addModifiers(PRIVATE); if (useRawType) { contributionField.addAnnotation(AnnotationSpecs.suppressWarnings(RAWTYPES)); } fieldSpec = contributionField.build(); - componentImplementation.addField(FRAMEWORK_FIELD, fieldSpec); + shardImplementation.addField(FRAMEWORK_FIELD, fieldSpec); return fieldSpec; } - private Class<?> delegateType() { - return isProvider() ? DelegateFactory.class : DelegateProducer.class; + private ClassName delegateType() { + return isProvider() ? TypeNames.DELEGATE_FACTORY : TypeNames.DELEGATE_PRODUCER; } private boolean isProvider() { diff --git a/java/dagger/internal/codegen/writing/FrameworkInstanceBindingRepresentation.java b/java/dagger/internal/codegen/writing/FrameworkInstanceBindingRepresentation.java new file mode 100644 index 000000000..d4fb497d3 --- /dev/null +++ b/java/dagger/internal/codegen/writing/FrameworkInstanceBindingRepresentation.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2021 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.writing; + +import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; +import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; +import static dagger.internal.codegen.writing.ProvisionBindingRepresentation.needsCaching; +import static dagger.spi.model.BindingKind.DELEGATE; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.internal.codegen.binding.BindingGraph; +import dagger.internal.codegen.binding.BindingRequest; +import dagger.internal.codegen.binding.FrameworkType; +import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.spi.model.RequestKind; +import java.util.HashMap; +import java.util.Map; + +/** Returns request representation that wraps a framework instance expression */ +final class FrameworkInstanceBindingRepresentation { + private final ProvisionBinding binding; + private final DerivedFromFrameworkInstanceRequestRepresentation.Factory + derivedFromFrameworkInstanceRequestRepresentationFactory; + private final ImmediateFutureRequestRepresentation.Factory + immediateFutureRequestRepresentationFactory; + private final Map<BindingRequest, RequestRepresentation> requestRepresentations = new HashMap<>(); + private final RequestRepresentation providerRequestRepresentation; + private final RequestRepresentation producerFromProviderRepresentation; + + @AssistedInject + FrameworkInstanceBindingRepresentation( + @Assisted ProvisionBinding binding, + BindingGraph graph, + @Assisted FrameworkInstanceSupplier providerField, + ComponentImplementation componentImplementation, + DelegateRequestRepresentation.Factory delegateRequestRepresentationFactory, + DerivedFromFrameworkInstanceRequestRepresentation.Factory + derivedFromFrameworkInstanceRequestRepresentationFactory, + ImmediateFutureRequestRepresentation.Factory immediateFutureRequestRepresentationFactory, + ProducerNodeInstanceRequestRepresentation.Factory + producerNodeInstanceRequestRepresentationFactory, + ProviderInstanceRequestRepresentation.Factory providerInstanceRequestRepresentationFactory, + ProducerFromProviderCreationExpression.Factory + producerFromProviderCreationExpressionFactory) { + this.binding = binding; + this.derivedFromFrameworkInstanceRequestRepresentationFactory = + derivedFromFrameworkInstanceRequestRepresentationFactory; + this.immediateFutureRequestRepresentationFactory = immediateFutureRequestRepresentationFactory; + this.providerRequestRepresentation = + binding.kind().equals(DELEGATE) && !needsCaching(binding, graph) + ? delegateRequestRepresentationFactory.create(binding, RequestKind.PROVIDER) + : providerInstanceRequestRepresentationFactory.create(binding, providerField); + this.producerFromProviderRepresentation = + producerNodeInstanceRequestRepresentationFactory.create( + binding, + new FrameworkFieldInitializer( + componentImplementation, + binding, + producerFromProviderCreationExpressionFactory.create( + providerRequestRepresentation, + componentImplementation.shardImplementation(binding).name()))); + } + + public RequestRepresentation getRequestRepresentation(BindingRequest request) { + return reentrantComputeIfAbsent( + requestRepresentations, request, this::getRequestRepresentationUncached); + } + + private RequestRepresentation getRequestRepresentationUncached(BindingRequest request) { + switch (request.requestKind()) { + case INSTANCE: + case LAZY: + case PRODUCED: + case PROVIDER_OF_LAZY: + return derivedFromFrameworkInstanceRequestRepresentationFactory.create( + binding, providerRequestRepresentation, request.requestKind(), FrameworkType.PROVIDER); + case PROVIDER: + return providerRequestRepresentation; + case PRODUCER: + return producerFromProviderRepresentation; + + case FUTURE: + return immediateFutureRequestRepresentationFactory.create( + getRequestRepresentation(bindingRequest(binding.key(), RequestKind.INSTANCE)), + binding.key().type().java()); + + default: + throw new AssertionError( + String.format("Invalid binding request kind: %s", request.requestKind())); + } + } + + @AssistedFactory + static interface Factory { + FrameworkInstanceBindingRepresentation create( + ProvisionBinding binding, FrameworkInstanceSupplier providerField); + } +} diff --git a/java/dagger/internal/codegen/writing/FrameworkInstanceKind.java b/java/dagger/internal/codegen/writing/FrameworkInstanceKind.java new file mode 100644 index 000000000..8bceddc11 --- /dev/null +++ b/java/dagger/internal/codegen/writing/FrameworkInstanceKind.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 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.writing; + +import static dagger.spi.model.BindingKind.DELEGATE; + +import dagger.internal.codegen.binding.ContributionBinding; +import dagger.internal.codegen.writing.ComponentImplementation.CompilerMode; +import dagger.spi.model.BindingKind; + +/** Generation mode for satisfying framework request to Provision Binding. */ +enum FrameworkInstanceKind { + SWITCHING_PROVIDER, + EXPERIMENTAL_SWITCHING_PROVIDER, + STATIC_FACTORY, + PROVIDER_FIELD; + + public static FrameworkInstanceKind from(ContributionBinding binding, CompilerMode compilerMode) { + if (usesSwitchingProvider(binding, compilerMode)) { + if (compilerMode.isExperimentalMergedMode()) { + return EXPERIMENTAL_SWITCHING_PROVIDER; + } else if (compilerMode.isFastInit()) { + return SWITCHING_PROVIDER; + } else { + throw new IllegalStateException( + "Compiler mode " + compilerMode + " cannot use Switching Provider."); + } + } else if (usesStaticFactoryCreation(binding, compilerMode)) { + return STATIC_FACTORY; + } else { + return PROVIDER_FIELD; + } + } + + private static boolean usesSwitchingProvider( + ContributionBinding binding, CompilerMode compilerMode) { + if (!compilerMode.isFastInit() && !compilerMode.isExperimentalMergedMode()) { + return false; + } + // TODO(wanyingd): remove this check once we allow inaccessible types in merged mode. + if (compilerMode.isExperimentalMergedMode() + && binding.kind().equals(BindingKind.ASSISTED_FACTORY)) { + return false; + } + switch (binding.kind()) { + case ASSISTED_INJECTION: + case BOUND_INSTANCE: + case COMPONENT: + case COMPONENT_DEPENDENCY: + case DELEGATE: + case MEMBERS_INJECTOR: // TODO(b/199889259): Consider optimizing this for fastInit mode. + // These binding kinds avoid SwitchingProvider when the backing instance already exists, + // e.g. a component provider can use FactoryInstance.create(this). + return false; + case MULTIBOUND_SET: + case MULTIBOUND_MAP: + case OPTIONAL: + // These binding kinds avoid SwitchingProvider when their are no dependencies, + // e.g. a multibound set with no dependency can use a singleton, SetFactory.empty(). + return !binding.dependencies().isEmpty(); + case INJECTION: + case PROVISION: + case ASSISTED_FACTORY: + case COMPONENT_PROVISION: + case SUBCOMPONENT_CREATOR: + case PRODUCTION: + case COMPONENT_PRODUCTION: + case MEMBERS_INJECTION: + return true; + } + throw new AssertionError(String.format("No such binding kind: %s", binding.kind())); + } + + private static boolean usesStaticFactoryCreation( + ContributionBinding binding, CompilerMode compilerMode) { + // If {@code binding} is an unscoped provision binding with no factory arguments, then + // we don't need a field to hold its factory. In that case, this method returns the static + // select that returns the factory. + // member + if (!binding.dependencies().isEmpty() || binding.scope().isPresent()) { + return false; + } + switch (binding.kind()) { + case MULTIBOUND_MAP: + case MULTIBOUND_SET: + return true; + case PROVISION: + return !compilerMode.isFastInit() + && !compilerMode.isExperimentalMergedMode() + && !binding.requiresModuleInstance(); + case INJECTION: + return !compilerMode.isFastInit() && !compilerMode.isExperimentalMergedMode(); + default: + return false; + } + } +} diff --git a/java/dagger/internal/codegen/writing/FrameworkInstanceBindingExpression.java b/java/dagger/internal/codegen/writing/FrameworkInstanceRequestRepresentation.java index 56a6ef3d6..e8120e832 100644 --- a/java/dagger/internal/codegen/writing/FrameworkInstanceBindingExpression.java +++ b/java/dagger/internal/codegen/writing/FrameworkInstanceRequestRepresentation.java @@ -20,8 +20,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.FieldSpec; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.FrameworkType; import dagger.internal.codegen.javapoet.Expression; @@ -31,13 +29,13 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; /** A binding expression that uses a {@link FrameworkType} field. */ -abstract class FrameworkInstanceBindingExpression extends BindingExpression { +abstract class FrameworkInstanceRequestRepresentation extends RequestRepresentation { private final ContributionBinding binding; private final FrameworkInstanceSupplier frameworkInstanceSupplier; private final DaggerTypes types; private final DaggerElements elements; - FrameworkInstanceBindingExpression( + FrameworkInstanceRequestRepresentation( ContributionBinding binding, FrameworkInstanceSupplier frameworkInstanceSupplier, DaggerTypes types, @@ -49,10 +47,8 @@ abstract class FrameworkInstanceBindingExpression extends BindingExpression { } /** - * The expression for the framework instance for this binding. The field will be {@link - * ComponentImplementation#addInitialization(CodeBlock) initialized} and {@link - * ComponentImplementation#addField(ComponentImplementation.FieldSpecKind, FieldSpec) added} to - * the component the first time this method is invoked. + * The expression for the framework instance for this binding. The field will be initialized and + * added to the component the first time this method is invoked. */ @Override Expression getDependencyExpression(ClassName requestingClass) { @@ -60,7 +56,7 @@ abstract class FrameworkInstanceBindingExpression extends BindingExpression { TypeMirror expressionType = isTypeAccessibleFrom(binding.contributedType(), requestingClass.packageName()) || isInlinedFactoryCreation(memberSelect) - ? types.wrapType(binding.contributedType(), frameworkType().frameworkClass()) + ? types.wrapType(binding.contributedType(), frameworkType().frameworkClassName()) : rawFrameworkType(); return Expression.create(expressionType, memberSelect.getExpressionFor(requestingClass)); } @@ -84,6 +80,6 @@ abstract class FrameworkInstanceBindingExpression extends BindingExpression { } private DeclaredType rawFrameworkType() { - return types.getDeclaredType(elements.getTypeElement(frameworkType().frameworkClass())); + return types.getDeclaredType(elements.getTypeElement(frameworkType().frameworkClassName())); } } diff --git a/java/dagger/internal/codegen/writing/GwtCompatibility.java b/java/dagger/internal/codegen/writing/GwtCompatibility.java index 2df6a998a..1daa92fbc 100644 --- a/java/dagger/internal/codegen/writing/GwtCompatibility.java +++ b/java/dagger/internal/codegen/writing/GwtCompatibility.java @@ -16,6 +16,7 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Preconditions.checkArgument; import com.squareup.javapoet.AnnotationSpec; @@ -33,7 +34,7 @@ final class GwtCompatibility { */ static Optional<AnnotationSpec> gwtIncompatibleAnnotation(Binding binding) { checkArgument(binding.bindingElement().isPresent()); - Element element = binding.bindingElement().get(); + Element element = toJavac(binding.bindingElement().get()); while (element != null) { Optional<AnnotationSpec> gwtIncompatible = element diff --git a/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java b/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java index d4dbde794..4bafb5aff 100644 --- a/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java +++ b/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java @@ -22,13 +22,13 @@ import static com.squareup.javapoet.TypeSpec.classBuilder; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static javax.lang.model.element.Modifier.PRIVATE; +import androidx.room.compiler.processing.XElement; import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import dagger.internal.codegen.base.SourceFileGenerator; -import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; /** @@ -48,7 +48,7 @@ public final class HjarSourceFileGenerator<T> extends SourceFileGenerator<T> { } @Override - public Element originatingElement(T input) { + public XElement originatingElement(T input) { return delegate.originatingElement(input); } diff --git a/java/dagger/internal/codegen/writing/ImmediateFutureBindingExpression.java b/java/dagger/internal/codegen/writing/ImmediateFutureRequestRepresentation.java index dcd02faef..158c0959d 100644 --- a/java/dagger/internal/codegen/writing/ImmediateFutureBindingExpression.java +++ b/java/dagger/internal/codegen/writing/ImmediateFutureRequestRepresentation.java @@ -17,31 +17,33 @@ package dagger.internal.codegen.writing; import static com.google.common.base.Preconditions.checkNotNull; -import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Key; -import dagger.model.RequestKind; import javax.lang.model.SourceVersion; +import javax.lang.model.type.TypeMirror; -final class ImmediateFutureBindingExpression extends BindingExpression { - private final Key key; - private final ComponentBindingExpressions componentBindingExpressions; +final class ImmediateFutureRequestRepresentation extends RequestRepresentation { + private final RequestRepresentation instanceRequestRepresentation; + private final TypeMirror type; private final DaggerTypes types; private final SourceVersion sourceVersion; - ImmediateFutureBindingExpression( - Key key, - ComponentBindingExpressions componentBindingExpressions, + @AssistedInject + ImmediateFutureRequestRepresentation( + @Assisted RequestRepresentation instanceRequestRepresentation, + @Assisted TypeMirror type, DaggerTypes types, SourceVersion sourceVersion) { - this.key = key; - this.componentBindingExpressions = checkNotNull(componentBindingExpressions); + this.instanceRequestRepresentation = instanceRequestRepresentation; + this.type = type; this.types = checkNotNull(types); this.sourceVersion = checkNotNull(sourceVersion); } @@ -49,25 +51,29 @@ final class ImmediateFutureBindingExpression extends BindingExpression { @Override Expression getDependencyExpression(ClassName requestingClass) { return Expression.create( - types.wrapType(key.type(), ListenableFuture.class), + types.wrapType(type, TypeNames.LISTENABLE_FUTURE), CodeBlock.of("$T.immediateFuture($L)", Futures.class, instanceExpression(requestingClass))); } private CodeBlock instanceExpression(ClassName requestingClass) { - Expression expression = - componentBindingExpressions.getDependencyExpression( - bindingRequest(key, RequestKind.INSTANCE), requestingClass); + Expression expression = instanceRequestRepresentation.getDependencyExpression(requestingClass); if (sourceVersion.compareTo(SourceVersion.RELEASE_7) <= 0) { // Java 7 type inference is not as strong as in Java 8, and therefore some generated code must // cast. // // For example, javac7 cannot detect that Futures.immediateFuture(ImmutableSet.of("T")) // can safely be assigned to ListenableFuture<Set<T>>. - if (!types.isSameType(expression.type(), key.type())) { + if (!types.isSameType(expression.type(), type)) { return CodeBlock.of( - "($T) $L", types.accessibleType(key.type(), requestingClass), expression.codeBlock()); + "($T) $L", types.accessibleType(type, requestingClass), expression.codeBlock()); } } return expression.codeBlock(); } + + @AssistedFactory + static interface Factory { + ImmediateFutureRequestRepresentation create( + RequestRepresentation instanceRequestRepresentation, TypeMirror type); + } } diff --git a/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java b/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java index 889f8985c..f48fd3647 100644 --- a/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java +++ b/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java @@ -22,17 +22,17 @@ import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XProcessingEnv; import com.google.common.collect.ImmutableList; import com.squareup.javapoet.TypeSpec; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.MapKeys; import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; /** * Generates a class that exposes a non-{@code public} {@link @@ -40,26 +40,27 @@ import javax.lang.model.element.Element; */ public final class InaccessibleMapKeyProxyGenerator extends SourceFileGenerator<ContributionBinding> { - private final DaggerTypes types; - private final DaggerElements elements; + private final XProcessingEnv processingEnv; @Inject InaccessibleMapKeyProxyGenerator( - Filer filer, DaggerTypes types, DaggerElements elements, SourceVersion sourceVersion) { + XProcessingEnv processingEnv, + XFiler filer, + DaggerElements elements, + SourceVersion sourceVersion) { super(filer, elements, sourceVersion); - this.types = types; - this.elements = elements; + this.processingEnv = processingEnv; } @Override - public Element originatingElement(ContributionBinding binding) { + public XElement originatingElement(ContributionBinding binding) { // a map key is only ever present on bindings that have a binding element return binding.bindingElement().get(); } @Override public ImmutableList<TypeSpec.Builder> topLevelTypes(ContributionBinding binding) { - return MapKeys.mapKeyFactoryMethod(binding, types, elements) + return MapKeys.mapKeyFactoryMethod(binding, processingEnv) .map( method -> classBuilder(MapKeys.mapKeyProxyClassName(binding)) diff --git a/java/dagger/internal/codegen/writing/InjectionMethods.java b/java/dagger/internal/codegen/writing/InjectionMethods.java index 7cebc10db..97d085f50 100644 --- a/java/dagger/internal/codegen/writing/InjectionMethods.java +++ b/java/dagger/internal/codegen/writing/InjectionMethods.java @@ -16,6 +16,7 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.auto.common.MoreElements.asExecutable; import static com.google.auto.common.MoreElements.asType; import static com.google.auto.common.MoreElements.asVariable; @@ -23,7 +24,7 @@ import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.Preconditions.checkArgument; import static com.squareup.javapoet.MethodSpec.methodBuilder; -import static dagger.internal.codegen.base.RequestKinds.requestTypeName; +import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedParameter; import static dagger.internal.codegen.binding.ConfigurationAnnotations.getNullableType; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable; @@ -38,11 +39,15 @@ import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibl import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessible; import static dagger.internal.codegen.langmodel.Accessibility.isRawTypePubliclyAccessible; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; +import static dagger.internal.codegen.xprocessing.XElements.asExecutable; +import static dagger.internal.codegen.xprocessing.XElements.asMethodParameter; import static java.util.stream.Collectors.toList; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import static javax.lang.model.type.TypeKind.VOID; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XExecutableParameterElement; import com.google.auto.common.MoreElements; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -56,7 +61,6 @@ import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeVariableName; import dagger.internal.Preconditions; import dagger.internal.codegen.base.UniqueNameSet; -import dagger.internal.codegen.binding.AssistedInjectionAnnotations; import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.compileroption.CompilerOptions; @@ -65,8 +69,8 @@ import dagger.internal.codegen.javapoet.CodeBlocks; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.DependencyRequest; -import dagger.model.RequestKind; +import dagger.spi.model.DaggerAnnotation; +import dagger.spi.model.DependencyRequest; import java.util.List; import java.util.Optional; import java.util.function.Function; @@ -76,7 +80,6 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Parameterizable; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; /** Convenience methods for creating and invoking {@link InjectionMethod}s. */ @@ -119,7 +122,7 @@ final class InjectionMethods { ProvisionBinding binding, CompilerOptions compilerOptions, KotlinMetadataUtil metadataUtil) { - ExecutableElement element = asExecutable(binding.bindingElement().get()); + ExecutableElement element = asExecutable(toJavac(binding.bindingElement().get())); switch (element.getKind()) { case CONSTRUCTOR: return constructorProxy(element); @@ -142,13 +145,15 @@ final class InjectionMethods { static CodeBlock invoke( ProvisionBinding binding, Function<DependencyRequest, CodeBlock> dependencyUsage, + Function<VariableElement, String> uniqueAssistedParameterName, ClassName requestingClass, Optional<CodeBlock> moduleReference, CompilerOptions compilerOptions, KotlinMetadataUtil metadataUtil) { ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder(); moduleReference.ifPresent(arguments::add); - invokeArguments(binding, dependencyUsage, requestingClass).forEach(arguments::add); + invokeArguments(binding, dependencyUsage, uniqueAssistedParameterName) + .forEach(arguments::add); ClassName enclosingClass = generatedClassNameForBinding(binding); MethodSpec methodSpec = create(binding, compilerOptions, metadataUtil); @@ -158,23 +163,22 @@ final class InjectionMethods { static ImmutableList<CodeBlock> invokeArguments( ProvisionBinding binding, Function<DependencyRequest, CodeBlock> dependencyUsage, - ClassName requestingClass) { - ImmutableMap<VariableElement, DependencyRequest> dependencyRequestMap = + Function<VariableElement, String> uniqueAssistedParameterName) { + ImmutableMap<XExecutableParameterElement, DependencyRequest> dependencyRequestMap = binding.provisionDependencies().stream() .collect( toImmutableMap( - request -> MoreElements.asVariable(request.requestElement().get()), + request -> asMethodParameter(request.requestElement().get().xprocessing()), request -> request)); ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder(); - for (VariableElement parameter : - asExecutable(binding.bindingElement().get()).getParameters()) { - if (AssistedInjectionAnnotations.isAssistedParameter(parameter)) { - arguments.add(CodeBlock.of("$L", parameter.getSimpleName())); + XExecutableElement method = asExecutable(binding.bindingElement().get()); + for (XExecutableParameterElement parameter : method.getParameters()) { + if (isAssistedParameter(parameter)) { + arguments.add(CodeBlock.of("$L", uniqueAssistedParameterName.apply(toJavac(parameter)))); } else if (dependencyRequestMap.containsKey(parameter)) { DependencyRequest request = dependencyRequestMap.get(parameter); - arguments.add( - injectionMethodArgument(request, dependencyUsage.apply(request), requestingClass)); + arguments.add(dependencyUsage.apply(request)); } else { throw new AssertionError("Unexpected parameter: " + parameter); } @@ -205,7 +209,7 @@ final class InjectionMethods { */ static boolean requiresInjectionMethod( ProvisionBinding binding, CompilerOptions compilerOptions, ClassName requestingClass) { - ExecutableElement method = MoreElements.asExecutable(binding.bindingElement().get()); + ExecutableElement method = asExecutable(toJavac(binding.bindingElement().get())); return !binding.injectionSites().isEmpty() || binding.shouldCheckForNull(compilerOptions) || !isElementAccessibleFrom(method, requestingClass.packageName()) @@ -276,7 +280,8 @@ final class InjectionMethods { // methods for fields have a single dependency request .collect(DaggerCollectors.onlyElement()) .key() - .qualifier(); + .qualifier() + .map(DaggerAnnotation::java); return fieldProxy(asVariable(injectionSite.element()), methodName, qualifier); } throw new AssertionError(injectionSite); @@ -371,44 +376,6 @@ final class InjectionMethods { } } - private static CodeBlock injectionMethodArgument( - DependencyRequest dependency, CodeBlock argument, ClassName generatedTypeName) { - TypeMirror keyType = dependency.key().type(); - CodeBlock.Builder codeBlock = CodeBlock.builder(); - if (!isRawTypeAccessible(keyType, generatedTypeName.packageName()) - && isTypeAccessibleFrom(keyType, generatedTypeName.packageName())) { - if (!dependency.kind().equals(RequestKind.INSTANCE)) { - TypeName usageTypeName = accessibleType(dependency); - codeBlock.add("($T) ($T)", usageTypeName, rawTypeName(usageTypeName)); - } else if (dependency.requestElement().get().asType().getKind().equals(TypeKind.TYPEVAR)) { - codeBlock.add("($T)", keyType); - } - } - return codeBlock.add(argument).build(); - } - - /** - * Returns the parameter type for {@code dependency}. If the raw type is not accessible, returns - * {@link Object}. - */ - private static TypeName accessibleType(DependencyRequest dependency) { - TypeName typeName = requestTypeName(dependency.kind(), accessibleType(dependency.key().type())); - return dependency - .requestElement() - .map(element -> element.asType().getKind().isPrimitive()) - .orElse(false) - ? typeName.unbox() - : typeName; - } - - /** - * Returns the accessible type for {@code type}. If the raw type is not accessible, returns {@link - * Object}. - */ - private static TypeName accessibleType(TypeMirror type) { - return isRawTypePubliclyAccessible(type) ? TypeName.get(type) : TypeName.OBJECT; - } - private enum InstanceCastPolicy { CAST_IF_NOT_PUBLIC, IGNORE; diff --git a/java/dagger/internal/codegen/writing/InjectionOrProvisionProviderCreationExpression.java b/java/dagger/internal/codegen/writing/InjectionOrProvisionProviderCreationExpression.java index b5135b057..b9bc70b18 100644 --- a/java/dagger/internal/codegen/writing/InjectionOrProvisionProviderCreationExpression.java +++ b/java/dagger/internal/codegen/writing/InjectionOrProvisionProviderCreationExpression.java @@ -18,11 +18,16 @@ package dagger.internal.codegen.writing; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; -import static dagger.model.BindingKind.INJECTION; +import static dagger.spi.model.BindingKind.INJECTION; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.javapoet.CodeBlocks; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; import javax.inject.Provider; @@ -35,12 +40,17 @@ final class InjectionOrProvisionProviderCreationExpression implements FrameworkInstanceCreationExpression { private final ContributionBinding binding; - private final ComponentBindingExpressions componentBindingExpressions; + private final ShardImplementation shardImplementation; + private final ComponentRequestRepresentations componentRequestRepresentations; + @AssistedInject InjectionOrProvisionProviderCreationExpression( - ContributionBinding binding, ComponentBindingExpressions componentBindingExpressions) { + @Assisted ContributionBinding binding, + ComponentImplementation componentImplementation, + ComponentRequestRepresentations componentRequestRepresentations) { this.binding = checkNotNull(binding); - this.componentBindingExpressions = checkNotNull(componentBindingExpressions); + this.shardImplementation = componentImplementation.shardImplementation(binding); + this.componentRequestRepresentations = componentRequestRepresentations; } @Override @@ -49,16 +59,22 @@ final class InjectionOrProvisionProviderCreationExpression CodeBlock.of( "$T.create($L)", generatedClassNameForBinding(binding), - componentBindingExpressions.getCreateMethodArgumentsCodeBlock(binding)); + componentRequestRepresentations.getCreateMethodArgumentsCodeBlock( + binding, shardImplementation.name())); // When scoping a parameterized factory for an @Inject class, Java 7 cannot always infer the // type properly, so cast to a raw framework type before scoping. if (binding.kind().equals(INJECTION) && binding.unresolved().isPresent() && binding.scope().isPresent()) { - return CodeBlocks.cast(createFactory, Provider.class); + return CodeBlocks.cast(createFactory, TypeNames.PROVIDER); } else { return createFactory; } } + + @AssistedFactory + static interface Factory { + InjectionOrProvisionProviderCreationExpression create(ContributionBinding binding); + } } diff --git a/java/dagger/internal/codegen/writing/InnerSwitchingProviders.java b/java/dagger/internal/codegen/writing/InnerSwitchingProviders.java deleted file mode 100644 index c2f9893b7..000000000 --- a/java/dagger/internal/codegen/writing/InnerSwitchingProviders.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2018 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.writing; - -import static com.squareup.javapoet.MethodSpec.constructorBuilder; -import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; -import static dagger.model.RequestKind.INSTANCE; -import static javax.lang.model.element.Modifier.FINAL; -import static javax.lang.model.element.Modifier.PRIVATE; - -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import dagger.internal.codegen.binding.ContributionBinding; -import dagger.internal.codegen.javapoet.Expression; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Key; -import javax.inject.Provider; -import javax.lang.model.type.TypeMirror; - -/** - * Generates {@linkplain BindingExpression binding expressions} for a binding that is represented by - * an inner {@code SwitchingProvider} class. - */ -final class InnerSwitchingProviders extends SwitchingProviders { - private final ComponentBindingExpressions componentBindingExpressions; - private final DaggerTypes types; - - InnerSwitchingProviders( - ComponentImplementation componentImplementation, - ComponentBindingExpressions componentBindingExpressions, - DaggerTypes types) { - super(componentImplementation, types); - this.componentBindingExpressions = componentBindingExpressions; - this.types = types; - } - - /** - * Returns the binding expression for a binding that satisfies a {@link Provider} requests with a - * inner {@code SwitchingProvider} class. - */ - BindingExpression newBindingExpression(ContributionBinding binding) { - return new BindingExpression() { - @Override - Expression getDependencyExpression(ClassName requestingClass) { - return getProviderExpression(new SwitchCase(binding, requestingClass)); - } - }; - } - - @Override - protected TypeSpec createSwitchingProviderType(TypeSpec.Builder builder) { - return builder - .addModifiers(PRIVATE, FINAL) - .addField(TypeName.INT, "id", PRIVATE, FINAL) - .addMethod( - constructorBuilder() - .addParameter(TypeName.INT, "id") - .addStatement("this.id = id") - .build()) - .build(); - } - - private final class SwitchCase implements SwitchingProviders.SwitchCase { - private final ContributionBinding binding; - private final ClassName requestingClass; - - SwitchCase(ContributionBinding binding, ClassName requestingClass) { - this.binding = binding; - this.requestingClass = requestingClass; - } - - @Override - public Key key() { - return binding.key(); - } - - @Override - public Expression getProviderExpression(ClassName switchingProviderClass, int switchId) { - TypeMirror instanceType = types.accessibleType(binding.contributedType(), requestingClass); - return Expression.create( - types.wrapType(instanceType, Provider.class), - CodeBlock.of("new $T<>($L)", switchingProviderClass, switchId)); - } - - @Override - public Expression getReturnExpression(ClassName switchingProviderClass) { - return componentBindingExpressions.getDependencyExpression( - bindingRequest(binding.key(), INSTANCE), switchingProviderClass); - } - } -} diff --git a/java/dagger/internal/codegen/writing/InstanceFactoryCreationExpression.java b/java/dagger/internal/codegen/writing/InstanceFactoryCreationExpression.java index a7d6685be..807c64c58 100644 --- a/java/dagger/internal/codegen/writing/InstanceFactoryCreationExpression.java +++ b/java/dagger/internal/codegen/writing/InstanceFactoryCreationExpression.java @@ -49,9 +49,4 @@ final class InstanceFactoryCreationExpression implements FrameworkInstanceCreati nullable ? "createNullable" : "create", instanceExpression.get()); } - - @Override - public boolean useInnerSwitchingProvider() { - return false; - } } diff --git a/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java b/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java index 104d48a1d..a326e0a05 100644 --- a/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java +++ b/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java @@ -19,56 +19,57 @@ package dagger.internal.codegen.writing; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.binding.MapKeys.getMapKeyExpression; import static dagger.internal.codegen.binding.SourceFiles.mapFactoryClassName; +import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; -import com.google.common.collect.ImmutableSet; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ContributionBinding; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.model.DependencyRequest; -import dagger.producers.Produced; -import dagger.producers.Producer; -import javax.inject.Provider; -import javax.lang.model.type.TypeMirror; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.DependencyRequest; +import java.util.stream.Stream; /** A factory creation expression for a multibound map. */ final class MapFactoryCreationExpression extends MultibindingFactoryCreationExpression { + private final XProcessingEnv processingEnv; private final ComponentImplementation componentImplementation; private final BindingGraph graph; private final ContributionBinding binding; - private final DaggerElements elements; + @AssistedInject MapFactoryCreationExpression( - ContributionBinding binding, + @Assisted ContributionBinding binding, + XProcessingEnv processingEnv, ComponentImplementation componentImplementation, - ComponentBindingExpressions componentBindingExpressions, - BindingGraph graph, - DaggerElements elements) { - super(binding, componentImplementation, componentBindingExpressions); + ComponentRequestRepresentations componentRequestRepresentations, + BindingGraph graph) { + super(binding, componentImplementation, componentRequestRepresentations); + this.processingEnv = processingEnv; this.binding = checkNotNull(binding); - this.componentImplementation = checkNotNull(componentImplementation); - this.graph = checkNotNull(graph); - this.elements = checkNotNull(elements); + this.componentImplementation = componentImplementation; + this.graph = graph; } @Override public CodeBlock creationExpression() { CodeBlock.Builder builder = CodeBlock.builder().add("$T.", mapFactoryClassName(binding)); if (!useRawType()) { - MapType mapType = MapType.from(binding.key().type()); + MapType mapType = MapType.from(binding.key()); // TODO(ronshapiro): either inline this into mapFactoryClassName, or add a // mapType.unwrappedValueType() method that doesn't require a framework type - TypeMirror valueType = mapType.valueType(); - for (Class<?> frameworkClass : - ImmutableSet.of(Provider.class, Producer.class, Produced.class)) { - if (mapType.valuesAreTypeOf(frameworkClass)) { - valueType = mapType.unwrappedValueType(frameworkClass); - break; - } - } - builder.add("<$T, $T>", mapType.keyType(), valueType); + XType valueType = + Stream.of(TypeNames.PROVIDER, TypeNames.PRODUCER, TypeNames.PRODUCED) + .filter(mapType::valuesAreTypeOf) + .map(mapType::unwrappedValueType) + .collect(toOptional()) + .orElseGet(mapType::valueType); + builder.add("<$T, $T>", mapType.keyType().getTypeName(), valueType.getTypeName()); } builder.add("builder($L)", binding.dependencies().size()); @@ -77,11 +78,16 @@ final class MapFactoryCreationExpression extends MultibindingFactoryCreationExpr ContributionBinding contributionBinding = graph.contributionBinding(dependency.key()); builder.add( ".put($L, $L)", - getMapKeyExpression(contributionBinding, componentImplementation.name(), elements), + getMapKeyExpression(contributionBinding, componentImplementation.name(), processingEnv), multibindingDependencyExpression(dependency)); } builder.add(".build()"); return builder.build(); } + + @AssistedFactory + static interface Factory { + MapFactoryCreationExpression create(ContributionBinding binding); + } } diff --git a/java/dagger/internal/codegen/writing/MapBindingExpression.java b/java/dagger/internal/codegen/writing/MapRequestRepresentation.java index 255e85d9c..876c24832 100644 --- a/java/dagger/internal/codegen/writing/MapBindingExpression.java +++ b/java/dagger/internal/codegen/writing/MapRequestRepresentation.java @@ -22,53 +22,57 @@ import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import static dagger.internal.codegen.binding.MapKeys.getMapKeyExpression; import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; -import static dagger.model.BindingKind.MULTIBOUND_MAP; -import static javax.lang.model.util.ElementFilter.methodsIn; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.spi.model.BindingKind.MULTIBOUND_MAP; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.MapBuilder; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.javapoet.Expression; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.BindingKind; -import dagger.model.DependencyRequest; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.BindingKind; +import dagger.spi.model.DependencyRequest; import java.util.Collections; -import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; -/** A {@link BindingExpression} for multibound maps. */ -final class MapBindingExpression extends SimpleInvocationBindingExpression { +/** A {@link RequestRepresentation} for multibound maps. */ +final class MapRequestRepresentation extends RequestRepresentation { /** Maximum number of key-value pairs that can be passed to ImmutableMap.of(K, V, K, V, ...). */ private static final int MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS = 5; + private final XProcessingEnv processingEnv; private final ProvisionBinding binding; private final ImmutableMap<DependencyRequest, ContributionBinding> dependencies; - private final ComponentBindingExpressions componentBindingExpressions; - private final DaggerTypes types; - private final DaggerElements elements; + private final ComponentRequestRepresentations componentRequestRepresentations; + private final boolean isExperimentalMergedMode; - MapBindingExpression( - ProvisionBinding binding, + @AssistedInject + MapRequestRepresentation( + @Assisted ProvisionBinding binding, + XProcessingEnv processingEnv, BindingGraph graph, - ComponentBindingExpressions componentBindingExpressions, - DaggerTypes types, - DaggerElements elements) { - super(binding); + ComponentImplementation componentImplementation, + ComponentRequestRepresentations componentRequestRepresentations) { this.binding = binding; + this.processingEnv = processingEnv; BindingKind bindingKind = this.binding.kind(); checkArgument(bindingKind.equals(MULTIBOUND_MAP), bindingKind); - this.componentBindingExpressions = componentBindingExpressions; - this.types = types; - this.elements = elements; + this.componentRequestRepresentations = componentRequestRepresentations; this.dependencies = Maps.toMap(binding.dependencies(), dep -> graph.contributionBinding(dep.key())); + this.isExperimentalMergedMode = + componentImplementation.compilerMode().isExperimentalMergedMode(); } @Override @@ -116,30 +120,38 @@ final class MapBindingExpression extends SimpleInvocationBindingExpression { instantiation.add(".put($L)", keyAndValueExpression(dependency, requestingClass)); } return Expression.create( - isImmutableMapAvailable ? immutableMapType() : binding.key().type(), + isImmutableMapAvailable ? immutableMapType() : binding.key().type().xprocessing(), instantiation.add(".build()").build()); } } - private DeclaredType immutableMapType() { + private XType immutableMapType() { MapType mapType = MapType.from(binding.key()); - return types.getDeclaredType( - elements.getTypeElement(ImmutableMap.class), mapType.keyType(), mapType.valueType()); + return processingEnv.getDeclaredType( + processingEnv.requireTypeElement(TypeNames.IMMUTABLE_MAP), + mapType.keyType(), + mapType.valueType()); } private CodeBlock keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass) { return CodeBlock.of( "$L, $L", - getMapKeyExpression(dependencies.get(dependency), requestingClass, elements), - componentBindingExpressions - .getDependencyExpression(bindingRequest(dependency), requestingClass) - .codeBlock()); + getMapKeyExpression(dependencies.get(dependency), requestingClass, processingEnv), + isExperimentalMergedMode + ? componentRequestRepresentations + .getExperimentalSwitchingProviderDependencyRepresentation( + bindingRequest(dependency)) + .getDependencyExpression(dependency.kind(), binding) + .codeBlock() + : componentRequestRepresentations + .getDependencyExpression(bindingRequest(dependency), requestingClass) + .codeBlock()); } private Expression collectionsStaticFactoryInvocation( ClassName requestingClass, CodeBlock methodInvocation) { return Expression.create( - binding.key().type(), + binding.key().type().java(), CodeBlock.builder() .add("$T.", Collections.class) .add(maybeTypeParameters(requestingClass)) @@ -148,23 +160,26 @@ final class MapBindingExpression extends SimpleInvocationBindingExpression { } private CodeBlock maybeTypeParameters(ClassName requestingClass) { - TypeMirror bindingKeyType = binding.key().type(); + TypeMirror bindingKeyType = binding.key().type().java(); MapType mapType = MapType.from(binding.key()); return isTypeAccessibleFrom(bindingKeyType, requestingClass.packageName()) - ? CodeBlock.of("<$T, $T>", mapType.keyType(), mapType.valueType()) + ? CodeBlock.of( + "<$T, $T>", mapType.keyType().getTypeName(), mapType.valueType().getTypeName()) : CodeBlock.of(""); } private boolean isImmutableMapBuilderWithExpectedSizeAvailable() { - if (isImmutableMapAvailable()) { - return methodsIn(elements.getTypeElement(ImmutableMap.class).getEnclosedElements()) - .stream() - .anyMatch(method -> method.getSimpleName().contentEquals("builderWithExpectedSize")); - } - return false; + return isImmutableMapAvailable() + && processingEnv.requireTypeElement(TypeNames.IMMUTABLE_MAP).getDeclaredMethods().stream() + .anyMatch(method -> getSimpleName(method).contentEquals("builderWithExpectedSize")); } private boolean isImmutableMapAvailable() { - return elements.getTypeElement(ImmutableMap.class) != null; + return processingEnv.findTypeElement(TypeNames.IMMUTABLE_MAP) != null; + } + + @AssistedFactory + static interface Factory { + MapRequestRepresentation create(ProvisionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/MemberSelect.java b/java/dagger/internal/codegen/writing/MemberSelect.java index b04e21be0..d4e675018 100644 --- a/java/dagger/internal/codegen/writing/MemberSelect.java +++ b/java/dagger/internal/codegen/writing/MemberSelect.java @@ -17,32 +17,10 @@ package dagger.internal.codegen.writing; import static com.google.common.base.Preconditions.checkNotNull; -import static dagger.internal.codegen.binding.ContributionBinding.FactoryCreationStrategy.SINGLETON_INSTANCE; -import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementTypeVariableNames; -import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; -import static dagger.internal.codegen.binding.SourceFiles.setFactoryClassName; -import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; -import static dagger.internal.codegen.javapoet.TypeNames.FACTORY; -import static dagger.internal.codegen.javapoet.TypeNames.MAP_FACTORY; -import static dagger.internal.codegen.javapoet.TypeNames.PRODUCER; -import static dagger.internal.codegen.javapoet.TypeNames.PRODUCERS; -import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER; -import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; -import static javax.lang.model.type.TypeKind.DECLARED; -import com.google.auto.common.MoreTypes; -import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.TypeVariableName; -import dagger.internal.codegen.base.SetType; -import dagger.internal.codegen.binding.BindingType; -import dagger.internal.codegen.binding.ContributionBinding; -import dagger.internal.codegen.javapoet.CodeBlocks; -import java.util.List; -import java.util.Optional; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; /** * Represents a {@link com.sun.source.tree.MemberSelectTree} as a {@link CodeBlock}. @@ -51,20 +29,22 @@ abstract class MemberSelect { /** * Returns a {@link MemberSelect} that accesses the field given by {@code fieldName} owned by - * {@code owningClass}. In this context "local" refers to the fact that the field is owned by the - * type (or an enclosing type) from which the code block will be used. The returned - * {@link MemberSelect} will not be valid for accessing the field from a different class - * (regardless of accessibility). + * {@code owningClass}. In this context "local" refers to the fact that the field is owned by the + * type (or an enclosing type) from which the code block will be used. The returned {@link + * MemberSelect} will not be valid for accessing the field from a different class (regardless of + * accessibility). */ - static MemberSelect localField(ClassName owningClass, String fieldName) { - return new LocalField(owningClass, fieldName); + static MemberSelect localField(ShardImplementation owningShard, String fieldName) { + return new LocalField(owningShard, fieldName); } private static final class LocalField extends MemberSelect { + final ShardImplementation owningShard; final String fieldName; - LocalField(ClassName owningClass, String fieldName) { - super(owningClass, false); + LocalField(ShardImplementation owningShard, String fieldName) { + super(owningShard.name(), false); + this.owningShard = owningShard; this.fieldName = checkNotNull(fieldName); } @@ -72,165 +52,7 @@ abstract class MemberSelect { CodeBlock getExpressionFor(ClassName usingClass) { return owningClass().equals(usingClass) ? CodeBlock.of("$N", fieldName) - : CodeBlock.of("$T.this.$N", owningClass(), fieldName); - } - } - - /** - * Returns a {@link MemberSelect} that accesses the method given by {@code methodName} owned by - * {@code owningClass}. In this context "local" refers to the fact that the method is owned by the - * type (or an enclosing type) from which the code block will be used. The returned {@link - * MemberSelect} will not be valid for accessing the method from a different class (regardless of - * accessibility). - */ - static MemberSelect localMethod(ClassName owningClass, String methodName) { - return new LocalMethod(owningClass, methodName); - } - - private static final class LocalMethod extends MemberSelect { - final String methodName; - - LocalMethod(ClassName owningClass, String methodName) { - super(owningClass, false); - this.methodName = checkNotNull(methodName); - } - - @Override - CodeBlock getExpressionFor(ClassName usingClass) { - return owningClass().equals(usingClass) - ? CodeBlock.of("$N()", methodName) - : CodeBlock.of("$T.this.$N()", owningClass(), methodName); - } - } - - /** - * If {@code resolvedBindings} is an unscoped provision binding with no factory arguments or a - * no-op members injection binding, then we don't need a field to hold its factory. In that case, - * this method returns the static member select that returns the factory or no-op members - * injector. - */ - static Optional<MemberSelect> staticFactoryCreation(ContributionBinding contributionBinding) { - if (contributionBinding.factoryCreationStrategy().equals(SINGLETON_INSTANCE) - && !contributionBinding.scope().isPresent()) { - switch (contributionBinding.kind()) { - case MULTIBOUND_MAP: - return Optional.of(emptyMapFactory(contributionBinding)); - - case MULTIBOUND_SET: - return Optional.of(emptySetFactory(contributionBinding)); - - case INJECTION: - case PROVISION: - TypeMirror keyType = contributionBinding.key().type(); - if (keyType.getKind().equals(DECLARED)) { - ImmutableList<TypeVariableName> typeVariables = - bindingTypeElementTypeVariableNames(contributionBinding); - if (!typeVariables.isEmpty()) { - List<? extends TypeMirror> typeArguments = - ((DeclaredType) keyType).getTypeArguments(); - return Optional.of( - MemberSelect.parameterizedFactoryCreateMethod( - generatedClassNameForBinding(contributionBinding), typeArguments)); - } - } - // fall through - - default: - return Optional.of( - new StaticMethod( - generatedClassNameForBinding(contributionBinding), CodeBlock.of("create()"))); - } - } - - return Optional.empty(); - } - - /** - * Returns a {@link MemberSelect} for the instance of a {@code create()} method on a factory. This - * only applies for factories that do not have any dependencies. - */ - private static MemberSelect parameterizedFactoryCreateMethod( - ClassName owningClass, List<? extends TypeMirror> parameters) { - return new ParameterizedStaticMethod( - owningClass, ImmutableList.copyOf(parameters), CodeBlock.of("create()"), FACTORY); - } - - private static final class StaticMethod extends MemberSelect { - final CodeBlock methodCodeBlock; - - StaticMethod(ClassName owningClass, CodeBlock methodCodeBlock) { - super(owningClass, true); - this.methodCodeBlock = checkNotNull(methodCodeBlock); - } - - @Override - CodeBlock getExpressionFor(ClassName usingClass) { - return owningClass().equals(usingClass) - ? methodCodeBlock - : CodeBlock.of("$T.$L", owningClass(), methodCodeBlock); - } - } - - /** A {@link MemberSelect} for a factory of an empty map. */ - private static MemberSelect emptyMapFactory(ContributionBinding contributionBinding) { - BindingType bindingType = contributionBinding.bindingType(); - ImmutableList<TypeMirror> typeParameters = - ImmutableList.copyOf( - MoreTypes.asDeclared(contributionBinding.key().type()).getTypeArguments()); - if (bindingType.equals(BindingType.PRODUCTION)) { - return new ParameterizedStaticMethod( - PRODUCERS, typeParameters, CodeBlock.of("emptyMapProducer()"), PRODUCER); - } else { - return new ParameterizedStaticMethod( - MAP_FACTORY, typeParameters, CodeBlock.of("emptyMapProvider()"), PROVIDER); - } - } - - /** - * A static member select for an empty set factory. Calls {@link - * dagger.internal.SetFactory#empty()}, {@link dagger.producers.internal.SetProducer#empty()}, or - * {@link dagger.producers.internal.SetOfProducedProducer#empty()}, depending on the set bindings. - */ - private static MemberSelect emptySetFactory(ContributionBinding binding) { - return new ParameterizedStaticMethod( - setFactoryClassName(binding), - ImmutableList.of(SetType.from(binding.key()).elementType()), - CodeBlock.of("empty()"), - FACTORY); - } - - private static final class ParameterizedStaticMethod extends MemberSelect { - final ImmutableList<TypeMirror> typeParameters; - final CodeBlock methodCodeBlock; - final ClassName rawReturnType; - - ParameterizedStaticMethod( - ClassName owningClass, - ImmutableList<TypeMirror> typeParameters, - CodeBlock methodCodeBlock, - ClassName rawReturnType) { - super(owningClass, true); - this.typeParameters = typeParameters; - this.methodCodeBlock = methodCodeBlock; - this.rawReturnType = rawReturnType; - } - - @Override - CodeBlock getExpressionFor(ClassName usingClass) { - boolean accessible = true; - for (TypeMirror typeParameter : typeParameters) { - accessible &= isTypeAccessibleFrom(typeParameter, usingClass.packageName()); - } - - if (accessible) { - return CodeBlock.of( - "$T.<$L>$L", - owningClass(), - typeParameters.stream().map(CodeBlocks::type).collect(toParametersCodeBlock()), - methodCodeBlock); - } else { - return CodeBlock.of("(($T) $T.$L)", rawReturnType, owningClass(), methodCodeBlock); - } + : CodeBlock.of("$L.$N", owningShard.shardFieldReference(), fieldName); } } diff --git a/java/dagger/internal/codegen/writing/MembersInjectionBindingRepresentation.java b/java/dagger/internal/codegen/writing/MembersInjectionBindingRepresentation.java new file mode 100644 index 000000000..cfad7458c --- /dev/null +++ b/java/dagger/internal/codegen/writing/MembersInjectionBindingRepresentation.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 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.writing; + +import static com.google.common.base.Preconditions.checkArgument; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.internal.codegen.binding.BindingRequest; +import dagger.internal.codegen.binding.MembersInjectionBinding; +import dagger.spi.model.RequestKind; + +/** + * A binding representation that wraps code generation methods that satisfy all kinds of request for + * that binding. + */ +final class MembersInjectionBindingRepresentation implements BindingRepresentation { + private final MembersInjectionBinding binding; + private final MembersInjectionRequestRepresentation membersInjectionRequestRepresentation; + + @AssistedInject + MembersInjectionBindingRepresentation( + @Assisted MembersInjectionBinding binding, + MembersInjectionRequestRepresentation.Factory membersInjectionRequestRepresentationFactory) { + this.binding = binding; + this.membersInjectionRequestRepresentation = + membersInjectionRequestRepresentationFactory.create(binding); + } + + @Override + public RequestRepresentation getRequestRepresentation(BindingRequest request) { + checkArgument(request.isRequestKind(RequestKind.MEMBERS_INJECTION), binding); + return membersInjectionRequestRepresentation; + } + + @AssistedFactory + static interface Factory { + MembersInjectionBindingRepresentation create(MembersInjectionBinding binding); + } +} diff --git a/java/dagger/internal/codegen/writing/MembersInjectionMethods.java b/java/dagger/internal/codegen/writing/MembersInjectionMethods.java index 3d602b3d6..ce332f5d1 100644 --- a/java/dagger/internal/codegen/writing/MembersInjectionMethods.java +++ b/java/dagger/internal/codegen/writing/MembersInjectionMethods.java @@ -17,11 +17,15 @@ package dagger.internal.codegen.writing; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; +import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.MEMBERS_INJECTION_METHOD; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static javax.lang.model.element.Modifier.PRIVATE; +import static javax.lang.model.element.Modifier.STATIC; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; @@ -34,30 +38,35 @@ import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.MembersInjectionBinding; import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.javapoet.Expression; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod; -import dagger.model.Key; +import dagger.spi.model.Key; import java.util.LinkedHashMap; import java.util.Map; -import javax.lang.model.element.Name; +import javax.inject.Inject; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; /** Manages the member injection methods for a component. */ +@PerComponentImplementation final class MembersInjectionMethods { - private final Map<Key, MethodSpec> membersInjectionMethods = new LinkedHashMap<>(); + private final Map<Key, Expression> injectMethodExpressions = new LinkedHashMap<>(); + private final Map<Key, Expression> experimentalInjectMethodExpressions = new LinkedHashMap<>(); private final ComponentImplementation componentImplementation; - private final ComponentBindingExpressions bindingExpressions; + private final ComponentRequestRepresentations bindingExpressions; private final BindingGraph graph; private final DaggerElements elements; private final DaggerTypes types; private final KotlinMetadataUtil metadataUtil; + @Inject MembersInjectionMethods( ComponentImplementation componentImplementation, - ComponentBindingExpressions bindingExpressions, + ComponentRequestRepresentations bindingExpressions, BindingGraph graph, DaggerElements elements, DaggerTypes types, @@ -71,34 +80,77 @@ final class MembersInjectionMethods { } /** - * Returns the members injection {@link MethodSpec} for the given {@link Key}, creating it if + * Returns the members injection {@link Expression} for the given {@link Key}, creating it if * necessary. */ - MethodSpec getOrCreate(Key key) { - return reentrantComputeIfAbsent(membersInjectionMethods, key, this::membersInjectionMethod); + Expression getInjectExpression(Key key, CodeBlock instance, ClassName requestingClass) { + Binding binding = + graph.localMembersInjectionBinding(key).isPresent() + ? graph.localMembersInjectionBinding(key).get() + : graph.localContributionBinding(key).get(); + Expression expression = + reentrantComputeIfAbsent( + injectMethodExpressions, key, k -> injectMethodExpression(binding, false)); + ShardImplementation shardImplementation = componentImplementation.shardImplementation(binding); + return Expression.create( + expression.type(), + shardImplementation.name().equals(requestingClass) + ? CodeBlock.of("$L($L)", expression.codeBlock(), instance) + : CodeBlock.of( + "$L.$L($L)", + shardImplementation.shardFieldReference(), + expression.codeBlock(), + instance)); } - private MethodSpec membersInjectionMethod(Key key) { - Binding binding = - graph.membersInjectionBinding(key).isPresent() - ? graph.membersInjectionBinding(key).get() - : graph.contributionBinding(key); - TypeMirror keyType = binding.key().type(); + /** + * Returns the members injection {@link Expression} for the given {@link Key}, creating it if + * necessary. + */ + Expression getInjectExpressionExperimental( + ProvisionBinding provisionBinding, CodeBlock instance, ClassName requestingClass) { + checkState( + componentImplementation.compilerMode().isExperimentalMergedMode(), + "Compiler mode should be experimentalMergedMode!"); + Expression expression = + reentrantComputeIfAbsent( + experimentalInjectMethodExpressions, + provisionBinding.key(), + k -> injectMethodExpression(provisionBinding, true)); + return Expression.create( + expression.type(), CodeBlock.of("$L($L, dependencies)", expression.codeBlock(), instance)); + } + + private Expression injectMethodExpression(Binding binding, boolean useStaticInjectionMethod) { + // TODO(wanyingd): move Switching Providers and injection methods to Shard classes to avoid + // exceeding component class constant pool limit. + // Add to Component Shard so that is can be accessible from Switching Providers. + ShardImplementation shardImplementation = + useStaticInjectionMethod + ? componentImplementation.getComponentShard() + : componentImplementation.shardImplementation(binding); + TypeMirror keyType = binding.key().type().java(); TypeMirror membersInjectedType = - isTypeAccessibleFrom(keyType, componentImplementation.name().packageName()) + isTypeAccessibleFrom(keyType, shardImplementation.name().packageName()) ? keyType - : elements.getTypeElement(Object.class).asType(); + : elements.getTypeElement(TypeName.OBJECT).asType(); TypeName membersInjectedTypeName = TypeName.get(membersInjectedType); - Name bindingTypeName = binding.bindingTypeElement().get().getSimpleName(); + String bindingTypeName = getSimpleName(binding.bindingTypeElement().get()); // TODO(ronshapiro): include type parameters in this name e.g. injectFooOfT, and outer class // simple names Foo.Builder -> injectFooBuilder - String methodName = componentImplementation.getUniqueMethodName("inject" + bindingTypeName); + String methodName = shardImplementation.getUniqueMethodName("inject" + bindingTypeName); ParameterSpec parameter = ParameterSpec.builder(membersInjectedTypeName, "instance").build(); MethodSpec.Builder methodBuilder = - methodBuilder(methodName) - .addModifiers(PRIVATE) - .returns(membersInjectedTypeName) - .addParameter(parameter); + useStaticInjectionMethod + ? methodBuilder(methodName) + .addModifiers(PRIVATE, STATIC) + .returns(membersInjectedTypeName) + .addParameter(parameter) + .addParameter(Object[].class, "dependencies") + : methodBuilder(methodName) + .addModifiers(PRIVATE) + .returns(membersInjectedTypeName) + .addParameter(parameter); TypeElement canIgnoreReturnValue = elements.getTypeElement("com.google.errorprone.annotations.CanIgnoreReturnValue"); if (canIgnoreReturnValue != null) { @@ -108,20 +160,25 @@ final class MembersInjectionMethods { methodBuilder.addCode( InjectionSiteMethod.invokeAll( injectionSites(binding), - componentImplementation.name(), + shardImplementation.name(), instance, membersInjectedType, request -> - bindingExpressions - .getDependencyArgumentExpression(request, componentImplementation.name()) + (useStaticInjectionMethod + ? bindingExpressions + .getExperimentalSwitchingProviderDependencyRepresentation( + bindingRequest(request)) + .getDependencyExpression(request.kind(), (ProvisionBinding) binding) + : bindingExpressions.getDependencyArgumentExpression( + request, shardImplementation.name())) .codeBlock(), types, metadataUtil)); methodBuilder.addStatement("return $L", instance); MethodSpec method = methodBuilder.build(); - componentImplementation.addMethod(MEMBERS_INJECTION_METHOD, method); - return method; + shardImplementation.addMethod(MEMBERS_INJECTION_METHOD, method); + return Expression.create(membersInjectedType, CodeBlock.of("$N", method)); } private static ImmutableSet<InjectionSite> injectionSites(Binding binding) { diff --git a/java/dagger/internal/codegen/writing/MembersInjectionBindingExpression.java b/java/dagger/internal/codegen/writing/MembersInjectionRequestRepresentation.java index abf9d03d3..748be8007 100644 --- a/java/dagger/internal/codegen/writing/MembersInjectionBindingExpression.java +++ b/java/dagger/internal/codegen/writing/MembersInjectionRequestRepresentation.java @@ -16,27 +16,32 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.XTypeKt.isVoid; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.collect.Iterables.getOnlyElement; -import static javax.lang.model.type.TypeKind.VOID; +import androidx.room.compiler.processing.XMethodElement; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.ParameterSpec; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; import dagger.internal.codegen.binding.MembersInjectionBinding; import dagger.internal.codegen.javapoet.Expression; -import javax.lang.model.element.ExecutableElement; /** * A binding expression for members injection component methods. See {@link * MembersInjectionMethods}. */ -final class MembersInjectionBindingExpression extends BindingExpression { +final class MembersInjectionRequestRepresentation extends RequestRepresentation { private final MembersInjectionBinding binding; private final MembersInjectionMethods membersInjectionMethods; - MembersInjectionBindingExpression( - MembersInjectionBinding binding, MembersInjectionMethods membersInjectionMethods) { + @AssistedInject + MembersInjectionRequestRepresentation( + @Assisted MembersInjectionBinding binding, MembersInjectionMethods membersInjectionMethods) { this.binding = binding; this.membersInjectionMethods = membersInjectionMethods; } @@ -46,27 +51,36 @@ final class MembersInjectionBindingExpression extends BindingExpression { throw new UnsupportedOperationException(binding.toString()); } - // TODO(ronshapiro): This class doesn't need to be a BindingExpression, as + // TODO(ronshapiro): This class doesn't need to be a RequestRepresentation, as // getDependencyExpression() should never be called for members injection methods. It's probably // better suited as a method on MembersInjectionMethods @Override protected CodeBlock getComponentMethodImplementation( ComponentMethodDescriptor componentMethod, ComponentImplementation component) { - ExecutableElement methodElement = componentMethod.methodElement(); - ParameterSpec parameter = ParameterSpec.get(getOnlyElement(methodElement.getParameters())); + XMethodElement methodElement = componentMethod.methodElement(); + ParameterSpec parameter = + ParameterSpec.get(toJavac(getOnlyElement(methodElement.getParameters()))); if (binding.injectionSites().isEmpty()) { - return methodElement.getReturnType().getKind().equals(VOID) + return isVoid(methodElement.getReturnType()) ? CodeBlock.of("") : CodeBlock.of("return $N;", parameter); } else { - return methodElement.getReturnType().getKind().equals(VOID) - ? CodeBlock.of("$L;", membersInjectionInvocation(parameter)) - : CodeBlock.of("return $L;", membersInjectionInvocation(parameter)); + ClassName requestingClass = component.name(); + return isVoid(methodElement.getReturnType()) + ? CodeBlock.of("$L;", membersInjectionInvocation(parameter, requestingClass).codeBlock()) + : CodeBlock.of( + "return $L;", membersInjectionInvocation(parameter, requestingClass).codeBlock()); } } - CodeBlock membersInjectionInvocation(ParameterSpec target) { - return CodeBlock.of("$N($N)", membersInjectionMethods.getOrCreate(binding.key()), target); + private Expression membersInjectionInvocation(ParameterSpec target, ClassName requestingClass) { + return membersInjectionMethods.getInjectExpression( + binding.key(), CodeBlock.of("$N", target), requestingClass); + } + + @AssistedFactory + static interface Factory { + MembersInjectionRequestRepresentation create(MembersInjectionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java index 8630ce6ca..b90693604 100644 --- a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java +++ b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java @@ -16,6 +16,7 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Preconditions.checkState; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.MethodSpec.methodBuilder; @@ -27,6 +28,7 @@ import static dagger.internal.codegen.binding.SourceFiles.frameworkFieldUsages; import static dagger.internal.codegen.binding.SourceFiles.generateBindingFieldsForDependencies; import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; import static dagger.internal.codegen.binding.SourceFiles.parameterizedGeneratedTypeNameForBinding; +import static dagger.internal.codegen.extension.DaggerStreams.presentValues; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings; @@ -39,8 +41,11 @@ import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; @@ -55,16 +60,17 @@ import dagger.internal.codegen.base.UniqueNameSet; import dagger.internal.codegen.binding.FrameworkField; import dagger.internal.codegen.binding.MembersInjectionBinding; import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod; -import dagger.model.DependencyRequest; +import dagger.spi.model.DaggerAnnotation; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; import java.util.Map.Entry; -import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; /** * Generates {@link MembersInjector} implementations from {@link MembersInjectionBinding} instances. @@ -75,7 +81,7 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI @Inject MembersInjectorGenerator( - Filer filer, + XFiler filer, DaggerElements elements, DaggerTypes types, SourceVersion sourceVersion, @@ -86,7 +92,7 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI } @Override - public Element originatingElement(MembersInjectionBinding binding) { + public XElement originatingElement(MembersInjectionBinding binding) { return binding.membersInjectedType(); } @@ -117,9 +123,10 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI TypeSpec.Builder injectorTypeBuilder = classBuilder(generatedTypeName) .addModifiers(PUBLIC, FINAL) - .addTypeVariables(typeParameters); + .addTypeVariables(typeParameters) + .addAnnotation(qualifierMetadataAnnotation(binding)); - TypeName injectedTypeName = TypeName.get(binding.key().type()); + TypeName injectedTypeName = TypeName.get(binding.key().type().java()); TypeName implementedType = membersInjectorOf(injectedTypeName); injectorTypeBuilder.addSuperinterface(implementedType); @@ -159,7 +166,7 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI // If the dependency type is not visible to this members injector, then use the raw framework // type for the field. boolean useRawFrameworkType = - !isTypeAccessibleFrom(dependency.key().type(), generatedTypeName.packageName()); + !isTypeAccessibleFrom(dependency.key().type().java(), generatedTypeName.packageName()); String fieldName = fieldNames.getUniqueName(bindingField.name()); TypeName fieldType = useRawFrameworkType ? bindingField.type().rawType : bindingField.type(); @@ -198,7 +205,7 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI binding.injectionSites(), generatedTypeName, CodeBlock.of("instance"), - binding.key().type(), + binding.key().type().java(), frameworkFieldUsages(binding.dependencies(), dependencyFields)::get, types, metadataUtil)); @@ -209,7 +216,10 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI injectorTypeBuilder.addMethod(injectMembersBuilder.build()); for (InjectionSite injectionSite : binding.injectionSites()) { - if (injectionSite.element().getEnclosingElement().equals(binding.membersInjectedType())) { + if (injectionSite + .element() + .getEnclosingElement() + .equals(toJavac(binding.membersInjectedType()))) { injectorTypeBuilder.addMethod(InjectionSiteMethod.create(injectionSite, metadataUtil)); } } @@ -218,4 +228,26 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI return ImmutableList.of(injectorTypeBuilder); } + + private AnnotationSpec qualifierMetadataAnnotation(MembersInjectionBinding binding) { + AnnotationSpec.Builder builder = AnnotationSpec.builder(TypeNames.QUALIFIER_METADATA); + binding.injectionSites().stream() + // filter out non-local injection sites. Injection sites for super types will be in their + // own generated _MembersInjector class. + .filter( + injectionSite -> + injectionSite + .element() + .getEnclosingElement() + .equals(toJavac(binding.membersInjectedType()))) + .flatMap(injectionSite -> injectionSite.dependencies().stream()) + .map(DependencyRequest::key) + .map(Key::qualifier) + .flatMap(presentValues()) + .map(DaggerAnnotation::className) + .map(ClassName::canonicalName) + .distinct() + .forEach(qualifier -> builder.addMember("value", "$S", qualifier)); + return builder.build(); + } } diff --git a/java/dagger/internal/codegen/writing/MembersInjectorProviderCreationExpression.java b/java/dagger/internal/codegen/writing/MembersInjectorProviderCreationExpression.java index 534c0573b..e00db0176 100644 --- a/java/dagger/internal/codegen/writing/MembersInjectorProviderCreationExpression.java +++ b/java/dagger/internal/codegen/writing/MembersInjectorProviderCreationExpression.java @@ -24,7 +24,11 @@ import static dagger.internal.codegen.javapoet.TypeNames.MEMBERS_INJECTORS; import com.google.auto.common.MoreTypes; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; @@ -33,19 +37,24 @@ import javax.lang.model.type.TypeMirror; final class MembersInjectorProviderCreationExpression implements FrameworkInstanceCreationExpression { - private final ComponentBindingExpressions componentBindingExpressions; + private final ShardImplementation shardImplementation; + private final ComponentRequestRepresentations componentRequestRepresentations; private final ProvisionBinding binding; + @AssistedInject MembersInjectorProviderCreationExpression( - ProvisionBinding binding, ComponentBindingExpressions componentBindingExpressions) { + @Assisted ProvisionBinding binding, + ComponentImplementation componentImplementation, + ComponentRequestRepresentations componentRequestRepresentations) { this.binding = checkNotNull(binding); - this.componentBindingExpressions = checkNotNull(componentBindingExpressions); + this.shardImplementation = componentImplementation.shardImplementation(binding); + this.componentRequestRepresentations = checkNotNull(componentRequestRepresentations); } @Override public CodeBlock creationExpression() { TypeMirror membersInjectedType = - getOnlyElement(MoreTypes.asDeclared(binding.key().type()).getTypeArguments()); + getOnlyElement(MoreTypes.asDeclared(binding.key().type().java()).getTypeArguments()); boolean castThroughRawType = false; CodeBlock membersInjector; @@ -60,13 +69,16 @@ final class MembersInjectorProviderCreationExpression injectedTypeElement = MoreTypes.asTypeElement(injectedTypeElement.getSuperclass()); } - membersInjector = CodeBlock.of( - "$T.create($L)", - membersInjectorNameForType(injectedTypeElement), - componentBindingExpressions.getCreateMethodArgumentsCodeBlock(binding)); + membersInjector = + CodeBlock.of( + "$T.create($L)", + membersInjectorNameForType(injectedTypeElement), + componentRequestRepresentations.getCreateMethodArgumentsCodeBlock( + binding, shardImplementation.name())); } - // TODO(ronshapiro): consider adding a MembersInjectorBindingExpression to return this directly + // TODO(ronshapiro): consider adding a MembersInjectorRequestRepresentation to return this + // directly // (as it's rarely requested as a Provider). CodeBlock providerExpression = CodeBlock.of("$T.create($L)", INSTANCE_FACTORY, membersInjector); // If needed we cast through raw type around the InstanceFactory type as opposed to the @@ -75,19 +87,19 @@ final class MembersInjectorProviderCreationExpression // a second cast. If we just cast to the raw type InstanceFactory though, that becomes // assignable. return castThroughRawType - ? CodeBlock.of("($T) $L", INSTANCE_FACTORY, providerExpression) : providerExpression; + ? CodeBlock.of("($T) $L", INSTANCE_FACTORY, providerExpression) + : providerExpression; } private boolean hasLocalInjectionSites(TypeElement injectedTypeElement) { - return binding.injectionSites() - .stream() + return binding.injectionSites().stream() .anyMatch( injectionSite -> injectionSite.element().getEnclosingElement().equals(injectedTypeElement)); } - @Override - public boolean useInnerSwitchingProvider() { - return !binding.injectionSites().isEmpty(); + @AssistedFactory + static interface Factory { + MembersInjectorProviderCreationExpression create(ProvisionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/MethodBindingExpression.java b/java/dagger/internal/codegen/writing/MethodBindingExpression.java deleted file mode 100644 index 9c0c74aca..000000000 --- a/java/dagger/internal/codegen/writing/MethodBindingExpression.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright (C) 2018 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.writing; - -import static com.google.common.base.Preconditions.checkNotNull; -import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedParameterSpecs; -import static dagger.internal.codegen.javapoet.CodeBlocks.parameterNames; -import static dagger.internal.codegen.writing.ComponentImplementation.FieldSpecKind.PRIVATE_METHOD_SCOPED_FIELD; -import static javax.lang.model.element.Modifier.PRIVATE; -import static javax.lang.model.element.Modifier.VOLATILE; - -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.FieldSpec; -import com.squareup.javapoet.TypeName; -import dagger.internal.DoubleCheck; -import dagger.internal.MemoizedSentinel; -import dagger.internal.codegen.binding.BindingRequest; -import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; -import dagger.internal.codegen.binding.ContributionBinding; -import dagger.internal.codegen.binding.FrameworkField; -import dagger.internal.codegen.binding.KeyVariableNamer; -import dagger.internal.codegen.javapoet.Expression; -import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.BindingKind; -import dagger.model.RequestKind; -import java.util.Optional; -import javax.lang.model.type.TypeMirror; - -/** A binding expression that wraps another in a nullary method on the component. */ -abstract class MethodBindingExpression extends BindingExpression { - private final BindingRequest request; - private final ContributionBinding binding; - private final BindingMethodImplementation bindingMethodImplementation; - private final ComponentImplementation componentImplementation; - private final ProducerEntryPointView producerEntryPointView; - private final BindingExpression wrappedBindingExpression; - private final DaggerTypes types; - - protected MethodBindingExpression( - BindingRequest request, - ContributionBinding binding, - MethodImplementationStrategy methodImplementationStrategy, - BindingExpression wrappedBindingExpression, - ComponentImplementation componentImplementation, - DaggerTypes types) { - this.request = checkNotNull(request); - this.binding = checkNotNull(binding); - this.bindingMethodImplementation = bindingMethodImplementation(methodImplementationStrategy); - this.wrappedBindingExpression = checkNotNull(wrappedBindingExpression); - this.componentImplementation = checkNotNull(componentImplementation); - this.producerEntryPointView = new ProducerEntryPointView(types); - this.types = checkNotNull(types); - } - - @Override - Expression getDependencyExpression(ClassName requestingClass) { - if (request.frameworkType().isPresent()) { - // Initializing a framework instance that participates in a cycle requires that the underlying - // FrameworkInstanceBindingExpression is invoked in order for a cycle to be detected properly. - // When a MethodBindingExpression wraps a FrameworkInstanceBindingExpression, the wrapped - // expression will only be invoked once to implement the method body. This is a hack to work - // around that weirdness - methodImplementation.body() will invoke the framework instance - // initialization again in case the field is not fully initialized. - // TODO(b/121196706): use a less hacky approach to fix this bug - Object unused = methodBody(); - } - - addMethod(); - - CodeBlock methodCall = - binding.kind() == BindingKind.ASSISTED_INJECTION - // Private methods for assisted injection take assisted parameters as input. - ? CodeBlock.of( - "$N($L)", methodName(), parameterNames(assistedParameterSpecs(binding, types))) - : CodeBlock.of("$N()", methodName()); - - return Expression.create( - returnType(), - requestingClass.equals(componentImplementation.name()) - ? methodCall - : CodeBlock.of("$L.$L", componentImplementation.externalReferenceBlock(), methodCall)); - } - - @Override - Expression getDependencyExpressionForComponentMethod(ComponentMethodDescriptor componentMethod, - ComponentImplementation component) { - return producerEntryPointView - .getProducerEntryPointField(this, componentMethod, component) - .orElseGet( - () -> super.getDependencyExpressionForComponentMethod(componentMethod, component)); - } - - /** Adds the method to the component (if necessary) the first time it's called. */ - protected abstract void addMethod(); - - /** Returns the name of the method to call. */ - protected abstract String methodName(); - - /** The method's body. */ - protected final CodeBlock methodBody() { - return implementation( - wrappedBindingExpression.getDependencyExpression(componentImplementation.name()) - ::codeBlock); - } - - /** The method's body if this method is a component method. */ - protected final CodeBlock methodBodyForComponentMethod( - ComponentMethodDescriptor componentMethod) { - return implementation( - wrappedBindingExpression.getDependencyExpressionForComponentMethod( - componentMethod, componentImplementation) - ::codeBlock); - } - - private CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression) { - return bindingMethodImplementation.implementation(simpleBindingExpression); - } - - private BindingMethodImplementation bindingMethodImplementation( - MethodImplementationStrategy methodImplementationStrategy) { - switch (methodImplementationStrategy) { - case SIMPLE: - return new SimpleMethodImplementation(); - case SINGLE_CHECK: - return new SingleCheckedMethodImplementation(); - case DOUBLE_CHECK: - return new DoubleCheckedMethodImplementation(); - } - throw new AssertionError(methodImplementationStrategy); - } - - /** Returns the return type for the dependency request. */ - protected TypeMirror returnType() { - if (request.isRequestKind(RequestKind.INSTANCE) - && binding.contributedPrimitiveType().isPresent()) { - return binding.contributedPrimitiveType().get(); - } - - if (matchingComponentMethod().isPresent()) { - // Component methods are part of the user-defined API, and thus we must use the user-defined - // type. - return matchingComponentMethod().get().resolvedReturnType(types); - } - - TypeMirror requestedType = request.requestedType(binding.contributedType(), types); - return types.accessibleType(requestedType, componentImplementation.name()); - } - - private Optional<ComponentMethodDescriptor> matchingComponentMethod() { - return componentImplementation.componentDescriptor().firstMatchingComponentMethod(request); - } - - /** Strateg for implementing the body of this method. */ - enum MethodImplementationStrategy { - SIMPLE, - SINGLE_CHECK, - DOUBLE_CHECK, - ; - } - - private abstract static class BindingMethodImplementation { - /** - * Returns the method body, which contains zero or more statements (including semicolons). - * - * <p>If the implementation has a non-void return type, the body will also include the {@code - * return} statement. - * - * @param simpleBindingExpression the expression to retrieve an instance of this binding without - * the wrapping method. - */ - abstract CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression); - } - - /** Returns the {@code wrappedBindingExpression} directly. */ - private static final class SimpleMethodImplementation extends BindingMethodImplementation { - @Override - CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression) { - return CodeBlock.of("return $L;", simpleBindingExpression.get()); - } - } - - /** - * Defines a method body for single checked caching of the given {@code wrappedBindingExpression}. - */ - private final class SingleCheckedMethodImplementation extends BindingMethodImplementation { - private final Supplier<FieldSpec> field = Suppliers.memoize(this::createField); - - @Override - CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression) { - String fieldExpression = field.get().name.equals("local") ? "this.local" : field.get().name; - - CodeBlock.Builder builder = CodeBlock.builder() - .addStatement("Object local = $N", fieldExpression); - - if (isNullable()) { - builder.beginControlFlow("if (local instanceof $T)", MemoizedSentinel.class); - } else { - builder.beginControlFlow("if (local == null)"); - } - - return builder - .addStatement("local = $L", simpleBindingExpression.get()) - .addStatement("$N = ($T) local", fieldExpression, returnType()) - .endControlFlow() - .addStatement("return ($T) local", returnType()) - .build(); - } - - FieldSpec createField() { - String name = - componentImplementation.getUniqueFieldName( - request.isRequestKind(RequestKind.INSTANCE) - ? KeyVariableNamer.name(binding.key()) - : FrameworkField.forBinding(binding, Optional.empty()).name()); - - FieldSpec.Builder builder = FieldSpec.builder(fieldType(), name, PRIVATE, VOLATILE); - if (isNullable()) { - builder.initializer("new $T()", MemoizedSentinel.class); - } - - FieldSpec field = builder.build(); - componentImplementation.addField(PRIVATE_METHOD_SCOPED_FIELD, field); - return field; - } - - TypeName fieldType() { - if (isNullable()) { - // Nullable instances use `MemoizedSentinel` instead of `null` as the initialization value, - // so the field type must accept that and the return type - return TypeName.OBJECT; - } - TypeName returnType = TypeName.get(returnType()); - return returnType.isPrimitive() ? returnType.box() : returnType; - } - - private boolean isNullable() { - return request.isRequestKind(RequestKind.INSTANCE) && binding.isNullable(); - } - } - - /** - * Defines a method body for double checked caching of the given {@code wrappedBindingExpression}. - */ - private final class DoubleCheckedMethodImplementation extends BindingMethodImplementation { - private final Supplier<String> fieldName = Suppliers.memoize(this::createField); - - @Override - CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression) { - String fieldExpression = fieldName.get().equals("local") ? "this.local" : fieldName.get(); - return CodeBlock.builder() - .addStatement("$T local = $L", TypeName.OBJECT, fieldExpression) - .beginControlFlow("if (local instanceof $T)", MemoizedSentinel.class) - .beginControlFlow("synchronized (local)") - .addStatement("local = $L", fieldExpression) - .beginControlFlow("if (local instanceof $T)", MemoizedSentinel.class) - .addStatement("local = $L", simpleBindingExpression.get()) - .addStatement("$1L = $2T.reentrantCheck($1L, local)", fieldExpression, DoubleCheck.class) - .endControlFlow() - .endControlFlow() - .endControlFlow() - .addStatement("return ($T) local", returnType()) - .build(); - } - - private String createField() { - String name = - componentImplementation.getUniqueFieldName(KeyVariableNamer.name(binding.key())); - componentImplementation.addField( - PRIVATE_METHOD_SCOPED_FIELD, - FieldSpec.builder(TypeName.OBJECT, name, PRIVATE, VOLATILE) - .initializer("new $T()", MemoizedSentinel.class) - .build()); - return name; - } - } - -} diff --git a/java/dagger/internal/codegen/writing/MethodRequestRepresentation.java b/java/dagger/internal/codegen/writing/MethodRequestRepresentation.java new file mode 100644 index 000000000..714e5f074 --- /dev/null +++ b/java/dagger/internal/codegen/writing/MethodRequestRepresentation.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 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.writing; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; +import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; +import javax.lang.model.type.TypeMirror; + +/** A binding expression that wraps another in a nullary method on the component. */ +abstract class MethodRequestRepresentation extends RequestRepresentation { + private final ShardImplementation shardImplementation; + private final ProducerEntryPointView producerEntryPointView; + + protected MethodRequestRepresentation( + ShardImplementation shardImplementation, DaggerTypes types) { + this.shardImplementation = shardImplementation; + this.producerEntryPointView = new ProducerEntryPointView(shardImplementation, types); + } + + @Override + Expression getDependencyExpression(ClassName requestingClass) { + return Expression.create( + returnType(), + requestingClass.equals(shardImplementation.name()) + ? methodCall() + : CodeBlock.of("$L.$L", shardImplementation.shardFieldReference(), methodCall())); + } + + @Override + Expression getDependencyExpressionForComponentMethod( + ComponentMethodDescriptor componentMethod, ComponentImplementation component) { + return producerEntryPointView + .getProducerEntryPointField(this, componentMethod, component.name()) + .orElseGet( + () -> super.getDependencyExpressionForComponentMethod(componentMethod, component)); + } + + /** Returns the return type for the dependency request. */ + protected abstract TypeMirror returnType(); + + /** Returns the method call. */ + protected abstract CodeBlock methodCall(); +} diff --git a/java/dagger/internal/codegen/writing/ModuleProxies.java b/java/dagger/internal/codegen/writing/ModuleProxies.java index 6d174f824..a88f6edb3 100644 --- a/java/dagger/internal/codegen/writing/ModuleProxies.java +++ b/java/dagger/internal/codegen/writing/ModuleProxies.java @@ -16,98 +16,92 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; 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.langmodel.Accessibility.isElementAccessibleFrom; -import static javax.lang.model.element.Modifier.ABSTRACT; +import static dagger.internal.codegen.xprocessing.XTypeElements.isNested; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import static javax.lang.model.util.ElementFilter.constructorsIn; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.TypeSpec; +import dagger.internal.codegen.base.ModuleKind; import dagger.internal.codegen.base.SourceFileGenerator; -import dagger.internal.codegen.binding.ModuleKind; import dagger.internal.codegen.binding.SourceFiles; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.Accessibility; import dagger.internal.codegen.langmodel.DaggerElements; import java.util.Optional; -import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; /** Convenience methods for generating and using module constructor proxy methods. */ public final class ModuleProxies { private final DaggerElements elements; - private final KotlinMetadataUtil metadataUtil; - @Inject - public ModuleProxies(DaggerElements elements, KotlinMetadataUtil metadataUtil) { + ModuleProxies(DaggerElements elements) { this.elements = elements; - this.metadataUtil = metadataUtil; } /** Generates a {@code public static} proxy method for constructing module instances. */ // TODO(dpb): See if this can become a SourceFileGenerator<ModuleDescriptor> instead. Doing so may // cause ModuleProcessingStep to defer elements multiple times. public static final class ModuleConstructorProxyGenerator - extends SourceFileGenerator<TypeElement> { + extends SourceFileGenerator<XTypeElement> { private final ModuleProxies moduleProxies; - private final KotlinMetadataUtil metadataUtil; @Inject ModuleConstructorProxyGenerator( - Filer filer, + XFiler filer, DaggerElements elements, SourceVersion sourceVersion, - ModuleProxies moduleProxies, - KotlinMetadataUtil metadataUtil) { + ModuleProxies moduleProxies) { super(filer, elements, sourceVersion); this.moduleProxies = moduleProxies; - this.metadataUtil = metadataUtil; } @Override - public Element originatingElement(TypeElement moduleElement) { + public XElement originatingElement(XTypeElement moduleElement) { return moduleElement; } @Override - public ImmutableList<TypeSpec.Builder> topLevelTypes(TypeElement moduleElement) { - ModuleKind.checkIsModule(moduleElement, metadataUtil); + public ImmutableList<TypeSpec.Builder> topLevelTypes(XTypeElement moduleElement) { + ModuleKind.checkIsModule(moduleElement); return moduleProxies.nonPublicNullaryConstructor(moduleElement).isPresent() ? ImmutableList.of(buildProxy(moduleElement)) : ImmutableList.of(); } - private TypeSpec.Builder buildProxy(TypeElement moduleElement) { + private TypeSpec.Builder buildProxy(XTypeElement moduleElement) { return classBuilder(moduleProxies.constructorProxyTypeName(moduleElement)) .addModifiers(PUBLIC, FINAL) .addMethod(constructorBuilder().addModifiers(PRIVATE).build()) .addMethod( methodBuilder("newInstance") .addModifiers(PUBLIC, STATIC) - .returns(ClassName.get(moduleElement)) - .addStatement("return new $T()", moduleElement) + .returns(moduleElement.getClassName()) + .addStatement("return new $T()", moduleElement.getClassName()) .build()); } } /** The name of the class that hosts the module constructor proxy method. */ - private ClassName constructorProxyTypeName(TypeElement moduleElement) { - ModuleKind.checkIsModule(moduleElement, metadataUtil); - ClassName moduleClassName = ClassName.get(moduleElement); + private ClassName constructorProxyTypeName(XTypeElement moduleElement) { + ModuleKind.checkIsModule(moduleElement); + ClassName moduleClassName = moduleElement.getClassName(); return moduleClassName .topLevelClassName() .peerClass(SourceFiles.classFileName(moduleClassName) + "_Proxy"); @@ -118,14 +112,12 @@ public final class ModuleProxies { * has no arguments. If an implicit reference to the enclosing class exists, or the module is * abstract, no proxy method can be generated. */ - private Optional<ExecutableElement> nonPublicNullaryConstructor(TypeElement moduleElement) { - ModuleKind.checkIsModule(moduleElement, metadataUtil); - if (moduleElement.getModifiers().contains(ABSTRACT) - || (moduleElement.getNestingKind().isNested() - && !moduleElement.getModifiers().contains(STATIC))) { + private Optional<ExecutableElement> nonPublicNullaryConstructor(XTypeElement moduleElement) { + ModuleKind.checkIsModule(moduleElement); + if (moduleElement.isAbstract() || (isNested(moduleElement) && !moduleElement.isStatic())) { return Optional.empty(); } - return constructorsIn(elements.getAllMembers(moduleElement)).stream() + return constructorsIn(elements.getAllMembers(toJavac(moduleElement))).stream() .filter(constructor -> !Accessibility.isElementPubliclyAccessible(constructor)) .filter(constructor -> !constructor.getModifiers().contains(PRIVATE)) .filter(constructor -> constructor.getParameters().isEmpty()) @@ -137,14 +129,14 @@ public final class ModuleProxies { * constructor if it's accessible from {@code requestingClass} or else by invoking the * constructor's generated proxy method. */ - public CodeBlock newModuleInstance(TypeElement moduleElement, ClassName requestingClass) { - ModuleKind.checkIsModule(moduleElement, metadataUtil); + public CodeBlock newModuleInstance(XTypeElement moduleElement, ClassName requestingClass) { + ModuleKind.checkIsModule(moduleElement); String packageName = requestingClass.packageName(); return nonPublicNullaryConstructor(moduleElement) .filter(constructor -> !isElementAccessibleFrom(constructor, packageName)) .map( constructor -> CodeBlock.of("$T.newInstance()", constructorProxyTypeName(moduleElement))) - .orElse(CodeBlock.of("new $T()", moduleElement)); + .orElse(CodeBlock.of("new $T()", moduleElement.getClassName())); } } diff --git a/java/dagger/internal/codegen/writing/MultibindingFactoryCreationExpression.java b/java/dagger/internal/codegen/writing/MultibindingFactoryCreationExpression.java index 64e008ee1..034e835cc 100644 --- a/java/dagger/internal/codegen/writing/MultibindingFactoryCreationExpression.java +++ b/java/dagger/internal/codegen/writing/MultibindingFactoryCreationExpression.java @@ -22,36 +22,37 @@ import com.squareup.javapoet.CodeBlock; import dagger.internal.codegen.binding.BindingRequest; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.javapoet.CodeBlocks; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; -import dagger.model.DependencyRequest; +import dagger.spi.model.DependencyRequest; /** An abstract factory creation expression for multibindings. */ abstract class MultibindingFactoryCreationExpression implements FrameworkInstanceCreationExpression { - private final ComponentImplementation componentImplementation; - private final ComponentBindingExpressions componentBindingExpressions; + private final ShardImplementation shardImplementation; + private final ComponentRequestRepresentations componentRequestRepresentations; private final ContributionBinding binding; MultibindingFactoryCreationExpression( ContributionBinding binding, ComponentImplementation componentImplementation, - ComponentBindingExpressions componentBindingExpressions) { + ComponentRequestRepresentations componentRequestRepresentations) { this.binding = checkNotNull(binding); - this.componentImplementation = checkNotNull(componentImplementation); - this.componentBindingExpressions = checkNotNull(componentBindingExpressions); + this.shardImplementation = checkNotNull(componentImplementation).shardImplementation(binding); + this.componentRequestRepresentations = checkNotNull(componentRequestRepresentations); } /** Returns the expression for a dependency of this multibinding. */ protected final CodeBlock multibindingDependencyExpression(DependencyRequest dependency) { CodeBlock expression = - componentBindingExpressions + componentRequestRepresentations .getDependencyExpression( BindingRequest.bindingRequest(dependency.key(), binding.frameworkType()), - componentImplementation.name()) + shardImplementation.name()) .codeBlock(); return useRawType() - ? CodeBlocks.cast(expression, binding.frameworkType().frameworkClass()) + ? CodeBlocks.cast(expression, binding.frameworkType().frameworkClassName()) : expression; } @@ -65,11 +66,6 @@ abstract class MultibindingFactoryCreationExpression * component, and therefore a raw type must be used. */ protected final boolean useRawType() { - return !componentImplementation.isTypeAccessible(binding.key().type()); - } - - @Override - public final boolean useInnerSwitchingProvider() { - return !binding.dependencies().isEmpty(); + return !shardImplementation.isTypeAccessible(binding.key().type().java()); } } diff --git a/java/dagger/internal/codegen/writing/OptionalFactories.java b/java/dagger/internal/codegen/writing/OptionalFactories.java index 2d809633f..07b497bcd 100644 --- a/java/dagger/internal/codegen/writing/OptionalFactories.java +++ b/java/dagger/internal/codegen/writing/OptionalFactories.java @@ -27,7 +27,6 @@ import static com.squareup.javapoet.TypeSpec.classBuilder; import static dagger.internal.codegen.base.RequestKinds.requestTypeName; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; -import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER; import static dagger.internal.codegen.javapoet.TypeNames.abstractProducerOf; import static dagger.internal.codegen.javapoet.TypeNames.listenableFutureOf; import static dagger.internal.codegen.javapoet.TypeNames.providerOf; @@ -61,9 +60,11 @@ import dagger.internal.codegen.binding.BindingType; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.FrameworkType; import dagger.internal.codegen.javapoet.AnnotationSpecs; -import dagger.model.RequestKind; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; import dagger.producers.Producer; import dagger.producers.internal.Producers; +import dagger.spi.model.RequestKind; import java.util.Comparator; import java.util.Map; import java.util.Optional; @@ -74,37 +75,50 @@ import javax.inject.Provider; /** The nested class and static methods required by the component to implement optional bindings. */ // TODO(dpb): Name members simply if a component uses only one of Guava or JDK Optional. -@PerGeneratedFile final class OptionalFactories { - private final ComponentImplementation componentImplementation; - - @Inject OptionalFactories(@TopLevel ComponentImplementation componentImplementation) { - this.componentImplementation = componentImplementation; + /** Keeps track of the fields, methods, and classes already added to the generated file. */ + @PerGeneratedFile + static final class PerGeneratedFileCache { + /** + * The factory classes that implement {@code Provider<Optional<T>>} or {@code + * Producer<Optional<T>>} for present optional bindings for a given kind of dependency request + * within the component. + * + * <p>The key is the {@code Provider<Optional<T>>} type. + */ + private final Map<PresentFactorySpec, TypeSpec> presentFactoryClasses = + new TreeMap<>( + Comparator.comparing(PresentFactorySpec::valueKind) + .thenComparing(PresentFactorySpec::frameworkType) + .thenComparing(PresentFactorySpec::optionalKind)); + + /** + * The static methods that return a {@code Provider<Optional<T>>} that always returns an absent + * value. + */ + private final Map<OptionalKind, MethodSpec> absentOptionalProviderMethods = new TreeMap<>(); + + /** + * The static fields for {@code Provider<Optional<T>>} objects that always return an absent + * value. + */ + private final Map<OptionalKind, FieldSpec> absentOptionalProviderFields = new TreeMap<>(); + + @Inject + PerGeneratedFileCache() {} } - /** - * The factory classes that implement {@code Provider<Optional<T>>} or {@code - * Producer<Optional<T>>} for present optional bindings for a given kind of dependency request - * within the component. - * - * <p>The key is the {@code Provider<Optional<T>>} type. - */ - private final Map<PresentFactorySpec, TypeSpec> presentFactoryClasses = - new TreeMap<>( - Comparator.comparing(PresentFactorySpec::valueKind) - .thenComparing(PresentFactorySpec::frameworkType) - .thenComparing(PresentFactorySpec::optionalKind)); + private final PerGeneratedFileCache perGeneratedFileCache; + private final ShardImplementation rootComponentShard; - /** - * The static methods that return a {@code Provider<Optional<T>>} that always returns an absent - * value. - */ - private final Map<OptionalKind, MethodSpec> absentOptionalProviderMethods = new TreeMap<>(); - - /** - * The static fields for {@code Provider<Optional<T>>} objects that always return an absent value. - */ - private final Map<OptionalKind, FieldSpec> absentOptionalProviderFields = new TreeMap<>(); + @Inject + OptionalFactories( + PerGeneratedFileCache perGeneratedFileCache, + ComponentImplementation componentImplementation) { + this.perGeneratedFileCache = perGeneratedFileCache; + this.rootComponentShard = + componentImplementation.rootComponentImplementation().getComponentShard(); + } /** * Returns an expression that calls a static method that returns a {@code Provider<Optional<T>>} @@ -118,11 +132,11 @@ final class OptionalFactories { OptionalKind optionalKind = OptionalType.from(binding.key()).kind(); return CodeBlock.of( "$N()", - absentOptionalProviderMethods.computeIfAbsent( + perGeneratedFileCache.absentOptionalProviderMethods.computeIfAbsent( optionalKind, kind -> { MethodSpec method = absentOptionalProviderMethod(kind); - componentImplementation.addMethod(ABSENT_OPTIONAL_METHOD, method); + rootComponentShard.addMethod(ABSENT_OPTIONAL_METHOD, method); return method; })); } @@ -141,20 +155,20 @@ final class OptionalFactories { .returns(providerOf(optionalKind.of(typeVariable))) .addJavadoc( "Returns a {@link $T} that returns {@code $L}.", - Provider.class, + TypeNames.PROVIDER, optionalKind.absentValueExpression()) .addCode("$L // safe covariant cast\n", AnnotationSpecs.suppressWarnings(UNCHECKED)) - .addCode( - "$1T provider = ($1T) $2N;", + .addStatement( + "$1T provider = ($1T) $2N", providerOf(optionalKind.of(typeVariable)), - absentOptionalProviderFields.computeIfAbsent( + perGeneratedFileCache.absentOptionalProviderFields.computeIfAbsent( optionalKind, kind -> { FieldSpec field = absentOptionalProviderField(kind); - componentImplementation.addField(ABSENT_OPTIONAL_FIELD, field); + rootComponentShard.addField(ABSENT_OPTIONAL_FIELD, field); return field; })) - .addCode("return provider;") + .addStatement("return provider") .build(); } @@ -164,7 +178,7 @@ final class OptionalFactories { */ private FieldSpec absentOptionalProviderField(OptionalKind optionalKind) { return FieldSpec.builder( - PROVIDER, + TypeNames.PROVIDER, String.format("ABSENT_%s_PROVIDER", optionalKind.name()), PRIVATE, STATIC, @@ -173,7 +187,7 @@ final class OptionalFactories { .initializer("$T.create($L)", InstanceFactory.class, optionalKind.absentValueExpression()) .addJavadoc( "A {@link $T} that returns {@code $L}.", - Provider.class, + TypeNames.PROVIDER, optionalKind.absentValueExpression()) .build(); } @@ -258,7 +272,7 @@ final class OptionalFactories { return new StringBuilder("Present") .append(UPPER_UNDERSCORE.to(UPPER_CAMEL, optionalKind().name())) .append(UPPER_UNDERSCORE.to(UPPER_CAMEL, valueKind().toString())) - .append(frameworkType().frameworkClass().getSimpleName()) + .append(frameworkType().frameworkClassName().simpleName()) .toString(); } @@ -296,11 +310,11 @@ final class OptionalFactories { CodeBlock presentOptionalFactory(ContributionBinding binding, CodeBlock delegateFactory) { return CodeBlock.of( "$N.of($L)", - presentFactoryClasses.computeIfAbsent( + perGeneratedFileCache.presentFactoryClasses.computeIfAbsent( PresentFactorySpec.of(binding), spec -> { TypeSpec type = presentOptionalFactoryClass(spec); - componentImplementation.addType(PRESENT_FACTORY, type); + rootComponentShard.addType(PRESENT_FACTORY, type); return type; }), delegateFactory); diff --git a/java/dagger/internal/codegen/writing/OptionalFactoryInstanceCreationExpression.java b/java/dagger/internal/codegen/writing/OptionalFactoryInstanceCreationExpression.java index 593921ef9..df9d15ef6 100644 --- a/java/dagger/internal/codegen/writing/OptionalFactoryInstanceCreationExpression.java +++ b/java/dagger/internal/codegen/writing/OptionalFactoryInstanceCreationExpression.java @@ -20,11 +20,14 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; /** - * A {@link FrameworkInstanceCreationExpression} for {@link dagger.model.BindingKind#OPTIONAL + * A {@link FrameworkInstanceCreationExpression} for {@link dagger.spi.model.BindingKind#OPTIONAL * optional bindings}. */ final class OptionalFactoryInstanceCreationExpression @@ -32,17 +35,18 @@ final class OptionalFactoryInstanceCreationExpression private final OptionalFactories optionalFactories; private final ContributionBinding binding; private final ComponentImplementation componentImplementation; - private final ComponentBindingExpressions componentBindingExpressions; + private final ComponentRequestRepresentations componentRequestRepresentations; + @AssistedInject OptionalFactoryInstanceCreationExpression( + @Assisted ContributionBinding binding, OptionalFactories optionalFactories, - ContributionBinding binding, ComponentImplementation componentImplementation, - ComponentBindingExpressions componentBindingExpressions) { + ComponentRequestRepresentations componentRequestRepresentations) { this.optionalFactories = optionalFactories; this.binding = binding; this.componentImplementation = componentImplementation; - this.componentBindingExpressions = componentBindingExpressions; + this.componentRequestRepresentations = componentRequestRepresentations; } @Override @@ -51,18 +55,16 @@ final class OptionalFactoryInstanceCreationExpression ? optionalFactories.absentOptionalProvider(binding) : optionalFactories.presentOptionalFactory( binding, - componentBindingExpressions + componentRequestRepresentations .getDependencyExpression( bindingRequest( getOnlyElement(binding.dependencies()).key(), binding.frameworkType()), - componentImplementation.name()) + componentImplementation.shardImplementation(binding).name()) .codeBlock()); } - @Override - public boolean useInnerSwitchingProvider() { - // Share providers for empty optionals from OptionalFactories so we don't have numerous - // switch cases that all return Optional.empty(). - return !binding.dependencies().isEmpty(); + @AssistedFactory + static interface Factory { + OptionalFactoryInstanceCreationExpression create(ContributionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/OptionalBindingExpression.java b/java/dagger/internal/codegen/writing/OptionalRequestRepresentation.java index 4faf3fa6f..caef3a694 100644 --- a/java/dagger/internal/codegen/writing/OptionalBindingExpression.java +++ b/java/dagger/internal/codegen/writing/OptionalRequestRepresentation.java @@ -22,33 +22,38 @@ import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFr import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.base.OptionalType; import dagger.internal.codegen.base.OptionalType.OptionalKind; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.javapoet.Expression; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.DependencyRequest; -import javax.inject.Inject; +import dagger.spi.model.DependencyRequest; import javax.lang.model.SourceVersion; /** A binding expression for optional bindings. */ -final class OptionalBindingExpression extends SimpleInvocationBindingExpression { +final class OptionalRequestRepresentation extends RequestRepresentation { private final ProvisionBinding binding; - private final ComponentBindingExpressions componentBindingExpressions; + private final ComponentRequestRepresentations componentRequestRepresentations; private final DaggerTypes types; private final SourceVersion sourceVersion; + private final boolean isExperimentalMergedMode; - @Inject - OptionalBindingExpression( - ProvisionBinding binding, - ComponentBindingExpressions componentBindingExpressions, + @AssistedInject + OptionalRequestRepresentation( + @Assisted ProvisionBinding binding, + ComponentImplementation componentImplementation, + ComponentRequestRepresentations componentRequestRepresentations, DaggerTypes types, SourceVersion sourceVersion) { - super(binding); this.binding = binding; - this.componentBindingExpressions = componentBindingExpressions; + this.componentRequestRepresentations = componentRequestRepresentations; this.types = types; this.sourceVersion = sourceVersion; + this.isExperimentalMergedMode = + componentImplementation.compilerMode().isExperimentalMergedMode(); } @Override @@ -62,33 +67,39 @@ final class OptionalBindingExpression extends SimpleInvocationBindingExpression // issues // when used as an argument to some members injection proxy methods (see // https://github.com/google/dagger/issues/916) - if (isTypeAccessibleFrom(binding.key().type(), requestingClass.packageName())) { + if (isTypeAccessibleFrom(binding.key().type().java(), requestingClass.packageName())) { return Expression.create( - binding.key().type(), optionalKind.parameterizedAbsentValueExpression(optionalType)); + binding.key().type().java(), + optionalKind.parameterizedAbsentValueExpression(optionalType)); } } - return Expression.create(binding.key().type(), optionalKind.absentValueExpression()); + return Expression.create(binding.key().type().java(), optionalKind.absentValueExpression()); } DependencyRequest dependency = getOnlyElement(binding.dependencies()); CodeBlock dependencyExpression = - componentBindingExpressions - .getDependencyExpression(bindingRequest(dependency), requestingClass) - .codeBlock(); + isExperimentalMergedMode + ? componentRequestRepresentations + .getExperimentalSwitchingProviderDependencyRepresentation( + bindingRequest(dependency)) + .getDependencyExpression(dependency.kind(), binding) + .codeBlock() + : componentRequestRepresentations + .getDependencyExpression(bindingRequest(dependency), requestingClass) + .codeBlock(); // If the dependency type is inaccessible, then we have to use Optional.<Object>of(...), or else // we will get "incompatible types: inference variable has incompatible bounds. - return isTypeAccessibleFrom(dependency.key().type(), requestingClass.packageName()) + return isTypeAccessibleFrom(dependency.key().type().java(), requestingClass.packageName()) ? Expression.create( - binding.key().type(), optionalKind.presentExpression(dependencyExpression)) + binding.key().type().java(), optionalKind.presentExpression(dependencyExpression)) : Expression.create( - types.erasure(binding.key().type()), + types.erasure(binding.key().type().java()), optionalKind.presentObjectExpression(dependencyExpression)); } - @Override - boolean requiresMethodEncapsulation() { - // TODO(dpb): Maybe require it for present bindings. - return false; + @AssistedFactory + static interface Factory { + OptionalRequestRepresentation create(ProvisionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/PrivateMethodBindingExpression.java b/java/dagger/internal/codegen/writing/PrivateMethodRequestRepresentation.java index b6502ea3f..1a828ba77 100644 --- a/java/dagger/internal/codegen/writing/PrivateMethodBindingExpression.java +++ b/java/dagger/internal/codegen/writing/PrivateMethodRequestRepresentation.java @@ -16,81 +16,98 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; import static com.squareup.javapoet.MethodSpec.methodBuilder; -import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedParameterSpecs; import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.PRIVATE_METHOD; import static javax.lang.model.element.Modifier.PRIVATE; -import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.TypeName; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.BindingRequest; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.BindingKind; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; +import dagger.spi.model.RequestKind; +import javax.lang.model.type.TypeMirror; /** * A binding expression that wraps the dependency expressions in a private, no-arg method. * * <p>Dependents of this binding expression will just call the no-arg private method. */ -final class PrivateMethodBindingExpression extends MethodBindingExpression { +final class PrivateMethodRequestRepresentation extends MethodRequestRepresentation { + private final ShardImplementation shardImplementation; private final ContributionBinding binding; private final BindingRequest request; - private final ComponentImplementation componentImplementation; + private final RequestRepresentation wrappedRequestRepresentation; private final CompilerOptions compilerOptions; private final DaggerTypes types; private String methodName; - PrivateMethodBindingExpression( - BindingRequest request, - ContributionBinding binding, - MethodImplementationStrategy methodImplementationStrategy, - BindingExpression wrappedBindingExpression, + @AssistedInject + PrivateMethodRequestRepresentation( + @Assisted BindingRequest request, + @Assisted ContributionBinding binding, + @Assisted RequestRepresentation wrappedRequestRepresentation, ComponentImplementation componentImplementation, DaggerTypes types, CompilerOptions compilerOptions) { - super( - request, - binding, - methodImplementationStrategy, - wrappedBindingExpression, - componentImplementation, - types); - this.binding = binding; + super(componentImplementation.shardImplementation(binding), types); + this.binding = checkNotNull(binding); this.request = checkNotNull(request); - this.componentImplementation = checkNotNull(componentImplementation); - this.compilerOptions = checkNotNull(compilerOptions); + this.wrappedRequestRepresentation = checkNotNull(wrappedRequestRepresentation); + this.shardImplementation = componentImplementation.shardImplementation(binding); + this.compilerOptions = compilerOptions; this.types = types; } @Override - protected void addMethod() { + protected CodeBlock methodCall() { + return CodeBlock.of("$N()", methodName()); + } + + @Override + protected TypeMirror returnType() { + if (request.isRequestKind(RequestKind.INSTANCE) + && binding.contributedPrimitiveType().isPresent()) { + return toJavac(binding.contributedPrimitiveType().get()); + } + + TypeMirror requestedType = request.requestedType(binding.contributedType(), types); + return types.accessibleType(requestedType, shardImplementation.name()); + } + + private String methodName() { if (methodName == null) { // Have to set methodName field before implementing the method in order to handle recursion. - methodName = componentImplementation.getUniqueMethodName(request); + methodName = shardImplementation.getUniqueMethodName(request); // TODO(bcorso): Fix the order that these generated methods are written to the component. - componentImplementation.addMethod( + shardImplementation.addMethod( PRIVATE_METHOD, methodBuilder(methodName) .addModifiers(PRIVATE) - .addParameters( - // Private methods for assisted injection take assisted parameters as input. - binding.kind() == BindingKind.ASSISTED_INJECTION - ? assistedParameterSpecs(binding, types) - : ImmutableList.of()) .returns(TypeName.get(returnType())) - .addCode(methodBody()) + .addStatement( + "return $L", + wrappedRequestRepresentation + .getDependencyExpression(shardImplementation.name()) + .codeBlock()) .build()); } + return methodName; } - @Override - protected String methodName() { - checkState(methodName != null, "addMethod() must be called before methodName()"); - return methodName; + @AssistedFactory + static interface Factory { + PrivateMethodRequestRepresentation create( + BindingRequest request, + ContributionBinding binding, + RequestRepresentation wrappedRequestRepresentation); } } diff --git a/java/dagger/internal/codegen/writing/ProducerCreationExpression.java b/java/dagger/internal/codegen/writing/ProducerCreationExpression.java index 0d1ccf3b8..8e8c27197 100644 --- a/java/dagger/internal/codegen/writing/ProducerCreationExpression.java +++ b/java/dagger/internal/codegen/writing/ProducerCreationExpression.java @@ -20,7 +20,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ContributionBinding; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; /** @@ -30,13 +34,18 @@ import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstan // TODO(dpb): Resolve with InjectionOrProvisionProviderCreationExpression. final class ProducerCreationExpression implements FrameworkInstanceCreationExpression { - private final ComponentBindingExpressions componentBindingExpressions; + private final ShardImplementation shardImplementation; + private final ComponentRequestRepresentations componentRequestRepresentations; private final ContributionBinding binding; + @AssistedInject ProducerCreationExpression( - ContributionBinding binding, ComponentBindingExpressions componentBindingExpressions) { + @Assisted ContributionBinding binding, + ComponentImplementation componentImplementation, + ComponentRequestRepresentations componentRequestRepresentations) { this.binding = checkNotNull(binding); - this.componentBindingExpressions = checkNotNull(componentBindingExpressions); + this.shardImplementation = componentImplementation.shardImplementation(binding); + this.componentRequestRepresentations = checkNotNull(componentRequestRepresentations); } @Override @@ -44,6 +53,12 @@ final class ProducerCreationExpression implements FrameworkInstanceCreationExpre return CodeBlock.of( "$T.create($L)", generatedClassNameForBinding(binding), - componentBindingExpressions.getCreateMethodArgumentsCodeBlock(binding)); + componentRequestRepresentations.getCreateMethodArgumentsCodeBlock( + binding, shardImplementation.name())); + } + + @AssistedFactory + static interface Factory { + ProducerCreationExpression create(ContributionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/ProducerEntryPointView.java b/java/dagger/internal/codegen/writing/ProducerEntryPointView.java index c58f4e0bf..271da51e3 100644 --- a/java/dagger/internal/codegen/writing/ProducerEntryPointView.java +++ b/java/dagger/internal/codegen/writing/ProducerEntryPointView.java @@ -17,18 +17,22 @@ package dagger.internal.codegen.writing; import static dagger.internal.codegen.writing.ComponentImplementation.FieldSpecKind.FRAMEWORK_FIELD; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static javax.lang.model.element.Modifier.PRIVATE; +import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.TypeName; import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.RequestKind; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; import dagger.producers.Producer; import dagger.producers.internal.CancellationListener; import dagger.producers.internal.Producers; +import dagger.spi.model.RequestKind; import java.util.Optional; import javax.lang.model.type.TypeMirror; @@ -37,9 +41,11 @@ import javax.lang.model.type.TypeMirror; * views} of {@link Producer}s. */ final class ProducerEntryPointView { + private final ShardImplementation shardImplementation; private final DaggerTypes types; - ProducerEntryPointView(DaggerTypes types) { + ProducerEntryPointView(ShardImplementation shardImplementation, DaggerTypes types) { + this.shardImplementation = shardImplementation; this.types = types; } @@ -49,22 +55,20 @@ final class ProducerEntryPointView { * Producer} or {@link com.google.common.util.concurrent.ListenableFuture}. * * <p>This is intended to be a replacement implementation for {@link - * dagger.internal.codegen.writing.BindingExpression#getDependencyExpressionForComponentMethod(ComponentMethodDescriptor, + * dagger.internal.codegen.writing.RequestRepresentation#getDependencyExpressionForComponentMethod(ComponentMethodDescriptor, * ComponentImplementation)}, and in cases where {@link Optional#empty()} is returned, callers * should call {@code super.getDependencyExpressionForComponentMethod()}. */ Optional<Expression> getProducerEntryPointField( - BindingExpression producerExpression, + RequestRepresentation producerExpression, ComponentMethodDescriptor componentMethod, - ComponentImplementation component) { - if (component.componentDescriptor().isProduction() + ClassName requestingClass) { + if (shardImplementation.componentDescriptor().isProduction() && (componentMethod.dependencyRequest().get().kind().equals(RequestKind.FUTURE) || componentMethod.dependencyRequest().get().kind().equals(RequestKind.PRODUCER))) { + MemberSelect field = createField(producerExpression, componentMethod); return Optional.of( - Expression.create( - fieldType(componentMethod), - "$N", - createField(producerExpression, componentMethod, component))); + Expression.create(fieldType(componentMethod), field.getExpressionFor(requestingClass))); } else { // If the component isn't a production component, it won't implement CancellationListener and // as such we can't create an entry point. But this binding must also just be a Producer from @@ -75,36 +79,43 @@ final class ProducerEntryPointView { } } - private FieldSpec createField( - BindingExpression producerExpression, - ComponentMethodDescriptor componentMethod, - ComponentImplementation component) { + private MemberSelect createField( + RequestRepresentation producerExpression, ComponentMethodDescriptor componentMethod) { // TODO(cgdecker): Use a FrameworkFieldInitializer for this? // Though I don't think we need the once-only behavior of that, since I think // getComponentMethodImplementation will only be called once anyway - String methodName = componentMethod.methodElement().getSimpleName().toString(); + String methodName = getSimpleName(componentMethod.methodElement()); FieldSpec field = FieldSpec.builder( TypeName.get(fieldType(componentMethod)), - component.getUniqueFieldName(methodName + "EntryPoint"), + shardImplementation.getUniqueFieldName(methodName + "EntryPoint"), PRIVATE) .build(); - component.addField(FRAMEWORK_FIELD, field); + shardImplementation.addField(FRAMEWORK_FIELD, field); CodeBlock fieldInitialization = CodeBlock.of( - "this.$N = $T.entryPointViewOf($L, this);", + "this.$N = $T.entryPointViewOf($L, $L);", field, Producers.class, - producerExpression.getDependencyExpression(component.name()).codeBlock()); - component.addInitialization(fieldInitialization); + producerExpression.getDependencyExpression(shardImplementation.name()).codeBlock(), + // Always pass in the componentShard reference here rather than the owning shard for + // this key because this needs to be the root CancellationListener. + shardImplementation.isComponentShard() + ? "this" + : shardImplementation + .getComponentImplementation() + .getComponentShard() + .shardFieldReference()); + shardImplementation.addInitialization(fieldInitialization); - return field; + return MemberSelect.localField(shardImplementation, field.name); } // TODO(cgdecker): Can we use producerExpression.getDependencyExpression().type() instead of // needing to (re)compute this? private TypeMirror fieldType(ComponentMethodDescriptor componentMethod) { - return types.wrapType(componentMethod.dependencyRequest().get().key().type(), Producer.class); + return types.wrapType( + componentMethod.dependencyRequest().get().key().type().java(), TypeNames.PRODUCER); } } diff --git a/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java b/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java index a013be940..1a92ba639 100644 --- a/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java +++ b/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java @@ -16,6 +16,7 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Verify.verifyNotNull; import static com.squareup.javapoet.ClassName.OBJECT; @@ -40,13 +41,16 @@ import static dagger.internal.codegen.javapoet.TypeNames.listenableFutureOf; import static dagger.internal.codegen.javapoet.TypeNames.producedOf; import static dagger.internal.codegen.writing.GwtCompatibility.gwtIncompatibleAnnotation; import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PROTECTED; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; -import com.google.common.collect.FluentIterable; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XType; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -70,21 +74,18 @@ import dagger.internal.codegen.javapoet.AnnotationSpecs; import dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.model.DependencyRequest; -import dagger.model.Key; -import dagger.model.RequestKind; import dagger.producers.Producer; import dagger.producers.internal.AbstractProducesMethodProducer; import dagger.producers.internal.Producers; +import dagger.spi.model.DependencyRequest; +import dagger.spi.model.Key; +import dagger.spi.model.RequestKind; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.Optional; -import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeMirror; /** Generates {@link Producer} implementations from {@link ProductionBinding} instances. */ public final class ProducerFactoryGenerator extends SourceFileGenerator<ProductionBinding> { @@ -93,7 +94,7 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti @Inject ProducerFactoryGenerator( - Filer filer, + XFiler filer, DaggerElements elements, SourceVersion sourceVersion, CompilerOptions compilerOptions, @@ -103,9 +104,8 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti this.keyFactory = keyFactory; } - @Override - public Element originatingElement(ProductionBinding binding) { + public XElement originatingElement(ProductionBinding binding) { // we only create factories for bindings that have a binding element return binding.bindingElement().get(); } @@ -116,7 +116,7 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti checkArgument(!binding.unresolved().isPresent()); checkArgument(binding.bindingElement().isPresent()); - TypeName providedTypeName = TypeName.get(binding.contributedType()); + TypeName providedTypeName = binding.contributedType().getTypeName(); TypeName futureTypeName = listenableFutureOf(providedTypeName); ClassName generatedTypeName = generatedClassNameForBinding(binding); @@ -137,7 +137,7 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti factoryBuilder, constructorBuilder, uniqueFieldNames.getUniqueName("module"), - TypeName.get(binding.bindingTypeElement().get().asType()))) + binding.bindingTypeElement().get().getType().getTypeName())) : Optional.empty(); List<CodeBlock> frameworkFieldAssignments = new ArrayList<>(); @@ -206,7 +206,7 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(futureTransform.applyArgType(), futureTransform.applyArgName()) - .addExceptions(getThrownTypeNames(binding.thrownTypes())) + .addExceptions(binding.thrownTypes().stream().map(XType::getTypeName).collect(toList())) .addCode( getInvocationCodeBlock( binding, providedTypeName, futureTransform.parameterCodeBlocks())); @@ -295,15 +295,15 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti "$S", String.format( "%s#%s", - ClassName.get(binding.bindingTypeElement().get()), - binding.bindingElement().get().getSimpleName())) + binding.bindingTypeElement().get().getClassName(), + toJavac(binding.bindingElement().get()).getSimpleName())) : CodeBlock.of("$T.class", generatedTypeName); return CodeBlock.of("$T.create($L)", PRODUCER_TOKEN, producerTokenArgs); } /** Returns a name of the variable representing this dependency's future. */ private static String dependencyFutureName(DependencyRequest dependency) { - return dependency.requestElement().get().getSimpleName() + "Future"; + return dependency.requestElement().get().java().getSimpleName() + "Future"; } /** Represents the transformation of an input future by a producer method. */ @@ -405,7 +405,7 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti @Override String applyArgName() { - String argName = asyncDependency.requestElement().get().getSimpleName().toString(); + String argName = asyncDependency.requestElement().get().java().getSimpleName().toString(); if (argName.equals("module")) { return "moduleArg"; } @@ -495,7 +495,7 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti } private static TypeName asyncDependencyType(DependencyRequest dependency) { - TypeName keyName = TypeName.get(dependency.key().type()); + TypeName keyName = TypeName.get(dependency.key().type().java()); switch (dependency.kind()) { case INSTANCE: return keyName; @@ -523,8 +523,8 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti "$L.$L($L)", binding.requiresModuleInstance() ? "module" - : CodeBlock.of("$T", ClassName.get(binding.bindingTypeElement().get())), - binding.bindingElement().get().getSimpleName(), + : CodeBlock.of("$T", binding.bindingTypeElement().get().getClassName()), + toJavac(binding.bindingElement().get()).getSimpleName(), makeParametersCodeBlock(parameterCodeBlocks)); final CodeBlock returnCodeBlock; @@ -545,16 +545,6 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti return CodeBlock.of("return $L;", returnCodeBlock); } - /** - * Converts the list of thrown types into type names. - * - * @param thrownTypes the list of thrown types. - */ - private FluentIterable<? extends TypeName> getThrownTypeNames( - Iterable<? extends TypeMirror> thrownTypes) { - return FluentIterable.from(thrownTypes).transform(TypeName::get); - } - @Override protected ImmutableSet<Suppression> warningSuppressions() { // TODO(beder): examine if we can remove this or prevent subtypes of Future from being produced diff --git a/java/dagger/internal/codegen/writing/ProducerFromProviderCreationExpression.java b/java/dagger/internal/codegen/writing/ProducerFromProviderCreationExpression.java index 9b0d4e8ba..d38561395 100644 --- a/java/dagger/internal/codegen/writing/ProducerFromProviderCreationExpression.java +++ b/java/dagger/internal/codegen/writing/ProducerFromProviderCreationExpression.java @@ -16,43 +16,37 @@ package dagger.internal.codegen.writing; -import static com.google.common.base.Preconditions.checkNotNull; -import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; -import dagger.internal.codegen.binding.ContributionBinding; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.FrameworkType; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; -import dagger.model.RequestKind; import dagger.producers.Producer; +import dagger.spi.model.RequestKind; import java.util.Optional; /** An {@link Producer} creation expression for provision bindings. */ final class ProducerFromProviderCreationExpression implements FrameworkInstanceCreationExpression { - private final ContributionBinding binding; - private final ComponentImplementation componentImplementation; - private final ComponentBindingExpressions componentBindingExpressions; + private final RequestRepresentation providerRequestRepresentation; + private final ClassName requestingClass; + @AssistedInject ProducerFromProviderCreationExpression( - ContributionBinding binding, - ComponentImplementation componentImplementation, - ComponentBindingExpressions componentBindingExpressions) { - this.binding = checkNotNull(binding); - this.componentImplementation = checkNotNull(componentImplementation); - this.componentBindingExpressions = checkNotNull(componentBindingExpressions); + @Assisted RequestRepresentation providerRequestRepresentation, + @Assisted ClassName requestingClass) { + this.providerRequestRepresentation = providerRequestRepresentation; + this.requestingClass = requestingClass; } @Override public CodeBlock creationExpression() { return FrameworkType.PROVIDER.to( RequestKind.PRODUCER, - componentBindingExpressions - .getDependencyExpression( - bindingRequest(binding.key(), FrameworkType.PROVIDER), - componentImplementation.name()) - .codeBlock()); + providerRequestRepresentation.getDependencyExpression(requestingClass).codeBlock()); } @Override @@ -60,5 +54,9 @@ final class ProducerFromProviderCreationExpression implements FrameworkInstanceC return Optional.of(TypeNames.PRODUCER); } - // TODO(ronshapiro): should this have a simple factory if the delegate expression is simple? + @AssistedFactory + static interface Factory { + ProducerFromProviderCreationExpression create( + RequestRepresentation providerRequestRepresentation, ClassName requestingClass); + } } diff --git a/java/dagger/internal/codegen/writing/ProducerNodeInstanceBindingExpression.java b/java/dagger/internal/codegen/writing/ProducerNodeInstanceRequestRepresentation.java index 0d3f7e876..dafb05245 100644 --- a/java/dagger/internal/codegen/writing/ProducerNodeInstanceBindingExpression.java +++ b/java/dagger/internal/codegen/writing/ProducerNodeInstanceRequestRepresentation.java @@ -16,34 +16,39 @@ package dagger.internal.codegen.writing; -import static com.google.common.base.Preconditions.checkNotNull; - import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.FrameworkType; import dagger.internal.codegen.javapoet.Expression; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Key; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; +import dagger.producers.internal.Producers; +import dagger.spi.model.Key; /** Binding expression for producer node instances. */ -final class ProducerNodeInstanceBindingExpression extends FrameworkInstanceBindingExpression { - /** The component defining this binding. */ - private final ComponentImplementation componentImplementation; +final class ProducerNodeInstanceRequestRepresentation + extends FrameworkInstanceRequestRepresentation { + private final ShardImplementation shardImplementation; private final Key key; private final ProducerEntryPointView producerEntryPointView; - ProducerNodeInstanceBindingExpression( - ContributionBinding binding, - FrameworkInstanceSupplier frameworkInstanceSupplier, + @AssistedInject + ProducerNodeInstanceRequestRepresentation( + @Assisted ContributionBinding binding, + @Assisted FrameworkInstanceSupplier frameworkInstanceSupplier, DaggerTypes types, DaggerElements elements, ComponentImplementation componentImplementation) { super(binding, frameworkInstanceSupplier, types, elements); - this.componentImplementation = checkNotNull(componentImplementation); + this.shardImplementation = componentImplementation.shardImplementation(binding); this.key = binding.key(); - this.producerEntryPointView = new ProducerEntryPointView(types); + this.producerEntryPointView = new ProducerEntryPointView(shardImplementation, types); } @Override @@ -54,7 +59,13 @@ final class ProducerNodeInstanceBindingExpression extends FrameworkInstanceBindi @Override Expression getDependencyExpression(ClassName requestingClass) { Expression result = super.getDependencyExpression(requestingClass); - componentImplementation.addCancellableProducerKey(key); + shardImplementation.addCancellation( + key, + CodeBlock.of( + "$T.cancel($L, $N);", + Producers.class, + result.codeBlock(), + ComponentImplementation.MAY_INTERRUPT_IF_RUNNING_PARAM)); return result; } @@ -62,8 +73,14 @@ final class ProducerNodeInstanceBindingExpression extends FrameworkInstanceBindi Expression getDependencyExpressionForComponentMethod( ComponentMethodDescriptor componentMethod, ComponentImplementation component) { return producerEntryPointView - .getProducerEntryPointField(this, componentMethod, component) + .getProducerEntryPointField(this, componentMethod, component.name()) .orElseGet( () -> super.getDependencyExpressionForComponentMethod(componentMethod, component)); } + + @AssistedFactory + static interface Factory { + ProducerNodeInstanceRequestRepresentation create( + ContributionBinding binding, FrameworkInstanceSupplier frameworkInstanceSupplier); + } } diff --git a/java/dagger/internal/codegen/writing/ProductionBindingRepresentation.java b/java/dagger/internal/codegen/writing/ProductionBindingRepresentation.java new file mode 100644 index 000000000..f2da69083 --- /dev/null +++ b/java/dagger/internal/codegen/writing/ProductionBindingRepresentation.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 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.writing; + +import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; +import static dagger.internal.codegen.writing.BindingRepresentations.scope; +import static dagger.spi.model.BindingKind.MULTIBOUND_MAP; +import static dagger.spi.model.BindingKind.MULTIBOUND_SET; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.internal.codegen.binding.BindingRequest; +import dagger.internal.codegen.binding.FrameworkType; +import dagger.internal.codegen.binding.ProductionBinding; +import dagger.internal.codegen.langmodel.DaggerTypes; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * A binding representation that wraps code generation methods that satisfy all kinds of request for + * that binding. + */ +final class ProductionBindingRepresentation implements BindingRepresentation { + private final ProductionBinding binding; + private final DerivedFromFrameworkInstanceRequestRepresentation.Factory + derivedFromFrameworkInstanceRequestRepresentationFactory; + private final RequestRepresentation frameworkInstanceRequestRepresentation; + private final Map<BindingRequest, RequestRepresentation> requestRepresentations = new HashMap<>(); + + @AssistedInject + ProductionBindingRepresentation( + @Assisted ProductionBinding binding, + ComponentImplementation componentImplementation, + DerivedFromFrameworkInstanceRequestRepresentation.Factory + derivedFromFrameworkInstanceRequestRepresentationFactory, + ProducerNodeInstanceRequestRepresentation.Factory + producerNodeInstanceRequestRepresentationFactory, + UnscopedFrameworkInstanceCreationExpressionFactory + unscopedFrameworkInstanceCreationExpressionFactory, + DaggerTypes types) { + this.binding = binding; + this.derivedFromFrameworkInstanceRequestRepresentationFactory = + derivedFromFrameworkInstanceRequestRepresentationFactory; + Optional<MemberSelect> staticMethod = staticFactoryCreation(); + FrameworkInstanceSupplier frameworkInstanceSupplier = + staticMethod.isPresent() + ? staticMethod::get + : new FrameworkFieldInitializer( + componentImplementation, + binding, + binding.scope().isPresent() + ? scope( + binding, unscopedFrameworkInstanceCreationExpressionFactory.create(binding)) + : unscopedFrameworkInstanceCreationExpressionFactory.create(binding)); + this.frameworkInstanceRequestRepresentation = + producerNodeInstanceRequestRepresentationFactory.create(binding, frameworkInstanceSupplier); + } + + @Override + public RequestRepresentation getRequestRepresentation(BindingRequest request) { + return reentrantComputeIfAbsent( + requestRepresentations, request, this::getRequestRepresentationUncached); + } + + private RequestRepresentation getRequestRepresentationUncached(BindingRequest request) { + return request.frameworkType().isPresent() + ? frameworkInstanceRequestRepresentation + : derivedFromFrameworkInstanceRequestRepresentationFactory.create( + binding, + frameworkInstanceRequestRepresentation, + request.requestKind(), + FrameworkType.PRODUCER_NODE); + } + /** + * If {@code resolvedBindings} is an unscoped provision binding with no factory arguments, then we + * don't need a field to hold its factory. In that case, this method returns the static member + * select that returns the factory. + */ + private Optional<MemberSelect> staticFactoryCreation() { + if (binding.dependencies().isEmpty()) { + if (binding.kind().equals(MULTIBOUND_MAP)) { + return Optional.of(StaticMemberSelects.emptyMapFactory(binding)); + } + if (binding.kind().equals(MULTIBOUND_SET)) { + return Optional.of(StaticMemberSelects.emptySetFactory(binding)); + } + } + return Optional.empty(); + } + + @AssistedFactory + static interface Factory { + ProductionBindingRepresentation create(ProductionBinding binding); + } +} diff --git a/java/dagger/internal/codegen/writing/ProviderInstanceBindingExpression.java b/java/dagger/internal/codegen/writing/ProviderInstanceRequestRepresentation.java index 400c6a259..53c49d1de 100644 --- a/java/dagger/internal/codegen/writing/ProviderInstanceBindingExpression.java +++ b/java/dagger/internal/codegen/writing/ProviderInstanceRequestRepresentation.java @@ -16,28 +16,34 @@ package dagger.internal.codegen.writing; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.FrameworkType; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; /** Binding expression for provider instances. */ -final class ProviderInstanceBindingExpression extends FrameworkInstanceBindingExpression { +final class ProviderInstanceRequestRepresentation extends FrameworkInstanceRequestRepresentation { - ProviderInstanceBindingExpression( - ContributionBinding binding, - FrameworkInstanceSupplier frameworkInstanceSupplier, + @AssistedInject + ProviderInstanceRequestRepresentation( + @Assisted ContributionBinding binding, + @Assisted FrameworkInstanceSupplier frameworkInstanceSupplier, DaggerTypes types, DaggerElements elements) { - super( - binding, - frameworkInstanceSupplier, - types, - elements); + super(binding, frameworkInstanceSupplier, types, elements); } @Override protected FrameworkType frameworkType() { return FrameworkType.PROVIDER; } + + @AssistedFactory + static interface Factory { + ProviderInstanceRequestRepresentation create( + ContributionBinding binding, FrameworkInstanceSupplier frameworkInstanceSupplier); + } } diff --git a/java/dagger/internal/codegen/writing/ProviderInstanceSupplier.java b/java/dagger/internal/codegen/writing/ProviderInstanceSupplier.java new file mode 100644 index 000000000..d95d4e2e6 --- /dev/null +++ b/java/dagger/internal/codegen/writing/ProviderInstanceSupplier.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 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.writing; + +import static dagger.internal.codegen.writing.BindingRepresentations.scope; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; + +/** An object that initializes a framework-type component field for a binding. */ +final class ProviderInstanceSupplier implements FrameworkInstanceSupplier { + private final FrameworkInstanceSupplier frameworkInstanceSupplier; + + @AssistedInject + ProviderInstanceSupplier( + @Assisted ProvisionBinding binding, + ComponentImplementation componentImplementation, + FrameworkInstanceBindingRepresentation.Factory frameworkInstanceBindingRepresentationFactory, + UnscopedFrameworkInstanceCreationExpressionFactory + unscopedFrameworkInstanceCreationExpressionFactory) { + FrameworkInstanceCreationExpression frameworkInstanceCreationExpression = + unscopedFrameworkInstanceCreationExpressionFactory.create(binding); + this.frameworkInstanceSupplier = + new FrameworkFieldInitializer( + componentImplementation, + binding, + binding.scope().isPresent() + ? scope(binding, frameworkInstanceCreationExpression) + : frameworkInstanceCreationExpression); + } + + @Override + public MemberSelect memberSelect() { + return frameworkInstanceSupplier.memberSelect(); + } + + @AssistedFactory + static interface Factory { + ProviderInstanceSupplier create(ProvisionBinding binding); + } +} diff --git a/java/dagger/internal/codegen/writing/ProvisionBindingRepresentation.java b/java/dagger/internal/codegen/writing/ProvisionBindingRepresentation.java new file mode 100644 index 000000000..0085d32cf --- /dev/null +++ b/java/dagger/internal/codegen/writing/ProvisionBindingRepresentation.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2021 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.writing; + +import static dagger.internal.codegen.writing.DelegateRequestRepresentation.isBindsScopeStrongerThanDependencyScope; +import static dagger.spi.model.BindingKind.DELEGATE; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.internal.codegen.binding.BindingGraph; +import dagger.internal.codegen.binding.BindingRequest; +import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.compileroption.CompilerOptions; +import dagger.internal.codegen.langmodel.DaggerTypes; +import dagger.internal.codegen.writing.ComponentImplementation.CompilerMode; +import dagger.spi.model.RequestKind; + +/** + * A binding representation that wraps code generation methods that satisfy all kinds of request for + * that binding. + */ +final class ProvisionBindingRepresentation implements BindingRepresentation { + private final BindingGraph graph; + private final CompilerMode compilerMode; + private final ProvisionBinding binding; + private final DirectInstanceBindingRepresentation directInstanceBindingRepresentation; + private final FrameworkInstanceBindingRepresentation frameworkInstanceBindingRepresentation; + + @AssistedInject + ProvisionBindingRepresentation( + @Assisted ProvisionBinding binding, + BindingGraph graph, + ComponentImplementation componentImplementation, + DirectInstanceBindingRepresentation.Factory directInstanceBindingRepresentationFactory, + FrameworkInstanceBindingRepresentation.Factory frameworkInstanceBindingRepresentationFactory, + SwitchingProviderInstanceSupplier.Factory switchingProviderInstanceSupplierFactory, + ProviderInstanceSupplier.Factory providerInstanceSupplierFactory, + StaticFactoryInstanceSupplier.Factory staticFactoryInstanceSupplierFactory, + CompilerOptions compilerOptions, + DaggerTypes types) { + this.binding = binding; + this.graph = graph; + this.compilerMode = componentImplementation.compilerMode(); + this.directInstanceBindingRepresentation = + directInstanceBindingRepresentationFactory.create(binding); + FrameworkInstanceSupplier frameworkInstanceSupplier = null; + switch (FrameworkInstanceKind.from(binding, compilerMode)) { + case SWITCHING_PROVIDER: + case EXPERIMENTAL_SWITCHING_PROVIDER: + frameworkInstanceSupplier = switchingProviderInstanceSupplierFactory.create(binding); + break; + case STATIC_FACTORY: + frameworkInstanceSupplier = staticFactoryInstanceSupplierFactory.create(binding); + break; + case PROVIDER_FIELD: + frameworkInstanceSupplier = providerInstanceSupplierFactory.create(binding); + break; + } + this.frameworkInstanceBindingRepresentation = + frameworkInstanceBindingRepresentationFactory.create(binding, frameworkInstanceSupplier); + } + + @Override + public RequestRepresentation getRequestRepresentation(BindingRequest request) { + return usesDirectInstanceExpression(request.requestKind()) + ? directInstanceBindingRepresentation.getRequestRepresentation(request) + : frameworkInstanceBindingRepresentation.getRequestRepresentation(request); + } + + private boolean usesDirectInstanceExpression(RequestKind requestKind) { + if (compilerMode.isExperimentalMergedMode()) { + return false; + } + if (requestKind != RequestKind.INSTANCE && requestKind != RequestKind.FUTURE) { + return false; + } + + // In fast init mode, we can avoid generating direct instance expressions if a framework + // instance expression already exists in the graph. Default mode has more edge cases, so can not + // be handled with simple pre-check in the graph. For example, a provider for a subcomponent + // builder is backed with its direct instance, returning framework instance for both cases will + // form a loop. There are also difficulties introduced by manually created framework requests. + // TODO(wanyingd): refactor framework instance so that we don't need to generate both direct + // instance and framework instance representation for the same binding. + if (compilerMode.isFastInit() && graph.topLevelBindingGraph().hasFrameworkRequest(binding)) { + return false; + } + + switch (binding.kind()) { + case MEMBERS_INJECTOR: + // Currently, we always use a framework instance for MembersInjectors, e.g. + // InstanceFactory.create(Foo_MembersInjector.create(...)). + // TODO(b/199889259): Consider optimizing this for fastInit mode. + case ASSISTED_FACTORY: + // Assisted factory binding can be requested with framework request, and it is essentially a + // provider for assisted injection binding. So we will always return framework instance for + // assisted factory bindings. + return false; + case ASSISTED_INJECTION: + throw new IllegalStateException( + "Assisted injection binding shouldn't be requested with an instance request."); + default: + // We don't need to use Provider#get() if there's no caching, so use a direct instance. + // TODO(bcorso): This can be optimized in cases where we know a Provider field already + // exists, in which case even if it's not scoped we might as well call Provider#get(). + return !needsCaching(binding, graph); + } + } + + /** + * Returns {@code true} if the component needs to make sure the provided value is cached. + * + * <p>The component needs to cache the value for scoped bindings except for {@code @Binds} + * bindings whose scope is no stronger than their delegate's. + */ + static boolean needsCaching(ProvisionBinding binding, BindingGraph graph) { + if (!binding.scope().isPresent()) { + return false; + } + if (binding.kind().equals(DELEGATE)) { + return isBindsScopeStrongerThanDependencyScope(binding, graph); + } + return true; + } + + @AssistedFactory + static interface Factory { + ProvisionBindingRepresentation create(ProvisionBinding binding); + } +} diff --git a/java/dagger/internal/codegen/writing/BindingExpression.java b/java/dagger/internal/codegen/writing/RequestRepresentation.java index bd45f603e..66c31dc62 100644 --- a/java/dagger/internal/codegen/writing/BindingExpression.java +++ b/java/dagger/internal/codegen/writing/RequestRepresentation.java @@ -23,7 +23,7 @@ import dagger.internal.codegen.javapoet.Expression; /** A factory of code expressions used to access a single request for a binding in a component. */ // TODO(bcorso): Rename this to RequestExpression? -abstract class BindingExpression { +abstract class RequestRepresentation { /** * Returns an expression that evaluates to the value of a request based on the given requesting @@ -43,11 +43,6 @@ abstract class BindingExpression { return getDependencyExpression(component.name()); } - /** Returns {@code true} if this binding expression should be encapsulated in a method. */ - boolean requiresMethodEncapsulation() { - return false; - } - /** * Returns an expression for the implementation of a component method with the given request. * diff --git a/java/dagger/internal/codegen/writing/SetFactoryCreationExpression.java b/java/dagger/internal/codegen/writing/SetFactoryCreationExpression.java index 754f909ad..624a41269 100644 --- a/java/dagger/internal/codegen/writing/SetFactoryCreationExpression.java +++ b/java/dagger/internal/codegen/writing/SetFactoryCreationExpression.java @@ -20,27 +20,31 @@ import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.binding.SourceFiles.setFactoryClassName; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.base.ContributionType; import dagger.internal.codegen.base.SetType; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.BindingType; import dagger.internal.codegen.binding.ContributionBinding; -import dagger.model.DependencyRequest; -import dagger.producers.Produced; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.spi.model.DependencyRequest; /** A factory creation expression for a multibound set. */ final class SetFactoryCreationExpression extends MultibindingFactoryCreationExpression { private final BindingGraph graph; private final ContributionBinding binding; + @AssistedInject SetFactoryCreationExpression( - ContributionBinding binding, + @Assisted ContributionBinding binding, ComponentImplementation componentImplementation, - ComponentBindingExpressions componentBindingExpressions, + ComponentRequestRepresentations componentRequestRepresentations, BindingGraph graph) { - super(binding, componentImplementation, componentBindingExpressions); + super(binding, componentImplementation, componentRequestRepresentations); this.binding = checkNotNull(binding); - this.graph = checkNotNull(graph); + this.graph = graph; } @Override @@ -50,9 +54,9 @@ final class SetFactoryCreationExpression extends MultibindingFactoryCreationExpr SetType setType = SetType.from(binding.key()); builder.add( "<$T>", - setType.elementsAreTypeOf(Produced.class) - ? setType.unwrappedElementType(Produced.class) - : setType.elementType()); + setType.elementsAreTypeOf(TypeNames.PRODUCED) + ? setType.unwrappedElementType(TypeNames.PRODUCED).getTypeName() + : setType.elementType().getTypeName()); } int individualProviders = 0; @@ -89,4 +93,9 @@ final class SetFactoryCreationExpression extends MultibindingFactoryCreationExpr return builder.add(".build()").build(); } + + @AssistedFactory + static interface Factory { + SetFactoryCreationExpression create(ContributionBinding binding); + } } diff --git a/java/dagger/internal/codegen/writing/SetBindingExpression.java b/java/dagger/internal/codegen/writing/SetRequestRepresentation.java index 0b2e11a9a..7fec86aee 100644 --- a/java/dagger/internal/codegen/writing/SetBindingExpression.java +++ b/java/dagger/internal/codegen/writing/SetRequestRepresentation.java @@ -16,48 +16,58 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; import static javax.lang.model.util.ElementFilter.methodsIn; +import androidx.room.compiler.processing.XType; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.SetBuilder; import dagger.internal.codegen.base.ContributionType; import dagger.internal.codegen.base.SetType; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.javapoet.CodeBlocks; import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.DependencyRequest; +import dagger.spi.model.DependencyRequest; import java.util.Collections; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; /** A binding expression for multibound sets. */ -final class SetBindingExpression extends SimpleInvocationBindingExpression { +final class SetRequestRepresentation extends RequestRepresentation { private final ProvisionBinding binding; private final BindingGraph graph; - private final ComponentBindingExpressions componentBindingExpressions; + private final ComponentRequestRepresentations componentRequestRepresentations; private final DaggerTypes types; private final DaggerElements elements; + private final boolean isExperimentalMergedMode; - SetBindingExpression( - ProvisionBinding binding, + @AssistedInject + SetRequestRepresentation( + @Assisted ProvisionBinding binding, BindingGraph graph, - ComponentBindingExpressions componentBindingExpressions, + ComponentImplementation componentImplementation, + ComponentRequestRepresentations componentRequestRepresentations, DaggerTypes types, DaggerElements elements) { - super(binding); this.binding = binding; this.graph = graph; - this.componentBindingExpressions = componentBindingExpressions; + this.componentRequestRepresentations = componentRequestRepresentations; this.types = types; this.elements = elements; + this.isExperimentalMergedMode = + componentImplementation.compilerMode().isExperimentalMergedMode(); } @Override @@ -120,27 +130,48 @@ final class SetBindingExpression extends SimpleInvocationBindingExpression { } instantiation.add(".build()"); return Expression.create( - isImmutableSetAvailable ? immutableSetType() : binding.key().type(), + isImmutableSetAvailable ? immutableSetType() : binding.key().type().java(), instantiation.build()); } } private DeclaredType immutableSetType() { return types.getDeclaredType( - elements.getTypeElement(ImmutableSet.class), SetType.from(binding.key()).elementType()); + elements.getTypeElement(TypeNames.IMMUTABLE_SET), + toJavac(SetType.from(binding.key()).elementType())); } private CodeBlock getContributionExpression( DependencyRequest dependency, ClassName requestingClass) { - return componentBindingExpressions - .getDependencyExpression(bindingRequest(dependency), requestingClass) - .codeBlock(); + RequestRepresentation bindingExpression = + componentRequestRepresentations.getRequestRepresentation(bindingRequest(dependency)); + CodeBlock expression = + isExperimentalMergedMode + ? componentRequestRepresentations + .getExperimentalSwitchingProviderDependencyRepresentation( + bindingRequest(dependency)) + .getDependencyExpression(dependency.kind(), binding) + .codeBlock() + : bindingExpression.getDependencyExpression(requestingClass).codeBlock(); + + // TODO(b/211774331): Type casting should be Set after contributions to Set multibinding are + // limited to be Set. + // Add a cast to "(Collection)" when the contribution is a raw "Provider" type because the + // "addAll()" method expects a collection. For example, ".addAll((Collection) + // provideInaccessibleSetOfFoo.get())" + return (!isSingleValue(dependency) + && !isTypeAccessibleFrom(binding.key().type().java(), requestingClass.packageName()) + // TODO(wanyingd): Replace instanceof checks with validation on the binding. + && (bindingExpression instanceof DerivedFromFrameworkInstanceRequestRepresentation + || bindingExpression instanceof DelegateRequestRepresentation)) + ? CodeBlocks.cast(expression, TypeNames.COLLECTION) + : expression; } private Expression collectionsStaticFactoryInvocation( ClassName requestingClass, CodeBlock methodInvocation) { return Expression.create( - binding.key().type(), + binding.key().type().java(), CodeBlock.builder() .add("$T.", Collections.class) .add(maybeTypeParameter(requestingClass)) @@ -149,9 +180,9 @@ final class SetBindingExpression extends SimpleInvocationBindingExpression { } private CodeBlock maybeTypeParameter(ClassName requestingClass) { - TypeMirror elementType = SetType.from(binding.key()).elementType(); - return isTypeAccessibleFrom(elementType, requestingClass.packageName()) - ? CodeBlock.of("<$T>", elementType) + XType elementType = SetType.from(binding.key()).elementType(); + return isTypeAccessibleFrom(toJavac(elementType), requestingClass.packageName()) + ? CodeBlock.of("<$T>", elementType.getTypeName()) : CodeBlock.of(""); } @@ -163,7 +194,7 @@ final class SetBindingExpression extends SimpleInvocationBindingExpression { private boolean isImmutableSetBuilderWithExpectedSizeAvailable() { if (isImmutableSetAvailable()) { - return methodsIn(elements.getTypeElement(ImmutableSet.class).getEnclosedElements()) + return methodsIn(elements.getTypeElement(TypeNames.IMMUTABLE_SET).getEnclosedElements()) .stream() .anyMatch(method -> method.getSimpleName().contentEquals("builderWithExpectedSize")); } @@ -171,6 +202,11 @@ final class SetBindingExpression extends SimpleInvocationBindingExpression { } private boolean isImmutableSetAvailable() { - return elements.getTypeElement(ImmutableSet.class) != null; + return elements.getTypeElement(TypeNames.IMMUTABLE_SET) != null; + } + + @AssistedFactory + static interface Factory { + SetRequestRepresentation create(ProvisionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/SimpleInvocationBindingExpression.java b/java/dagger/internal/codegen/writing/SimpleInvocationBindingExpression.java deleted file mode 100644 index f2062f4e8..000000000 --- a/java/dagger/internal/codegen/writing/SimpleInvocationBindingExpression.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2016 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.writing; - -import static com.google.common.base.Preconditions.checkNotNull; - -import dagger.internal.codegen.binding.ContributionBinding; - -/** A simple binding expression for instance requests. Does not scope. */ -abstract class SimpleInvocationBindingExpression extends BindingExpression { - private final ContributionBinding binding; - - SimpleInvocationBindingExpression(ContributionBinding binding) { - this.binding = checkNotNull(binding); - } - - @Override - boolean requiresMethodEncapsulation() { - return !binding.dependencies().isEmpty(); - } -} diff --git a/java/dagger/internal/codegen/writing/SimpleMethodBindingExpression.java b/java/dagger/internal/codegen/writing/SimpleMethodRequestRepresentation.java index 82e662857..20c6ac6cb 100644 --- a/java/dagger/internal/codegen/writing/SimpleMethodBindingExpression.java +++ b/java/dagger/internal/codegen/writing/SimpleMethodRequestRepresentation.java @@ -16,58 +16,64 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.auto.common.MoreElements.asExecutable; import static com.google.auto.common.MoreElements.asType; import static com.google.common.base.Preconditions.checkArgument; +import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; import static dagger.internal.codegen.javapoet.TypeNames.rawTypeName; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; import static dagger.internal.codegen.writing.InjectionMethods.ProvisionMethod.requiresInjectionMethod; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.common.MoreTypes; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ComponentRequirement; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.javapoet.Expression; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; -import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; import dagger.internal.codegen.writing.InjectionMethods.ProvisionMethod; -import dagger.model.DependencyRequest; +import dagger.spi.model.DependencyRequest; import java.util.Optional; import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; /** * A binding expression that invokes methods or constructors directly (without attempting to scope) - * {@link dagger.model.RequestKind#INSTANCE} requests. + * {@link dagger.spi.model.RequestKind#INSTANCE} requests. */ -final class SimpleMethodBindingExpression extends SimpleInvocationBindingExpression { +final class SimpleMethodRequestRepresentation extends RequestRepresentation { private final CompilerOptions compilerOptions; private final ProvisionBinding provisionBinding; - private final ComponentBindingExpressions componentBindingExpressions; + private final ComponentRequestRepresentations componentRequestRepresentations; private final MembersInjectionMethods membersInjectionMethods; private final ComponentRequirementExpressions componentRequirementExpressions; - private final DaggerElements elements; private final SourceVersion sourceVersion; private final KotlinMetadataUtil metadataUtil; + private final ShardImplementation shardImplementation; + private final boolean isExperimentalMergedMode; - SimpleMethodBindingExpression( - ProvisionBinding binding, - CompilerOptions compilerOptions, - ComponentBindingExpressions componentBindingExpressions, + @AssistedInject + SimpleMethodRequestRepresentation( + @Assisted ProvisionBinding binding, MembersInjectionMethods membersInjectionMethods, + CompilerOptions compilerOptions, + ComponentRequestRepresentations componentRequestRepresentations, ComponentRequirementExpressions componentRequirementExpressions, - DaggerElements elements, SourceVersion sourceVersion, - KotlinMetadataUtil metadataUtil) { - super(binding); + KotlinMetadataUtil metadataUtil, + ComponentImplementation componentImplementation, + ExperimentalSwitchingProviders switchingProviders) { this.compilerOptions = compilerOptions; this.provisionBinding = binding; this.metadataUtil = metadataUtil; @@ -75,11 +81,13 @@ final class SimpleMethodBindingExpression extends SimpleInvocationBindingExpress provisionBinding.implicitDependencies().isEmpty(), "framework deps are not currently supported"); checkArgument(provisionBinding.bindingElement().isPresent()); - this.componentBindingExpressions = componentBindingExpressions; + this.componentRequestRepresentations = componentRequestRepresentations; this.membersInjectionMethods = membersInjectionMethods; this.componentRequirementExpressions = componentRequirementExpressions; - this.elements = elements; this.sourceVersion = sourceVersion; + this.shardImplementation = componentImplementation.shardImplementation(binding); + this.isExperimentalMergedMode = + componentImplementation.compilerMode().isExperimentalMergedMode(); } @Override @@ -96,8 +104,8 @@ final class SimpleMethodBindingExpression extends SimpleInvocationBindingExpress ProvisionMethod.invokeArguments( provisionBinding, request -> dependencyArgument(request, requestingClass).codeBlock(), - requestingClass)); - ExecutableElement method = asExecutable(provisionBinding.bindingElement().get()); + shardImplementation::getUniqueFieldNameForAssistedParam)); + ExecutableElement method = asExecutable(toJavac(provisionBinding.bindingElement().get())); CodeBlock invocation; switch (method.getKind()) { case CONSTRUCTOR: @@ -111,9 +119,11 @@ final class SimpleMethodBindingExpression extends SimpleInvocationBindingExpress } else if (metadataUtil.isObjectClass(asType(method.getEnclosingElement()))) { // Call through the singleton instance. // See: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#static-methods - module = CodeBlock.of("$T.INSTANCE", provisionBinding.bindingTypeElement().get()); + module = + CodeBlock.of( + "$T.INSTANCE", provisionBinding.bindingTypeElement().get().getClassName()); } else { - module = CodeBlock.of("$T", provisionBinding.bindingTypeElement().get()); + module = CodeBlock.of("$T", provisionBinding.bindingTypeElement().get().getClassName()); } invocation = CodeBlock.of("$L.$L($L)", module, method.getSimpleName(), arguments); break; @@ -125,7 +135,7 @@ final class SimpleMethodBindingExpression extends SimpleInvocationBindingExpress } private TypeName constructorTypeName(ClassName requestingClass) { - DeclaredType type = MoreTypes.asDeclared(provisionBinding.key().type()); + DeclaredType type = MoreTypes.asDeclared(provisionBinding.key().type().java()); TypeName typeName = TypeName.get(type); if (type.getTypeArguments().stream() .allMatch(t -> isTypeAccessibleFrom(t, requestingClass.packageName()))) { @@ -139,17 +149,24 @@ final class SimpleMethodBindingExpression extends SimpleInvocationBindingExpress ProvisionMethod.invoke( provisionBinding, request -> dependencyArgument(request, requestingClass).codeBlock(), + shardImplementation::getUniqueFieldNameForAssistedParam, requestingClass, moduleReference(requestingClass), compilerOptions, - metadataUtil)); + metadataUtil), + requestingClass); } private Expression dependencyArgument(DependencyRequest dependency, ClassName requestingClass) { - return componentBindingExpressions.getDependencyArgumentExpression(dependency, requestingClass); + return isExperimentalMergedMode + ? componentRequestRepresentations + .getExperimentalSwitchingProviderDependencyRepresentation(bindingRequest(dependency)) + .getDependencyExpression(dependency.kind(), provisionBinding) + : componentRequestRepresentations.getDependencyArgumentExpression( + dependency, requestingClass); } - private Expression injectMembers(CodeBlock instance) { + private Expression injectMembers(CodeBlock instance, ClassName requestingClass) { if (provisionBinding.injectionSites().isEmpty()) { return Expression.create(simpleMethodReturnType(), instance); } @@ -157,30 +174,42 @@ final class SimpleMethodBindingExpression extends SimpleInvocationBindingExpress // Java 7 type inference can't figure out that instance in // injectParameterized(Parameterized_Factory.newParameterized()) is Parameterized<T> and not // Parameterized<Object> - if (!MoreTypes.asDeclared(provisionBinding.key().type()).getTypeArguments().isEmpty()) { - TypeName keyType = TypeName.get(provisionBinding.key().type()); + if (!MoreTypes.asDeclared(provisionBinding.key().type().java()) + .getTypeArguments() + .isEmpty()) { + TypeName keyType = TypeName.get(provisionBinding.key().type().java()); instance = CodeBlock.of("($T) ($T) $L", keyType, rawTypeName(keyType), instance); } } - MethodSpec membersInjectionMethod = membersInjectionMethods.getOrCreate(provisionBinding.key()); - TypeMirror returnType = - membersInjectionMethod.returnType.equals(TypeName.OBJECT) - ? elements.getTypeElement(Object.class).asType() - : provisionBinding.key().type(); - return Expression.create(returnType, CodeBlock.of("$N($L)", membersInjectionMethod, instance)); + return isExperimentalMergedMode + ? membersInjectionMethods.getInjectExpressionExperimental( + provisionBinding, instance, requestingClass) + : membersInjectionMethods.getInjectExpression( + provisionBinding.key(), instance, requestingClass); } private Optional<CodeBlock> moduleReference(ClassName requestingClass) { return provisionBinding.requiresModuleInstance() ? provisionBinding .contributingModule() - .map(Element::asType) + .map(XTypeElement::getType) .map(ComponentRequirement::forModule) - .map(module -> componentRequirementExpressions.getExpression(module, requestingClass)) + .map( + module -> + isExperimentalMergedMode + ? CodeBlock.of("(($T) dependencies[0])", module.type().getTypeName()) + : componentRequirementExpressions.getExpression(module, requestingClass)) : Optional.empty(); } - private TypeMirror simpleMethodReturnType() { - return provisionBinding.contributedPrimitiveType().orElse(provisionBinding.key().type()); + private XType simpleMethodReturnType() { + return provisionBinding + .contributedPrimitiveType() + .orElse(provisionBinding.key().type().xprocessing()); + } + + @AssistedFactory + static interface Factory { + SimpleMethodRequestRepresentation create(ProvisionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/StaticFactoryInstanceSupplier.java b/java/dagger/internal/codegen/writing/StaticFactoryInstanceSupplier.java new file mode 100644 index 000000000..5537c8f3c --- /dev/null +++ b/java/dagger/internal/codegen/writing/StaticFactoryInstanceSupplier.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 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.writing; + +import static dagger.spi.model.BindingKind.MULTIBOUND_MAP; +import static dagger.spi.model.BindingKind.MULTIBOUND_SET; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.internal.codegen.binding.ContributionBinding; + +/** An object that returns static factory to satisfy framework instance request. */ +final class StaticFactoryInstanceSupplier implements FrameworkInstanceSupplier { + private final FrameworkInstanceSupplier frameworkInstanceSupplier; + + @AssistedInject + StaticFactoryInstanceSupplier( + @Assisted ContributionBinding binding, + FrameworkInstanceBindingRepresentation.Factory + frameworkInstanceBindingRepresentationFactory) { + this.frameworkInstanceSupplier = () -> staticFactoryCreation(binding); + } + + @Override + public MemberSelect memberSelect() { + return frameworkInstanceSupplier.memberSelect(); + } + + // TODO(wanyingd): no-op members injector is currently handled in + // `MembersInjectorProviderCreationExpression`, we should inline the logic here so we won't create + // an extra field for it. + private MemberSelect staticFactoryCreation(ContributionBinding binding) { + switch (binding.kind()) { + case MULTIBOUND_MAP: + return StaticMemberSelects.emptyMapFactory(binding); + case MULTIBOUND_SET: + return StaticMemberSelects.emptySetFactory(binding); + case PROVISION: + case INJECTION: + return StaticMemberSelects.factoryCreateNoArgumentMethod(binding); + default: + throw new AssertionError(String.format("Invalid binding kind: %s", binding.kind())); + } + } + + @AssistedFactory + static interface Factory { + StaticFactoryInstanceSupplier create(ContributionBinding binding); + } +} diff --git a/java/dagger/internal/codegen/writing/StaticMemberSelects.java b/java/dagger/internal/codegen/writing/StaticMemberSelects.java new file mode 100644 index 000000000..e547ab5c6 --- /dev/null +++ b/java/dagger/internal/codegen/writing/StaticMemberSelects.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2021 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.writing; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementTypeVariableNames; +import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; +import static dagger.internal.codegen.binding.SourceFiles.setFactoryClassName; +import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; +import static dagger.internal.codegen.javapoet.TypeNames.FACTORY; +import static dagger.internal.codegen.javapoet.TypeNames.MAP_FACTORY; +import static dagger.internal.codegen.javapoet.TypeNames.PRODUCER; +import static dagger.internal.codegen.javapoet.TypeNames.PRODUCERS; +import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER; +import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; +import static javax.lang.model.type.TypeKind.DECLARED; + +import com.google.auto.common.MoreTypes; +import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.TypeVariableName; +import dagger.internal.codegen.base.SetType; +import dagger.internal.codegen.binding.Binding; +import dagger.internal.codegen.binding.BindingType; +import dagger.internal.codegen.binding.ContributionBinding; +import dagger.internal.codegen.javapoet.CodeBlocks; +import java.util.List; +import javax.lang.model.type.TypeMirror; + +/** Helper class for static member select creation. */ +final class StaticMemberSelects { + /** A {@link MemberSelect} for a factory of an empty map. */ + static MemberSelect emptyMapFactory(Binding binding) { + BindingType bindingType = binding.bindingType(); + ImmutableList<TypeMirror> typeParameters = + ImmutableList.copyOf(MoreTypes.asDeclared(binding.key().type().java()).getTypeArguments()); + if (bindingType.equals(BindingType.PRODUCTION)) { + return new ParameterizedStaticMethod( + PRODUCERS, typeParameters, CodeBlock.of("emptyMapProducer()"), PRODUCER); + } else { + return new ParameterizedStaticMethod( + MAP_FACTORY, typeParameters, CodeBlock.of("emptyMapProvider()"), PROVIDER); + } + } + + /** + * A static member select for an empty set factory. Calls {@link + * dagger.internal.SetFactory#empty()}, {@link dagger.producers.internal.SetProducer#empty()}, or + * {@link dagger.producers.internal.SetOfProducedProducer#empty()}, depending on the set bindings. + */ + static MemberSelect emptySetFactory(ContributionBinding binding) { + return new ParameterizedStaticMethod( + setFactoryClassName(binding), + ImmutableList.of(toJavac(SetType.from(binding.key()).elementType())), + CodeBlock.of("empty()"), + FACTORY); + } + + /** + * Returns a {@link MemberSelect} for the instance of a {@code create()} method on a factory with + * no arguments. + */ + static MemberSelect factoryCreateNoArgumentMethod(Binding binding) { + checkArgument( + binding.bindingType().equals(BindingType.PROVISION), + "Invalid binding type: %s", + binding.bindingType()); + checkArgument( + binding.dependencies().isEmpty() && !binding.scope().isPresent(), + "%s should have no dependencies and be unscoped to create a no argument factory.", + binding); + + ClassName factoryName = generatedClassNameForBinding(binding); + TypeMirror keyType = binding.key().type().java(); + if (keyType.getKind().equals(DECLARED)) { + ImmutableList<TypeVariableName> typeVariables = bindingTypeElementTypeVariableNames(binding); + if (!typeVariables.isEmpty()) { + List<? extends TypeMirror> typeArguments = MoreTypes.asDeclared(keyType).getTypeArguments(); + return new ParameterizedStaticMethod( + factoryName, ImmutableList.copyOf(typeArguments), CodeBlock.of("create()"), FACTORY); + } + } + return new StaticMethod(factoryName, CodeBlock.of("create()")); + } + + private static final class StaticMethod extends MemberSelect { + private final CodeBlock methodCodeBlock; + + StaticMethod(ClassName owningClass, CodeBlock methodCodeBlock) { + super(owningClass, true); + this.methodCodeBlock = checkNotNull(methodCodeBlock); + } + + @Override + CodeBlock getExpressionFor(ClassName usingClass) { + return owningClass().equals(usingClass) + ? methodCodeBlock + : CodeBlock.of("$T.$L", owningClass(), methodCodeBlock); + } + } + + private static final class ParameterizedStaticMethod extends MemberSelect { + private final ImmutableList<TypeMirror> typeParameters; + private final CodeBlock methodCodeBlock; + private final ClassName rawReturnType; + + ParameterizedStaticMethod( + ClassName owningClass, + ImmutableList<TypeMirror> typeParameters, + CodeBlock methodCodeBlock, + ClassName rawReturnType) { + super(owningClass, true); + this.typeParameters = typeParameters; + this.methodCodeBlock = methodCodeBlock; + this.rawReturnType = rawReturnType; + } + + @Override + CodeBlock getExpressionFor(ClassName usingClass) { + boolean accessible = true; + for (TypeMirror typeParameter : typeParameters) { + accessible &= isTypeAccessibleFrom(typeParameter, usingClass.packageName()); + } + + if (accessible) { + return CodeBlock.of( + "$T.<$L>$L", + owningClass(), + typeParameters.stream().map(CodeBlocks::type).collect(toParametersCodeBlock()), + methodCodeBlock); + } else { + return CodeBlock.of("(($T) $T.$L)", rawReturnType, owningClass(), methodCodeBlock); + } + } + } + + private StaticMemberSelects() {} +} diff --git a/java/dagger/internal/codegen/writing/SubcomponentCreatorBindingExpression.java b/java/dagger/internal/codegen/writing/SubcomponentCreatorBindingExpression.java deleted file mode 100644 index 3099048e6..000000000 --- a/java/dagger/internal/codegen/writing/SubcomponentCreatorBindingExpression.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.writing; - -import com.squareup.javapoet.ClassName; -import dagger.internal.codegen.binding.ContributionBinding; -import dagger.internal.codegen.javapoet.Expression; -import javax.lang.model.type.TypeMirror; - -/** A binding expression for a subcomponent creator that just invokes the constructor. */ -final class SubcomponentCreatorBindingExpression extends SimpleInvocationBindingExpression { - private final TypeMirror creatorType; - private final String creatorImplementationName; - - SubcomponentCreatorBindingExpression( - ContributionBinding binding, String creatorImplementationName) { - super(binding); - this.creatorType = binding.key().type(); - this.creatorImplementationName = creatorImplementationName; - } - - @Override - Expression getDependencyExpression(ClassName requestingClass) { - return Expression.create(creatorType, "new $L()", creatorImplementationName); - } -} diff --git a/java/dagger/internal/codegen/writing/SubcomponentCreatorRequestRepresentation.java b/java/dagger/internal/codegen/writing/SubcomponentCreatorRequestRepresentation.java new file mode 100644 index 000000000..74ebbad38 --- /dev/null +++ b/java/dagger/internal/codegen/writing/SubcomponentCreatorRequestRepresentation.java @@ -0,0 +1,80 @@ +/* + * 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.writing; + +import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.internal.codegen.binding.ContributionBinding; +import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; +import java.util.ArrayList; +import java.util.List; + +/** A binding expression for a subcomponent creator that just invokes the constructor. */ +final class SubcomponentCreatorRequestRepresentation extends RequestRepresentation { + private final ShardImplementation shardImplementation; + private final ContributionBinding binding; + private final boolean isExperimentalMergedMode; + + @AssistedInject + SubcomponentCreatorRequestRepresentation( + @Assisted ContributionBinding binding, ComponentImplementation componentImplementation) { + this.binding = binding; + this.shardImplementation = componentImplementation.shardImplementation(binding); + this.isExperimentalMergedMode = + componentImplementation.compilerMode().isExperimentalMergedMode(); + } + + @Override + Expression getDependencyExpression(ClassName requestingClass) { + return Expression.create( + binding.key().type().java(), + "new $T($L)", + shardImplementation.getSubcomponentCreatorSimpleName(binding.key()), + isExperimentalMergedMode + ? getDependenciesExperimental() + : shardImplementation.componentFieldsByImplementation().values().stream() + .map(field -> CodeBlock.of("$N", field)) + .collect(toParametersCodeBlock())); + } + + private CodeBlock getDependenciesExperimental() { + List<CodeBlock> expressions = new ArrayList<>(); + int index = 0; + for (FieldSpec field : shardImplementation.componentFieldsByImplementation().values()) { + expressions.add(CodeBlock.of("($T) dependencies[$L]", field.type, index++)); + } + return expressions.stream().collect(toParametersCodeBlock()); + } + + CodeBlock getDependencyExpressionArguments() { + return shardImplementation.componentFieldsByImplementation().values().stream() + .map(field -> CodeBlock.of("$N", field)) + .collect(toParametersCodeBlock()); + } + + @AssistedFactory + static interface Factory { + SubcomponentCreatorRequestRepresentation create(ContributionBinding binding); + } +} diff --git a/java/dagger/internal/codegen/writing/SubcomponentNames.java b/java/dagger/internal/codegen/writing/SubcomponentNames.java deleted file mode 100644 index fa0037b67..000000000 --- a/java/dagger/internal/codegen/writing/SubcomponentNames.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2015 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.writing; - -import static com.google.common.base.Preconditions.checkArgument; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; -import static java.lang.Character.isUpperCase; -import static java.lang.String.format; - -import com.google.common.base.CharMatcher; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.collect.Multimaps; -import dagger.internal.codegen.base.UniqueNameSet; -import dagger.internal.codegen.binding.BindingGraph; -import dagger.internal.codegen.binding.ComponentCreatorDescriptor; -import dagger.internal.codegen.binding.ComponentDescriptor; -import dagger.internal.codegen.binding.KeyFactory; -import dagger.model.Key; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import javax.lang.model.element.Name; -import javax.lang.model.element.TypeElement; - -/** - * Holds the unique simple names for all subcomponents, keyed by their {@link ComponentDescriptor} - * and {@link Key} of the subcomponent builder. - */ -public final class SubcomponentNames { - private static final Splitter QUALIFIED_NAME_SPLITTER = Splitter.on('.'); - - private final ImmutableMap<ComponentDescriptor, String> namesByDescriptor; - private final ImmutableMap<Key, ComponentDescriptor> descriptorsByCreatorKey; - - public SubcomponentNames(BindingGraph graph, KeyFactory keyFactory) { - this.namesByDescriptor = namesByDescriptor(graph); - this.descriptorsByCreatorKey = descriptorsByCreatorKey(keyFactory, namesByDescriptor.keySet()); - } - - /** Returns the simple component name for the given {@link ComponentDescriptor}. */ - String get(ComponentDescriptor componentDescriptor) { - return namesByDescriptor.get(componentDescriptor); - } - - /** - * Returns the simple name for the subcomponent creator implementation with the given {@link Key}. - */ - String getCreatorName(Key key) { - return getCreatorName(descriptorsByCreatorKey.get(key)); - } - - /** - * Returns the simple name for the subcomponent creator implementation for the given {@link - * ComponentDescriptor}. - */ - String getCreatorName(ComponentDescriptor componentDescriptor) { - checkArgument(componentDescriptor.creatorDescriptor().isPresent()); - ComponentCreatorDescriptor creatorDescriptor = componentDescriptor.creatorDescriptor().get(); - return get(componentDescriptor) + creatorDescriptor.kind().typeName(); - } - - private static ImmutableMap<ComponentDescriptor, String> namesByDescriptor(BindingGraph graph) { - ImmutableListMultimap<String, ComponentDescriptor> componentDescriptorsBySimpleName = - Multimaps.index(graph.componentDescriptors(), SubcomponentNames::simpleName); - Map<ComponentDescriptor, String> subcomponentImplSimpleNames = new LinkedHashMap<>(); - componentDescriptorsBySimpleName - .asMap() - .values() - .stream() - .map(SubcomponentNames::disambiguateConflictingSimpleNames) - .forEach(subcomponentImplSimpleNames::putAll); - subcomponentImplSimpleNames.remove(graph.componentDescriptor()); - return ImmutableMap.copyOf(subcomponentImplSimpleNames); - } - - private static ImmutableMap<Key, ComponentDescriptor> descriptorsByCreatorKey( - KeyFactory keyFactory, ImmutableSet<ComponentDescriptor> subcomponents) { - return subcomponents.stream() - .filter(subcomponent -> subcomponent.creatorDescriptor().isPresent()) - .collect( - toImmutableMap( - subcomponent -> - keyFactory.forSubcomponentCreator( - subcomponent.creatorDescriptor().get().typeElement().asType()), - subcomponent -> subcomponent)); - } - - private static ImmutableMap<ComponentDescriptor, String> disambiguateConflictingSimpleNames( - Collection<ComponentDescriptor> componentsWithConflictingNames) { - // If there's only 1 component there's nothing to disambiguate so return the simple name. - if (componentsWithConflictingNames.size() == 1) { - ComponentDescriptor component = Iterables.getOnlyElement(componentsWithConflictingNames); - return ImmutableMap.of(component, simpleName(component)); - } - - // There are conflicting simple names, so disambiguate them with a unique prefix. - // We keep them small to fix https://github.com/google/dagger/issues/421. - UniqueNameSet nameSet = new UniqueNameSet(); - ImmutableMap.Builder<ComponentDescriptor, String> uniqueNames = ImmutableMap.builder(); - for (ComponentDescriptor component : componentsWithConflictingNames) { - String simpleName = simpleName(component); - String basePrefix = uniquingPrefix(component); - uniqueNames.put(component, format("%s_%s", nameSet.getUniqueName(basePrefix), simpleName)); - } - return uniqueNames.build(); - } - - private static String simpleName(ComponentDescriptor component) { - return component.typeElement().getSimpleName().toString(); - } - - /** Returns a prefix that could make the component's simple name more unique. */ - private static String uniquingPrefix(ComponentDescriptor component) { - TypeElement typeElement = component.typeElement(); - String containerName = typeElement.getEnclosingElement().getSimpleName().toString(); - - // If parent element looks like a class, use its initials as a prefix. - if (!containerName.isEmpty() && isUpperCase(containerName.charAt(0))) { - return CharMatcher.javaLowerCase().removeFrom(containerName); - } - - // Not in a normally named class. Prefix with the initials of the elements leading here. - Name qualifiedName = typeElement.getQualifiedName(); - Iterator<String> pieces = QUALIFIED_NAME_SPLITTER.split(qualifiedName).iterator(); - StringBuilder b = new StringBuilder(); - - while (pieces.hasNext()) { - String next = pieces.next(); - if (pieces.hasNext()) { - b.append(next.charAt(0)); - } - } - - // Note that a top level class in the root package will be prefixed "$_". - return b.length() > 0 ? b.toString() : "$"; - } -} diff --git a/java/dagger/internal/codegen/writing/SwitchingProviderInstanceSupplier.java b/java/dagger/internal/codegen/writing/SwitchingProviderInstanceSupplier.java new file mode 100644 index 000000000..ef6bd499d --- /dev/null +++ b/java/dagger/internal/codegen/writing/SwitchingProviderInstanceSupplier.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2021 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.writing; + +import static dagger.internal.codegen.javapoet.TypeNames.DOUBLE_CHECK; +import static dagger.internal.codegen.javapoet.TypeNames.SINGLE_CHECK; + +import com.squareup.javapoet.CodeBlock; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.internal.codegen.binding.Binding; +import dagger.internal.codegen.binding.BindingGraph; +import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; +import dagger.spi.model.BindingKind; + +/** + * An object that initializes a framework-type component field for a binding using instances created + * by switching providers. + */ +final class SwitchingProviderInstanceSupplier implements FrameworkInstanceSupplier { + private final FrameworkInstanceSupplier frameworkInstanceSupplier; + + @AssistedInject + SwitchingProviderInstanceSupplier( + @Assisted ProvisionBinding binding, + SwitchingProviders switchingProviders, + ExperimentalSwitchingProviders experimentalSwitchingProviders, + BindingGraph graph, + ComponentImplementation componentImplementation, + UnscopedDirectInstanceRequestRepresentationFactory + unscopedDirectInstanceRequestRepresentationFactory) { + FrameworkInstanceCreationExpression frameworkInstanceCreationExpression = + componentImplementation.compilerMode().isExperimentalMergedMode() + ? experimentalSwitchingProviders.newFrameworkInstanceCreationExpression( + binding, unscopedDirectInstanceRequestRepresentationFactory.create(binding)) + : switchingProviders.newFrameworkInstanceCreationExpression( + binding, unscopedDirectInstanceRequestRepresentationFactory.create(binding)); + this.frameworkInstanceSupplier = + new FrameworkFieldInitializer( + componentImplementation, binding, scope(binding, frameworkInstanceCreationExpression)); + } + + @Override + public MemberSelect memberSelect() { + return frameworkInstanceSupplier.memberSelect(); + } + + private FrameworkInstanceCreationExpression scope( + Binding binding, FrameworkInstanceCreationExpression unscoped) { + // Caching assisted factory provider, so that there won't be new factory created for each + // provider.get() call. + if (!binding.scope().isPresent() && !binding.kind().equals(BindingKind.ASSISTED_FACTORY)) { + return unscoped; + } + return () -> + CodeBlock.of( + "$T.provider($L)", + binding.scope().isPresent() + ? (binding.scope().get().isReusable() ? SINGLE_CHECK : DOUBLE_CHECK) + : SINGLE_CHECK, + unscoped.creationExpression()); + } + + @AssistedFactory + static interface Factory { + SwitchingProviderInstanceSupplier create(ProvisionBinding binding); + } +} diff --git a/java/dagger/internal/codegen/writing/SwitchingProviders.java b/java/dagger/internal/codegen/writing/SwitchingProviders.java index d38b9d957..b922b1740 100644 --- a/java/dagger/internal/codegen/writing/SwitchingProviders.java +++ b/java/dagger/internal/codegen/writing/SwitchingProviders.java @@ -16,6 +16,7 @@ package dagger.internal.codegen.writing; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Iterables.getLast; import static com.google.common.collect.Iterables.getOnlyElement; @@ -25,57 +26,41 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings; import static dagger.internal.codegen.javapoet.TypeNames.providerOf; +import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; +import static javax.lang.model.element.Modifier.STATIC; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import dagger.internal.codegen.base.UniqueNameSet; +import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.javapoet.CodeBlocks; -import dagger.internal.codegen.javapoet.Expression; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Key; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; +import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; +import dagger.spi.model.BindingKind; +import dagger.spi.model.Key; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; +import javax.inject.Inject; /** * Keeps track of all provider expression requests for a component. * - * <p>The provider expression request will be satisfied by a single generated {@code Provider} inner - * class that can provide instances for all types by switching on an id. + * <p>The provider expression request will be satisfied by a single generated {@code Provider} class + * that can provide instances for all types by switching on an id. */ -// TODO(ronshapiro): either merge this with InnerSwitchingProviders, or repurpose this for -// SwitchingProducers -abstract class SwitchingProviders { - /** - * Defines the {@linkplain Expression expressions} for a switch case in a {@code SwitchProvider} - * for a particular binding. - */ - // TODO(bcorso): Consider handling SwitchingProviders with dependency arguments in this class, - // then we wouldn't need the getProviderExpression method. - // TODO(bcorso): Consider making this an abstract class with equals/hashCode defined by the key - // and then using this class directly in Map types instead of Key. - interface SwitchCase { - /** Returns the {@link Key} for this switch case. */ - Key key(); - - /** Returns the {@link Expression} that returns the provided instance for this case. */ - Expression getReturnExpression(ClassName switchingProviderClass); - - /** - * Returns the {@link Expression} that returns the {@code SwitchProvider} instance for this - * case. - */ - Expression getProviderExpression(ClassName switchingProviderClass, int switchId); - } - +@PerComponentImplementation +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 @@ -96,35 +81,36 @@ abstract class SwitchingProviders { private final Map<Key, SwitchingProviderBuilder> switchingProviderBuilders = new LinkedHashMap<>(); - private final ComponentImplementation componentImplementation; - private final ClassName owningComponent; + private final ShardImplementation shardImplementation; private final DaggerTypes types; private final UniqueNameSet switchingProviderNames = new UniqueNameSet(); + @Inject SwitchingProviders(ComponentImplementation componentImplementation, DaggerTypes types) { - this.componentImplementation = checkNotNull(componentImplementation); + // Currently, the SwitchingProviders types are only added to the componentShard. + this.shardImplementation = checkNotNull(componentImplementation).getComponentShard(); this.types = checkNotNull(types); - this.owningComponent = checkNotNull(componentImplementation).name(); } - /** Returns the {@link TypeSpec} for a {@code SwitchingProvider} based on the given builder. */ - protected abstract TypeSpec createSwitchingProviderType(TypeSpec.Builder builder); - - /** - * Returns the {@link Expression} that returns the {@code SwitchProvider} instance for the case. - */ - protected final Expression getProviderExpression(SwitchCase switchCase) { - return switchingProviderBuilders - .computeIfAbsent(switchCase.key(), key -> getSwitchingProviderBuilder()) - .getProviderExpression(switchCase); + /** Returns the framework instance creation expression for an inner switching provider class. */ + FrameworkInstanceCreationExpression newFrameworkInstanceCreationExpression( + ContributionBinding binding, RequestRepresentation unscopedInstanceRequestRepresentation) { + return new FrameworkInstanceCreationExpression() { + @Override + public CodeBlock creationExpression() { + return switchingProviderBuilders + .computeIfAbsent(binding.key(), key -> getSwitchingProviderBuilder()) + .getNewInstanceCodeBlock(binding, unscopedInstanceRequestRepresentation); + } + }; } private SwitchingProviderBuilder getSwitchingProviderBuilder() { if (switchingProviderBuilders.size() % MAX_CASES_PER_CLASS == 0) { String name = switchingProviderNames.getUniqueName("SwitchingProvider"); SwitchingProviderBuilder switchingProviderBuilder = - new SwitchingProviderBuilder(owningComponent.nestedClass(name)); - componentImplementation.addTypeSupplier(switchingProviderBuilder::build); + new SwitchingProviderBuilder(shardImplementation.name().nestedClass(name)); + shardImplementation.addTypeSupplier(switchingProviderBuilder::build); return switchingProviderBuilder; } return getLast(switchingProviderBuilders.values()); @@ -142,33 +128,72 @@ abstract class SwitchingProviders { this.switchingProviderType = checkNotNull(switchingProviderType); } - Expression getProviderExpression(SwitchCase switchCase) { - Key key = switchCase.key(); + private CodeBlock getNewInstanceCodeBlock( + ContributionBinding binding, RequestRepresentation unscopedInstanceRequestRepresentation) { + Key key = binding.key(); if (!switchIds.containsKey(key)) { int switchId = switchIds.size(); switchIds.put(key, switchId); - switchCases.put(switchId, createSwitchCaseCodeBlock(switchCase)); + switchCases.put( + switchId, createSwitchCaseCodeBlock(key, unscopedInstanceRequestRepresentation)); } - return switchCase.getProviderExpression(switchingProviderType, switchIds.get(key)); + return CodeBlock.of( + "new $T<$L>($L, $L)", + switchingProviderType, + // Add the type parameter explicitly when the binding is scoped because Java can't resolve + // the type when wrapped. For example, the following will error: + // fooProvider = DoubleCheck.provider(new SwitchingProvider<>(1)); + (binding.scope().isPresent() || binding.kind().equals(BindingKind.ASSISTED_FACTORY)) + ? CodeBlock.of( + "$T", shardImplementation.accessibleType(toJavac(binding.contributedType()))) + : "", + shardImplementation.componentFieldsByImplementation().values().stream() + .map(field -> CodeBlock.of("$N", field)) + .collect(CodeBlocks.toParametersCodeBlock()), + switchIds.get(key)); } - private CodeBlock createSwitchCaseCodeBlock(SwitchCase switchCase) { + private CodeBlock createSwitchCaseCodeBlock( + Key key, RequestRepresentation unscopedInstanceRequestRepresentation) { + // TODO(bcorso): Try to delay calling getDependencyExpression() until we are writing out the + // SwitchingProvider because calling it here makes FrameworkFieldInitializer think there's a + // cycle when initializing SwitchingProviders which adds an uncessary DelegateFactory. CodeBlock instanceCodeBlock = - switchCase.getReturnExpression(switchingProviderType).box(types).codeBlock(); + unscopedInstanceRequestRepresentation + .getDependencyExpression(switchingProviderType) + .box(types) + .codeBlock(); return CodeBlock.builder() // TODO(bcorso): Is there something else more useful than the key? - .add("case $L: // $L \n", switchIds.get(switchCase.key()), switchCase.key()) + .add("case $L: // $L \n", switchIds.get(key), key) .addStatement("return ($T) $L", T, instanceCodeBlock) .build(); } private TypeSpec build() { - return createSwitchingProviderType( + TypeSpec.Builder builder = classBuilder(switchingProviderType) + .addModifiers(PRIVATE, FINAL, STATIC) .addTypeVariable(T) .addSuperinterface(providerOf(T)) - .addMethods(getMethods())); + .addMethods(getMethods()); + + // The SwitchingProvider constructor lists all component parameters first and switch id last. + MethodSpec.Builder constructor = MethodSpec.constructorBuilder(); + shardImplementation + .componentFieldsByImplementation() + .values() + .forEach( + field -> { + builder.addField(field); + constructor.addParameter(field.type, field.name); + constructor.addStatement("this.$1N = $1N", field); + }); + builder.addField(TypeName.INT, "id", PRIVATE, FINAL); + constructor.addParameter(TypeName.INT, "id").addStatement("this.id = id"); + + return builder.addMethod(constructor.build()).build(); } private ImmutableList<MethodSpec> getMethods() { diff --git a/java/dagger/internal/codegen/writing/UnscopedDirectInstanceRequestRepresentationFactory.java b/java/dagger/internal/codegen/writing/UnscopedDirectInstanceRequestRepresentationFactory.java new file mode 100644 index 000000000..28c23700f --- /dev/null +++ b/java/dagger/internal/codegen/writing/UnscopedDirectInstanceRequestRepresentationFactory.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2021 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.writing; + +import dagger.internal.codegen.binding.ComponentRequirement; +import dagger.internal.codegen.binding.ContributionBinding; +import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.spi.model.RequestKind; +import javax.inject.Inject; + +/** + * A factory for creating a binding expression for an unscoped instance. + * + * <p>Note that these binding expressions are for getting "direct" instances -- i.e. instances that + * are created via constructors or modules (e.g. {@code new Foo()} or {@code + * FooModule.provideFoo()}) as opposed to an instance created from calling a getter on a framework + * type (e.g. {@code fooProvider.get()}). See {@link FrameworkInstanceRequestRepresentation} for + * binding expressions that are created from framework types. + */ +final class UnscopedDirectInstanceRequestRepresentationFactory { + private final AssistedFactoryRequestRepresentation.Factory + assistedFactoryRequestRepresentationFactory; + private final ComponentInstanceRequestRepresentation.Factory + componentInstanceRequestRepresentationFactory; + private final ComponentProvisionRequestRepresentation.Factory + componentProvisionRequestRepresentationFactory; + private final ComponentRequirementRequestRepresentation.Factory + componentRequirementRequestRepresentationFactory; + private final DelegateRequestRepresentation.Factory delegateRequestRepresentationFactory; + private final MapRequestRepresentation.Factory mapRequestRepresentationFactory; + private final OptionalRequestRepresentation.Factory optionalRequestRepresentationFactory; + private final SetRequestRepresentation.Factory setRequestRepresentationFactory; + private final SimpleMethodRequestRepresentation.Factory simpleMethodRequestRepresentationFactory; + private final SubcomponentCreatorRequestRepresentation.Factory + subcomponentCreatorRequestRepresentationFactory; + + @Inject + UnscopedDirectInstanceRequestRepresentationFactory( + ComponentImplementation componentImplementation, + AssistedFactoryRequestRepresentation.Factory assistedFactoryRequestRepresentationFactory, + ComponentInstanceRequestRepresentation.Factory componentInstanceRequestRepresentationFactory, + ComponentProvisionRequestRepresentation.Factory + componentProvisionRequestRepresentationFactory, + ComponentRequirementRequestRepresentation.Factory + componentRequirementRequestRepresentationFactory, + DelegateRequestRepresentation.Factory delegateRequestRepresentationFactory, + MapRequestRepresentation.Factory mapRequestRepresentationFactory, + OptionalRequestRepresentation.Factory optionalRequestRepresentationFactory, + SetRequestRepresentation.Factory setRequestRepresentationFactory, + SimpleMethodRequestRepresentation.Factory simpleMethodRequestRepresentationFactory, + SubcomponentCreatorRequestRepresentation.Factory + subcomponentCreatorRequestRepresentationFactory) { + this.assistedFactoryRequestRepresentationFactory = assistedFactoryRequestRepresentationFactory; + this.componentInstanceRequestRepresentationFactory = + componentInstanceRequestRepresentationFactory; + this.componentProvisionRequestRepresentationFactory = + componentProvisionRequestRepresentationFactory; + this.componentRequirementRequestRepresentationFactory = + componentRequirementRequestRepresentationFactory; + this.delegateRequestRepresentationFactory = delegateRequestRepresentationFactory; + this.mapRequestRepresentationFactory = mapRequestRepresentationFactory; + this.optionalRequestRepresentationFactory = optionalRequestRepresentationFactory; + this.setRequestRepresentationFactory = setRequestRepresentationFactory; + this.simpleMethodRequestRepresentationFactory = simpleMethodRequestRepresentationFactory; + this.subcomponentCreatorRequestRepresentationFactory = + subcomponentCreatorRequestRepresentationFactory; + } + + /** Returns a direct, unscoped binding expression for a {@link RequestKind#INSTANCE} request. */ + RequestRepresentation create(ContributionBinding binding) { + switch (binding.kind()) { + case DELEGATE: + return delegateRequestRepresentationFactory.create(binding, RequestKind.INSTANCE); + + case COMPONENT: + return componentInstanceRequestRepresentationFactory.create(binding); + + case COMPONENT_DEPENDENCY: + return componentRequirementRequestRepresentationFactory.create( + binding, ComponentRequirement.forDependency(binding.key().type().xprocessing())); + + case COMPONENT_PROVISION: + return componentProvisionRequestRepresentationFactory.create((ProvisionBinding) binding); + + case SUBCOMPONENT_CREATOR: + return subcomponentCreatorRequestRepresentationFactory.create(binding); + + case MULTIBOUND_SET: + return setRequestRepresentationFactory.create((ProvisionBinding) binding); + + case MULTIBOUND_MAP: + return mapRequestRepresentationFactory.create((ProvisionBinding) binding); + + case OPTIONAL: + return optionalRequestRepresentationFactory.create((ProvisionBinding) binding); + + case BOUND_INSTANCE: + return componentRequirementRequestRepresentationFactory.create( + binding, ComponentRequirement.forBoundInstance(binding)); + + case ASSISTED_FACTORY: + return assistedFactoryRequestRepresentationFactory.create((ProvisionBinding) binding); + + case INJECTION: + case PROVISION: + return simpleMethodRequestRepresentationFactory.create((ProvisionBinding) binding); + + case ASSISTED_INJECTION: + case MEMBERS_INJECTOR: + case MEMBERS_INJECTION: + case COMPONENT_PRODUCTION: + case PRODUCTION: + // Fall through + } + throw new AssertionError("Unexpected binding kind: " + binding.kind()); + } +} diff --git a/java/dagger/internal/codegen/writing/UnscopedFrameworkInstanceCreationExpressionFactory.java b/java/dagger/internal/codegen/writing/UnscopedFrameworkInstanceCreationExpressionFactory.java new file mode 100644 index 000000000..d77b14626 --- /dev/null +++ b/java/dagger/internal/codegen/writing/UnscopedFrameworkInstanceCreationExpressionFactory.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2021 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.writing; + +import com.squareup.javapoet.CodeBlock; +import dagger.internal.codegen.binding.ComponentRequirement; +import dagger.internal.codegen.binding.ContributionBinding; +import dagger.internal.codegen.binding.ProvisionBinding; +import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; +import javax.inject.Inject; + +/** + * A factory for creating unscoped creation expressions for a provision or production binding. + * + * <p>A creation expression is responsible for creating the factory for a given binding (e.g. by + * calling the generated factory create method, {@code Foo_Factory.create(...)}). Note that this + * class does not handle scoping of these factories (e.g. wrapping in {@code + * DoubleCheck.provider()}). + */ +final class UnscopedFrameworkInstanceCreationExpressionFactory { + private final ComponentImplementation componentImplementation; + private final ComponentRequirementExpressions componentRequirementExpressions; + private final AnonymousProviderCreationExpression.Factory + anonymousProviderCreationExpressionFactory; + private final DelegatingFrameworkInstanceCreationExpression.Factory + delegatingFrameworkInstanceCreationExpressionFactory; + private final DependencyMethodProducerCreationExpression.Factory + dependencyMethodProducerCreationExpressionFactory; + private final DependencyMethodProviderCreationExpression.Factory + dependencyMethodProviderCreationExpressionFactory; + private final InjectionOrProvisionProviderCreationExpression.Factory + injectionOrProvisionProviderCreationExpressionFactory; + private final MapFactoryCreationExpression.Factory mapFactoryCreationExpressionFactory; + private final MembersInjectorProviderCreationExpression.Factory + membersInjectorProviderCreationExpressionFactory; + private final OptionalFactoryInstanceCreationExpression.Factory + optionalFactoryInstanceCreationExpressionFactory; + private final ProducerCreationExpression.Factory producerCreationExpressionFactory; + private final SetFactoryCreationExpression.Factory setFactoryCreationExpressionFactory; + + @Inject + UnscopedFrameworkInstanceCreationExpressionFactory( + ComponentImplementation componentImplementation, + ComponentRequirementExpressions componentRequirementExpressions, + AnonymousProviderCreationExpression.Factory anonymousProviderCreationExpressionFactory, + DelegatingFrameworkInstanceCreationExpression.Factory + delegatingFrameworkInstanceCreationExpressionFactory, + DependencyMethodProducerCreationExpression.Factory + dependencyMethodProducerCreationExpressionFactory, + DependencyMethodProviderCreationExpression.Factory + dependencyMethodProviderCreationExpressionFactory, + InjectionOrProvisionProviderCreationExpression.Factory + injectionOrProvisionProviderCreationExpressionFactory, + MapFactoryCreationExpression.Factory mapFactoryCreationExpressionFactory, + MembersInjectorProviderCreationExpression.Factory + membersInjectorProviderCreationExpressionFactory, + OptionalFactoryInstanceCreationExpression.Factory + optionalFactoryInstanceCreationExpressionFactory, + ProducerCreationExpression.Factory producerCreationExpressionFactory, + SetFactoryCreationExpression.Factory setFactoryCreationExpressionFactory) { + this.componentImplementation = componentImplementation; + this.componentRequirementExpressions = componentRequirementExpressions; + this.anonymousProviderCreationExpressionFactory = anonymousProviderCreationExpressionFactory; + this.delegatingFrameworkInstanceCreationExpressionFactory = + delegatingFrameworkInstanceCreationExpressionFactory; + this.dependencyMethodProducerCreationExpressionFactory = + dependencyMethodProducerCreationExpressionFactory; + this.dependencyMethodProviderCreationExpressionFactory = + dependencyMethodProviderCreationExpressionFactory; + this.injectionOrProvisionProviderCreationExpressionFactory = + injectionOrProvisionProviderCreationExpressionFactory; + this.mapFactoryCreationExpressionFactory = mapFactoryCreationExpressionFactory; + this.membersInjectorProviderCreationExpressionFactory = + membersInjectorProviderCreationExpressionFactory; + this.optionalFactoryInstanceCreationExpressionFactory = + optionalFactoryInstanceCreationExpressionFactory; + this.producerCreationExpressionFactory = producerCreationExpressionFactory; + this.setFactoryCreationExpressionFactory = setFactoryCreationExpressionFactory; + } + + /** + * Returns an unscoped creation expression for a {@link javax.inject.Provider} for provision + * bindings or a {@link dagger.producers.Producer} for production bindings. + */ + FrameworkInstanceCreationExpression create(ContributionBinding binding) { + switch (binding.kind()) { + case COMPONENT: + // The cast can be removed when we drop java 7 source support + return new InstanceFactoryCreationExpression( + () -> + CodeBlock.of( + "($T) $L", + binding.key().type().java(), + componentImplementation.componentFieldReference())); + + case BOUND_INSTANCE: + return instanceFactoryCreationExpression( + binding, ComponentRequirement.forBoundInstance(binding)); + + case COMPONENT_DEPENDENCY: + return instanceFactoryCreationExpression( + binding, ComponentRequirement.forDependency(binding.key().type().xprocessing())); + + case COMPONENT_PROVISION: + return dependencyMethodProviderCreationExpressionFactory.create((ProvisionBinding) binding); + + case SUBCOMPONENT_CREATOR: + return anonymousProviderCreationExpressionFactory.create(binding); + + case ASSISTED_FACTORY: + case ASSISTED_INJECTION: + case INJECTION: + case PROVISION: + return injectionOrProvisionProviderCreationExpressionFactory.create(binding); + + case COMPONENT_PRODUCTION: + return dependencyMethodProducerCreationExpressionFactory.create(binding); + + case PRODUCTION: + return producerCreationExpressionFactory.create(binding); + + case MULTIBOUND_SET: + return setFactoryCreationExpressionFactory.create(binding); + + case MULTIBOUND_MAP: + return mapFactoryCreationExpressionFactory.create(binding); + + case DELEGATE: + return delegatingFrameworkInstanceCreationExpressionFactory.create(binding); + + case OPTIONAL: + return optionalFactoryInstanceCreationExpressionFactory.create(binding); + + case MEMBERS_INJECTOR: + return membersInjectorProviderCreationExpressionFactory.create((ProvisionBinding) binding); + + default: + throw new AssertionError(binding); + } + } + + private InstanceFactoryCreationExpression instanceFactoryCreationExpression( + ContributionBinding binding, ComponentRequirement componentRequirement) { + return new InstanceFactoryCreationExpression( + binding.nullableType().isPresent(), + () -> + componentRequirementExpressions.getExpressionDuringInitialization( + componentRequirement, componentImplementation.name())); + } +} diff --git a/java/dagger/internal/codegen/writing/UnwrappedMapKeyGenerator.java b/java/dagger/internal/codegen/writing/UnwrappedMapKeyGenerator.java index f07b882b2..094f07a06 100644 --- a/java/dagger/internal/codegen/writing/UnwrappedMapKeyGenerator.java +++ b/java/dagger/internal/codegen/writing/UnwrappedMapKeyGenerator.java @@ -16,13 +16,13 @@ package dagger.internal.codegen.writing; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XTypeElement; import dagger.MapKey; import dagger.internal.codegen.langmodel.DaggerElements; import java.util.Set; -import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; -import javax.lang.model.element.TypeElement; /** * Generates classes that create annotation instances for an unwrapped {@link MapKey} annotation @@ -56,13 +56,13 @@ import javax.lang.model.element.TypeElement; public final class UnwrappedMapKeyGenerator extends AnnotationCreatorGenerator { @Inject - UnwrappedMapKeyGenerator(Filer filer, DaggerElements elements, SourceVersion sourceVersion) { + UnwrappedMapKeyGenerator(XFiler filer, DaggerElements elements, SourceVersion sourceVersion) { super(filer, elements, sourceVersion); } @Override - protected Set<TypeElement> annotationsToCreate(TypeElement annotationElement) { - Set<TypeElement> nestedAnnotationElements = super.annotationsToCreate(annotationElement); + protected Set<XTypeElement> annotationsToCreate(XTypeElement annotationElement) { + Set<XTypeElement> nestedAnnotationElements = super.annotationsToCreate(annotationElement); nestedAnnotationElements.remove(annotationElement); return nestedAnnotationElements; } diff --git a/java/dagger/internal/codegen/xprocessing/BUILD b/java/dagger/internal/codegen/xprocessing/BUILD new file mode 100644 index 000000000..80a0c027a --- /dev/null +++ b/java/dagger/internal/codegen/xprocessing/BUILD @@ -0,0 +1,51 @@ +# Copyright (C) 2021 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. + +# Description: +# Import for including XProcessing in Dagger. + +load("@rules_java//java:defs.bzl", "java_import") + +package(default_visibility = ["//:src"]) + +java_library( + name = "xprocessing", + # TODO(b/181056551): Ideally, all of the methods in these utility classes + # will move directly into XProcessing, and we can then remove these classes. + srcs = glob(["*.java"]), + exports = [ + ":xprocessing-lib", + ], + deps = [ + ":xprocessing-lib", + "//java/dagger/internal/codegen/extension", + "//third_party/java/auto:common", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", + "@maven//:org_jetbrains_kotlin_kotlin_stdlib", + "@maven//:org_jetbrains_kotlin_kotlin_stdlib_jdk8", + ], +) + +alias( + name = "xprocessing-lib", + actual = ":xprocessing-jar", + visibility = ["//visibility:private"], +) + +java_import( + name = "xprocessing-jar", + jars = ["xprocessing.jar"], +) diff --git a/java/dagger/internal/codegen/xprocessing/MethodSpecs.java b/java/dagger/internal/codegen/xprocessing/MethodSpecs.java new file mode 100644 index 000000000..6c0322cb4 --- /dev/null +++ b/java/dagger/internal/codegen/xprocessing/MethodSpecs.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 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.xprocessing; + +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static javax.lang.model.element.Modifier.PROTECTED; +import static javax.lang.model.element.Modifier.PUBLIC; + +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XType; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; + +// TODO(bcorso): Consider moving these methods into XProcessing library. +/** A utility class for {@link MethodSpec} helper methods. */ +public final class MethodSpecs { + + /** Returns a {@link MethodSpec} that overrides the given method. */ + public static MethodSpec.Builder overriding(XMethodElement method, XType owner) { + XMethodType methodType = method.asMemberOf(owner); + MethodSpec.Builder builder = + MethodSpec.methodBuilder(getSimpleName(method)) + .addAnnotation(Override.class) + .addTypeVariables(methodType.getTypeVariableNames()) + .varargs(method.isVarArgs()) + .returns(methodType.getReturnType().getTypeName()); + if (method.isPublic()) { + builder.addModifiers(PUBLIC); + } else if (method.isProtected()) { + builder.addModifiers(PROTECTED); + } + for (int i = 0; i < methodType.getParameterTypes().size(); i++) { + String parameterName = getSimpleName(method.getParameters().get(i)); + TypeName parameterType = methodType.getParameterTypes().get(i).getTypeName(); + builder.addParameter(ParameterSpec.builder(parameterType, parameterName).build()); + } + method.getThrownTypes().stream().map(XType::getTypeName).forEach(builder::addException); + return builder; + } + + private MethodSpecs() {} +} diff --git a/java/dagger/internal/codegen/xprocessing/XAnnotations.java b/java/dagger/internal/codegen/xprocessing/XAnnotations.java new file mode 100644 index 000000000..9e77a866f --- /dev/null +++ b/java/dagger/internal/codegen/xprocessing/XAnnotations.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 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.xprocessing; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; + +import androidx.room.compiler.processing.XAnnotation; +import com.google.auto.common.AnnotationMirrors; +import com.squareup.javapoet.ClassName; + +// TODO(bcorso): Consider moving these methods into XProcessing library. +/** A utility class for {@link XAnnotation} helper methods. */ +public final class XAnnotations { + + /** Returns the string representation of the given annotation. */ + public static String toString(XAnnotation annotation) { + return AnnotationMirrors.toString(toJavac(annotation)); + } + + /** Returns the class name of the given annotation */ + public static ClassName getClassName(XAnnotation annotation) { + return annotation.getType().getTypeElement().getClassName(); + } + + private XAnnotations() {} +} diff --git a/java/dagger/internal/codegen/xprocessing/XElements.java b/java/dagger/internal/codegen/xprocessing/XElements.java new file mode 100644 index 000000000..4c338ef42 --- /dev/null +++ b/java/dagger/internal/codegen/xprocessing/XElements.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2021 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.xprocessing; + +import static androidx.room.compiler.processing.XElementKt.isConstructor; +import static androidx.room.compiler.processing.XElementKt.isField; +import static androidx.room.compiler.processing.XElementKt.isMethod; +import static androidx.room.compiler.processing.XElementKt.isMethodParameter; +import static androidx.room.compiler.processing.XElementKt.isTypeElement; +import static androidx.room.compiler.processing.XElementKt.isVariableElement; +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static com.google.common.base.Preconditions.checkState; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import androidx.room.compiler.processing.XAnnotated; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XEnumEntry; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XFieldElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.XVariableElement; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import java.util.Collection; +import java.util.Optional; +import javax.lang.model.element.ElementKind; + +// TODO(bcorso): Consider moving these methods into XProcessing library. +/** A utility class for {@link XElement} helper methods. */ +public final class XElements { + + // TODO(bcorso): Replace usages with getJvmName() once it exists. + /** Returns the simple name of the element. */ + public static String getSimpleName(XElement element) { + return toJavac(element).getSimpleName().toString(); + } + + /** + * Returns the closest enclosing element that is a {@link XTypeElement} or throws an {@link + * IllegalStateException} if one doesn't exists. + */ + public static XTypeElement closestEnclosingTypeElement(XElement element) { + return optionalClosestEnclosingTypeElement(element) + .orElseThrow(() -> new IllegalStateException("No enclosing TypeElement for: " + element)); + } + + private static Optional<XTypeElement> optionalClosestEnclosingTypeElement(XElement element) { + if (isTypeElement(element)) { + return Optional.of(asTypeElement(element)); + } else if (isConstructor(element)) { + return Optional.of(asConstructor(element).getEnclosingElement()); + } else if (isMethod(element)) { + return optionalClosestEnclosingTypeElement(asMethod(element).getEnclosingElement()); + } else if (isField(element)) { + return optionalClosestEnclosingTypeElement(asField(element).getEnclosingElement()); + } else if (isMethodParameter(element)) { + return optionalClosestEnclosingTypeElement( + asMethodParameter(element).getEnclosingMethodElement()); + } + return Optional.empty(); + } + + public static boolean isEnumEntry(XElement element) { + return element instanceof XEnumEntry; + } + + public static boolean isEnum(XElement element) { + return toJavac(element).getKind() == ElementKind.ENUM; + } + + public static boolean isExecutable(XElement element) { + return isConstructor(element) || isMethod(element); + } + + public static XExecutableElement asExecutable(XElement element) { + checkState(isExecutable(element)); + return (XExecutableElement) element; + } + + public static XTypeElement asTypeElement(XElement element) { + checkState(isTypeElement(element)); + return (XTypeElement) element; + } + + // TODO(bcorso): Rename this and the XElementKt.isMethodParameter to isExecutableParameter. + public static XExecutableParameterElement asMethodParameter(XElement element) { + checkState(isMethodParameter(element)); + return (XExecutableParameterElement) element; + } + + public static XFieldElement asField(XElement element) { + checkState(isField(element)); + return (XFieldElement) element; + } + + public static XVariableElement asVariable(XElement element) { + checkState(isVariableElement(element)); + return (XVariableElement) element; + } + + public static XConstructorElement asConstructor(XElement element) { + checkState(isConstructor(element)); + return (XConstructorElement) element; + } + + public static XMethodElement asMethod(XElement element) { + checkState(isMethod(element)); + return (XMethodElement) element; + } + + public static ImmutableSet<XAnnotation> getAnnotatedAnnotations( + XAnnotated annotated, ClassName annotationName) { + return annotated.getAllAnnotations().stream() + .filter(annotation -> annotation.getType().getTypeElement().hasAnnotation(annotationName)) + .collect(toImmutableSet()); + } + + /** Returns {@code true} if {@code annotated} is annotated with any of the given annotations. */ + public static boolean hasAnyAnnotation(XAnnotated annotated, ClassName... annotations) { + return hasAnyAnnotation(annotated, ImmutableSet.copyOf(annotations)); + } + + /** Returns {@code true} if {@code annotated} is annotated with any of the given annotations. */ + public static boolean hasAnyAnnotation(XAnnotated annotated, Collection<ClassName> annotations) { + return annotations.stream().anyMatch(annotated::hasAnnotation); + } + + /** + * Returns any annotation from {@code annotations} that annotates {@code annotated} or else {@code + * Optional.empty()}. + */ + public static Optional<XAnnotation> getAnyAnnotation( + XAnnotated annotated, ClassName... annotations) { + return getAnyAnnotation(annotated, ImmutableSet.copyOf(annotations)); + } + + /** + * Returns any annotation from {@code annotations} that annotates {@code annotated} or else + * {@code Optional.empty()}. + */ + public static Optional<XAnnotation> getAnyAnnotation( + XAnnotated annotated, Collection<ClassName> annotations) { + return annotations.stream() + .filter(annotated::hasAnnotation) + .map(annotated::getAnnotation) + .findFirst(); + } + + /** Returns all annotations from {@code annotations} that annotate {@code annotated}. */ + public static ImmutableSet<XAnnotation> getAllAnnotations( + XAnnotated annotated, Collection<ClassName> annotations) { + return annotations.stream() + .filter(annotated::hasAnnotation) + .map(annotated::getAnnotation) + .collect(toImmutableSet()); + } + + private XElements() {} +} diff --git a/java/dagger/internal/codegen/xprocessing/XMethodElements.java b/java/dagger/internal/codegen/xprocessing/XMethodElements.java new file mode 100644 index 000000000..3cd8711ca --- /dev/null +++ b/java/dagger/internal/codegen/xprocessing/XMethodElements.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 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.xprocessing; + +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XTypeElement; + +// TODO(bcorso): Consider moving these methods into XProcessing library. +/** A utility class for {@link XMethodElement} helper methods. */ +public final class XMethodElements { + + /** Returns the type this method is enclosed in. */ + public static XTypeElement getEnclosingTypeElement(XMethodElement method) { + // TODO(bcorso): In Javac, a method is always enclosed in a type; however, once we start + // processing Kotlin we'll want to check this explicitly and add an error to the validation + // report if the method is not enclosed in a type. + return method.getEnclosingElement().getType().getTypeElement(); + } + + /** Returns {@code true} if the given method has type parameters. */ + public static boolean hasTypeParameters(XMethodElement method) { + return !method.getExecutableType().getTypeVariableNames().isEmpty(); + } + + private XMethodElements() {} +} diff --git a/java/dagger/internal/codegen/xprocessing/XTypeElements.java b/java/dagger/internal/codegen/xprocessing/XTypeElements.java new file mode 100644 index 000000000..c0759d11a --- /dev/null +++ b/java/dagger/internal/codegen/xprocessing/XTypeElements.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 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.xprocessing; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static com.google.common.base.Preconditions.checkNotNull; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static kotlin.streams.jdk8.StreamsKt.asStream; + +import androidx.room.compiler.processing.XHasModifiers; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XTypeElement; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +// TODO(bcorso): Consider moving these methods into XProcessing library. +/** A utility class for {@link XTypeElement} helper methods. */ +public final class XTypeElements { + private enum Visibility { + PUBLIC, + PRIVATE, + OTHER; + + /** Returns the visibility of the given {@link XTypeElement}. */ + private static Visibility of(XTypeElement element) { + checkNotNull(element); + if (element.isPrivate()) { + return Visibility.PRIVATE; + } else if (element.isPublic()) { + return Visibility.PUBLIC; + } else { + return Visibility.OTHER; + } + } + } + + /** Returns {@code true} if the given element is nested. */ + public static boolean isNested(XTypeElement typeElement) { + return typeElement.getEnclosingTypeElement() != null; + } + + /** Returns {@code true} if the given {@code type} has type parameters. */ + public static boolean hasTypeParameters(XTypeElement type) { + // TODO(bcorso): Add support for XTypeElement#getTypeParameters() or at least + // XTypeElement#hasTypeParameters() in XProcessing. XTypes#getTypeArguments() isn't quite the + // same -- it tells you if the declared type has parameters rather than the element itself. + return !toJavac(type).getTypeParameters().isEmpty(); + } + + /** Returns all non-private, non-static, abstract methods in {@code type}. */ + public static ImmutableList<XMethodElement> getAllUnimplementedMethods(XTypeElement type) { + return asStream(type.getAllNonPrivateInstanceMethods()) + .filter(XHasModifiers::isAbstract) + .collect(toImmutableList()); + } + + public static boolean isEffectivelyPublic(XTypeElement element) { + return allVisibilities(element).stream() + .allMatch(visibility -> visibility.equals(Visibility.PUBLIC)); + } + + public static boolean isEffectivelyPrivate(XTypeElement element) { + return allVisibilities(element).contains(Visibility.PRIVATE); + } + + /** + * Returns a list of visibilities containing visibility of the given element and the visibility of + * its enclosing elements. + */ + private static ImmutableSet<Visibility> allVisibilities(XTypeElement element) { + checkNotNull(element); + ImmutableSet.Builder<Visibility> visibilities = ImmutableSet.builder(); + XTypeElement currentElement = element; + while (currentElement != null) { + visibilities.add(Visibility.of(currentElement)); + currentElement = currentElement.getEnclosingTypeElement(); + } + return visibilities.build(); + } + + private XTypeElements() {} +} diff --git a/java/dagger/internal/codegen/xprocessing/XTypes.java b/java/dagger/internal/codegen/xprocessing/XTypes.java new file mode 100644 index 000000000..8a3c53766 --- /dev/null +++ b/java/dagger/internal/codegen/xprocessing/XTypes.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2021 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.xprocessing; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; + +import androidx.room.compiler.processing.XArrayType; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.compat.XConverters; +import com.squareup.javapoet.ClassName; +import javax.lang.model.type.TypeKind; + +// TODO(bcorso): Consider moving these methods into XProcessing library. +/** A utility class for {@link XType} helper methods. */ +public final class XTypes { + + /** Returns {@code true} if the given type is a raw type of a parameterized type. */ + public static boolean isRawParameterizedType(XType type) { + return isDeclared(type) + && type.getTypeArguments().isEmpty() + && !type.getTypeElement().getType().getTypeArguments().isEmpty(); + } + + /** Returns the given {@code type} as an {@link XArrayType}. */ + public static XArrayType asArray(XType type) { + return (XArrayType) type; + } + + /** Returns {@code true} if the raw type of {@code type} is equal to {@code className}. */ + public static boolean isTypeOf(XType type, ClassName className) { + return isDeclared(type) && type.getTypeElement().getClassName().equals(className); + } + + /** Returns {@code true} if the given type is a declared type. */ + public static boolean isWildcard(XType type) { + return toJavac(type).getKind().equals(TypeKind.WILDCARD); + } + + /** Returns {@code true} if the given type is a declared type. */ + public static boolean isDeclared(XType type) { + return type.getTypeElement() != null; + } + + /** Returns {@code true} if the given type is a type variable. */ + public static boolean isTypeVariable(XType type) { + return XConverters.toJavac(type).getKind() == TypeKind.TYPEVAR; + } + + /** + * Returns {@code true} if {@code type1} is equivalent to {@code type2}. + */ + public static boolean areEquivalentTypes(XType type1, XType type2) { + return type1.getTypeName().equals(type2.getTypeName()); + } + + /** Returns {@code true} if the given type is a primitive type. */ + public static boolean isPrimitive(XType type) { + return XConverters.toJavac(type).getKind().isPrimitive(); + } + + /** Returns {@code true} if the given type has type parameters. */ + public static boolean hasTypeParameters(XType type) { + return !type.getTypeArguments().isEmpty(); + } + + private XTypes() {} +} diff --git a/java/dagger/internal/codegen/xprocessing/xprocessing.jar b/java/dagger/internal/codegen/xprocessing/xprocessing.jar Binary files differnew file mode 100644 index 000000000..2b026687e --- /dev/null +++ b/java/dagger/internal/codegen/xprocessing/xprocessing.jar diff --git a/java/dagger/internal/guava/BUILD b/java/dagger/internal/guava/BUILD deleted file mode 100644 index 0bd4dcc60..000000000 --- a/java/dagger/internal/guava/BUILD +++ /dev/null @@ -1,68 +0,0 @@ -# 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. - -# Description: -# Aliases from guava libraries to //third_party/java/guava. - -package(default_visibility = ["//:src"]) - -alias( - name = "annotations", - actual = "@google_bazel_common//third_party/java/guava", -) - -alias( - name = "base", - actual = "@google_bazel_common//third_party/java/guava", -) - -alias( - name = "cache", - actual = "@google_bazel_common//third_party/java/guava", -) - -alias( - name = "collect", - actual = "@google_bazel_common//third_party/java/guava", -) - -alias( - name = "graph", - actual = "@google_bazel_common//third_party/java/guava", -) - -alias( - name = "io", - actual = "@google_bazel_common//third_party/java/guava", -) - -alias( - name = "concurrent", - actual = "@google_bazel_common//third_party/java/guava", -) - -alias( - name = "base-android", - actual = "@maven//:com_google_guava_guava", -) - -alias( - name = "collect-android", - actual = "@maven//:com_google_guava_guava", -) - -alias( - name = "concurrent-android", - actual = "@maven//:com_google_guava_guava", -) diff --git a/java/dagger/lint/BUILD b/java/dagger/lint/BUILD index 3e0ffec2e..173d035c3 100644 --- a/java/dagger/lint/BUILD +++ b/java/dagger/lint/BUILD @@ -26,7 +26,7 @@ kt_jvm_library( srcs = glob(["*.kt"]), tags = ["maven_coordinates=com.google.dagger:dagger-lint:" + POM_VERSION], deps = [ - "@google_bazel_common//third_party/java/auto:service", + "//third_party/java/auto:service", "@maven//:com_android_tools_external_com_intellij_intellij_core", "@maven//:com_android_tools_external_com_intellij_kotlin_compiler", "@maven//:com_android_tools_external_org_jetbrains_uast", diff --git a/java/dagger/lint/DaggerIssueRegistry.kt b/java/dagger/lint/DaggerIssueRegistry.kt index 113e85c01..f82168831 100644 --- a/java/dagger/lint/DaggerIssueRegistry.kt +++ b/java/dagger/lint/DaggerIssueRegistry.kt @@ -16,6 +16,7 @@ package dagger.lint import com.android.tools.lint.client.api.IssueRegistry +import com.android.tools.lint.client.api.Vendor import com.android.tools.lint.detector.api.CURRENT_API import com.android.tools.lint.detector.api.Issue import com.google.auto.service.AutoService @@ -35,6 +36,12 @@ class DaggerIssueRegistry : IssueRegistry() { // The api is meant to be the current api for which this registry was compiled, but we set a // higher number without depending on a newer Lint to avoid Lint warning users of custom checks // that might not work. This value eventually has to be updated as newer Api become available. - override val api: Int = 8 + override val api: Int = 11 override val issues: List<Issue> = DaggerKotlinIssueDetector.issues + override val vendor = Vendor( + vendorName = "Google", + identifier = "com.google.dagger:dagger-lint", + feedbackUrl = "https://github.com/google/dagger/issues", + contact = "https://github.com/google/dagger" + ) } diff --git a/java/dagger/lint/DaggerKotlinIssueDetector.kt b/java/dagger/lint/DaggerKotlinIssueDetector.kt index f3fdbd318..8ca862309 100644 --- a/java/dagger/lint/DaggerKotlinIssueDetector.kt +++ b/java/dagger/lint/DaggerKotlinIssueDetector.kt @@ -42,7 +42,6 @@ import org.jetbrains.uast.UElement import org.jetbrains.uast.UField import org.jetbrains.uast.UMethod import org.jetbrains.uast.getUastParentOfType -import org.jetbrains.uast.kotlin.KotlinUClass import org.jetbrains.uast.toUElement /** @@ -61,7 +60,9 @@ import org.jetbrains.uast.toUElement * `@Module` when the parent class is _not_ also annotated with `@Module`. While technically legal, * these should be moved up to top-level objects to avoid confusion. */ -@Suppress("UnstableApiUsage") // Lots of Lint APIs are marked with @Beta. +@Suppress( + "UnstableApiUsage" // Lots of Lint APIs are marked with @Beta. +) class DaggerKotlinIssueDetector : Detector(), SourceCodeScanner { companion object { @@ -164,9 +165,9 @@ class DaggerKotlinIssueDetector : Detector(), SourceCodeScanner { } // Can't use hasAnnotation because it doesn't capture all annotations! val injectAnnotation = - node.annotations.find { it.qualifiedName == INJECT_ANNOTATION } ?: return + node.uAnnotations.find { it.qualifiedName == INJECT_ANNOTATION } ?: return // Look for qualifier annotations - node.annotations.forEach { annotation -> + node.uAnnotations.forEach { annotation -> if (annotation === injectAnnotation) { // Skip the inject annotation return@forEach @@ -257,6 +258,6 @@ class DaggerKotlinIssueDetector : Detector(), SourceCodeScanner { /** @return whether or not the [this] is a Kotlin `object` type. */ private fun UClass.isObject(): Boolean { - return this is KotlinUClass && ktClass is KtObjectDeclaration + return sourcePsi is KtObjectDeclaration } } diff --git a/java/dagger/model/BUILD b/java/dagger/model/BUILD index 0be8fc5fc..71a6c6b7d 100644 --- a/java/dagger/model/BUILD +++ b/java/dagger/model/BUILD @@ -32,13 +32,10 @@ package( ], ) -INTERNAL_PROXIES = ["BindingGraphProxies.java"] - filegroup( name = "model-srcs", srcs = glob( ["*.java"], - exclude = INTERNAL_PROXIES, ), ) @@ -49,27 +46,14 @@ java_library( deps = [ "//java/dagger:core", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:graph", "//java/dagger/producers", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/error_prone:annotations", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/jsr330_inject", - "@maven//:com_google_auto_auto_common", - ], -) - -java_library( - name = "internal-proxies", - srcs = INTERNAL_PROXIES, - tags = ["maven:merged"], - visibility = ["//:src"], - deps = [ - ":model", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:graph", - "@google_bazel_common//third_party/java/auto:value", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/error_prone:annotations", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/graph", + "//third_party/java/javapoet", + "//third_party/java/jsr330_inject", ], ) diff --git a/java/dagger/model/BindingGraphProxies.java b/java/dagger/model/BindingGraphProxies.java deleted file mode 100644 index 85d4df8ca..000000000 --- a/java/dagger/model/BindingGraphProxies.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2018 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.model; - -import com.google.auto.value.AutoValue; -import com.google.auto.value.extension.memoized.Memoized; -import com.google.common.collect.ImmutableSetMultimap; -import com.google.common.graph.ImmutableNetwork; -import com.google.common.graph.Network; -import dagger.model.BindingGraph.Edge; -import dagger.model.BindingGraph.MissingBinding; -import dagger.model.BindingGraph.Node; - -/** - * Exposes package-private constructors to the {@code dagger.internal.codegen} package. <em>This - * class should only be used in the Dagger implementation and is not part of any documented - * API.</em> - */ -public final class BindingGraphProxies { - - @AutoValue - abstract static class BindingGraphImpl extends BindingGraph { - @Override - @Memoized - public ImmutableSetMultimap<Class<? extends Node>, ? extends Node> nodesByClass() { - return super.nodesByClass(); - } - } - - @AutoValue - abstract static class MissingBindingImpl extends MissingBinding { - @Memoized - @Override - public abstract int hashCode(); - - @Override - public abstract boolean equals(Object o); - } - - /** Creates a new {@link BindingGraph}. */ - public static BindingGraph bindingGraph(Network<Node, Edge> network, boolean isFullBindingGraph) { - return new AutoValue_BindingGraphProxies_BindingGraphImpl( - ImmutableNetwork.copyOf(network), isFullBindingGraph); - } - - /** Creates a new {@link MissingBinding}. */ - public static MissingBinding missingBindingNode(ComponentPath component, Key key) { - return new AutoValue_BindingGraphProxies_MissingBindingImpl(component, key); - } - - private BindingGraphProxies() {} -} diff --git a/java/dagger/model/Key.java b/java/dagger/model/Key.java index 03dd41c76..aeeabdff2 100644 --- a/java/dagger/model/Key.java +++ b/java/dagger/model/Key.java @@ -111,7 +111,7 @@ public abstract class Key { * and {@code @A(c = "c", b = "b", attributeWithDefaultValue = "default value")}. */ // TODO(ronshapiro): move this to auto-common - private static String stableAnnotationMirrorToString(AnnotationMirror qualifier) { + static String stableAnnotationMirrorToString(AnnotationMirror qualifier) { StringBuilder builder = new StringBuilder("@").append(qualifier.getAnnotationType()); ImmutableMap<ExecutableElement, AnnotationValue> elementValues = AnnotationMirrors.getAnnotationValuesWithDefaults(qualifier); diff --git a/java/dagger/model/Scope.java b/java/dagger/model/Scope.java index f48fa16df..c7ebfa811 100644 --- a/java/dagger/model/Scope.java +++ b/java/dagger/model/Scope.java @@ -24,9 +24,9 @@ import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; import com.google.common.base.Equivalence; +import com.squareup.javapoet.ClassName; import dagger.Reusable; import dagger.producers.ProductionScope; -import java.lang.annotation.Annotation; import javax.inject.Singleton; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.TypeElement; @@ -66,26 +66,35 @@ public abstract class Scope { * Returns {@code true} if {@code scopeAnnotationType} is a {@link javax.inject.Scope} annotation. */ public static boolean isScope(TypeElement scopeAnnotationType) { - return isAnnotationPresent(scopeAnnotationType, javax.inject.Scope.class); + return isAnnotationPresent(scopeAnnotationType, SCOPE.canonicalName()) + || isAnnotationPresent(scopeAnnotationType, SCOPE_JAVAX.canonicalName()); } + private static final ClassName PRODUCTION_SCOPE = + ClassName.get("dagger.producers", "ProductionScope"); + private static final ClassName SINGLETON = ClassName.get("jakarta.inject", "Singleton"); + private static final ClassName SINGLETON_JAVAX = ClassName.get("javax.inject", "Singleton"); + private static final ClassName REUSABLE = ClassName.get("dagger", "Reusable"); + private static final ClassName SCOPE = ClassName.get("jakarta.inject", "Scope"); + private static final ClassName SCOPE_JAVAX = ClassName.get("javax.inject", "Scope"); + /** Returns {@code true} if this scope is the {@link Singleton @Singleton} scope. */ public final boolean isSingleton() { - return isScope(Singleton.class); + return isScope(SINGLETON) || isScope(SINGLETON_JAVAX); } /** Returns {@code true} if this scope is the {@link Reusable @Reusable} scope. */ public final boolean isReusable() { - return isScope(Reusable.class); + return isScope(REUSABLE); } /** Returns {@code true} if this scope is the {@link ProductionScope @ProductionScope} scope. */ public final boolean isProductionScope() { - return isScope(ProductionScope.class); + return isScope(PRODUCTION_SCOPE); } - private boolean isScope(Class<? extends Annotation> annotation) { - return scopeAnnotationElement().getQualifiedName().contentEquals(annotation.getCanonicalName()); + private boolean isScope(ClassName className) { + return scopeAnnotationElement().getQualifiedName().contentEquals(className.canonicalName()); } /** Returns a debug representation of the scope. */ diff --git a/java/dagger/model/testing/BUILD b/java/dagger/model/testing/BUILD index 9c9f44aea..722990aa0 100644 --- a/java/dagger/model/testing/BUILD +++ b/java/dagger/model/testing/BUILD @@ -31,11 +31,11 @@ java_library( javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES, deps = [ "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", "//java/dagger/spi", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/checker_framework_annotations", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/auto:value", + "//third_party/java/checker_framework_annotations", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/truth", ], ) diff --git a/java/dagger/multibindings/ClassKey.java b/java/dagger/multibindings/ClassKey.java index ac255457c..edc9ea356 100644 --- a/java/dagger/multibindings/ClassKey.java +++ b/java/dagger/multibindings/ClassKey.java @@ -16,11 +16,11 @@ package dagger.multibindings; -import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import dagger.MapKey; import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -31,7 +31,11 @@ import java.lang.annotation.Target; * member whose type is {@code Class<? extends Something>}. */ @Documented -@Target(METHOD) +// While METHOD is the only valid target for Dagger, FIELD was added to support Hilt's +// @BindValueIntoMap and TYPE was added to support external extension types since it likely won't +// cause confusion/maintenance issues as this isn't part of Dagger's core API. +// See discussion on https://github.com/google/dagger/pull/2831#issuecomment-919417457 for details. +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) @Retention(RUNTIME) @MapKey public @interface ClassKey { diff --git a/java/dagger/multibindings/IntKey.java b/java/dagger/multibindings/IntKey.java index 55e79a1bf..1e7960f53 100644 --- a/java/dagger/multibindings/IntKey.java +++ b/java/dagger/multibindings/IntKey.java @@ -16,17 +16,21 @@ package dagger.multibindings; -import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import dagger.MapKey; import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; /** A {@link MapKey} annotation for maps with {@code int} keys. */ @Documented -@Target(METHOD) +// While METHOD is the only valid target for Dagger, FIELD was added to support Hilt's +// @BindValueIntoMap and TYPE was added to support external extension types since it likely won't +// cause confusion/maintenance issues as this isn't part of Dagger's core API. +// See discussion on https://github.com/google/dagger/pull/2831#issuecomment-919417457 for details. +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) @Retention(RUNTIME) @MapKey public @interface IntKey { diff --git a/java/dagger/multibindings/LongKey.java b/java/dagger/multibindings/LongKey.java index 71d0fe116..802478f16 100644 --- a/java/dagger/multibindings/LongKey.java +++ b/java/dagger/multibindings/LongKey.java @@ -16,17 +16,21 @@ package dagger.multibindings; -import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import dagger.MapKey; import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; /** A {@link MapKey} annotation for maps with {@code long} keys. */ @Documented -@Target(METHOD) +// While METHOD is the only valid target for Dagger, FIELD was added to support Hilt's +// @BindValueIntoMap and TYPE was added to support external extension types since it likely won't +// cause confusion/maintenance issues as this isn't part of Dagger's core API. +// See discussion on https://github.com/google/dagger/pull/2831#issuecomment-919417457 for details. +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) @Retention(RUNTIME) @MapKey public @interface LongKey { diff --git a/java/dagger/multibindings/StringKey.java b/java/dagger/multibindings/StringKey.java index 5dad8e3e9..c773ceab6 100644 --- a/java/dagger/multibindings/StringKey.java +++ b/java/dagger/multibindings/StringKey.java @@ -16,17 +16,21 @@ package dagger.multibindings; -import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import dagger.MapKey; import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; /** A {@link MapKey} annotation for maps with {@link String} keys. */ @Documented -@Target(METHOD) +// While METHOD is the only valid target for Dagger, FIELD was added to support Hilt's +// @BindValueIntoMap and TYPE was added to support external extension types since it likely won't +// cause confusion/maintenance issues as this isn't part of Dagger's core API. +// See discussion on https://github.com/google/dagger/pull/2831#issuecomment-919417457 for details. +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) @Retention(RUNTIME) @MapKey public @interface StringKey { diff --git a/java/dagger/producers/BUILD b/java/dagger/producers/BUILD index 41762e6e1..b966ad588 100644 --- a/java/dagger/producers/BUILD +++ b/java/dagger/producers/BUILD @@ -42,18 +42,18 @@ java_library( javacopts = SOURCE_7_TARGET_7 + DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES, tags = ["maven_coordinates=com.google.dagger:dagger-producers:" + POM_VERSION], exports = [ - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:concurrent", - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/guava/base", + "//third_party/java/guava/util/concurrent", + "//third_party/java/jsr330_inject", ], deps = [ "//java/dagger:core", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", - "@google_bazel_common//third_party/java/checker_framework_annotations", - "@google_bazel_common//third_party/java/error_prone:annotations", - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/checker_framework_annotations", + "//third_party/java/error_prone:annotations", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/util/concurrent", + "//third_party/java/jsr330_inject", ], ) diff --git a/java/dagger/producers/internal/AnnotationUsages.java b/java/dagger/producers/internal/AnnotationUsages.java new file mode 100644 index 000000000..9e7760837 --- /dev/null +++ b/java/dagger/producers/internal/AnnotationUsages.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 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.producers.internal; + +import dagger.producers.Production; +import dagger.producers.ProductionScope; + +/** + * This class should never be referenced directly! + * + * This class should only be used by Dagger's annotation processor to get access to the annotations + * types. + */ +final class AnnotationUsages { + @Production + static final class ProductionUsage {} + + @ProductionImplementation + static final class ProductionImplementationUsage {} + + @ProductionScope + static final class ProductionScopeUsage {} + + private AnnotationUsages() {} +} diff --git a/java/dagger/spi/BUILD b/java/dagger/spi/BUILD index cbec4658d..529842ad3 100644 --- a/java/dagger/spi/BUILD +++ b/java/dagger/spi/BUILD @@ -30,6 +30,7 @@ filegroup( name = "spi-srcs", srcs = glob(["*.java"]) + [ "//java/dagger/model:model-srcs", + "//java/dagger/spi/model:model-srcs", ], ) @@ -40,15 +41,13 @@ java_library( tags = ["maven_coordinates=com.google.dagger:dagger-spi:" + POM_VERSION], exports = [ "//java/dagger/model", + "//java/dagger/spi/model", ], deps = [ "//java/dagger:core", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", "//java/dagger/model", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/error_prone:annotations", - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/auto:value", + "//third_party/java/error_prone:annotations", ], ) @@ -59,23 +58,33 @@ gen_maven_artifact( artifact_target = ":spi", artifact_target_libs = [ "//java/dagger/internal/codegen/extension", + "//java/dagger/internal/codegen/xprocessing", "//java/dagger/model", + "//java/dagger/spi/model", ], artifact_target_maven_deps = [ "com.google.auto:auto-common", "com.google.code.findbugs:jsr305", "com.google.dagger:dagger-producers", "com.google.dagger:dagger", + "com.google.devtools.ksp:symbol-processing-api", "com.google.guava:failureaccess", "com.google.guava:guava", "com.squareup:javapoet", "javax.inject:javax.inject", + "org.jetbrains.kotlin:kotlin-stdlib", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8", ], javadoc_root_packages = [ "dagger.model", "dagger.spi", ], javadoc_srcs = [":spi-srcs"], - shaded_deps = ["@maven//:com_google_auto_auto_common"], - shaded_rules = ["rule com.google.auto.common.** dagger.spi.shaded.auto.common.@1"], + # The shaded deps are added using jarjar, but they won't be shaded until later + # due to: https://github.com/google/dagger/issues/2765. For the shaded rules see + # util/deploy-dagger.sh + shaded_deps = [ + "//third_party/java/auto:common", + "//java/dagger/internal/codegen/xprocessing:xprocessing-jar", + ], ) diff --git a/java/dagger/spi/model/BUILD b/java/dagger/spi/model/BUILD new file mode 100644 index 000000000..9c825b4cb --- /dev/null +++ b/java/dagger/spi/model/BUILD @@ -0,0 +1,60 @@ +# Copyright (C) 2021 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. + +# Description: +# Dagger's core APIs exposed for plugins + +load("@rules_java//java:defs.bzl", "java_library") +load( + "//:build_defs.bzl", + "DOCLINT_HTML_AND_SYNTAX", + "DOCLINT_REFERENCES", +) + +package( + default_visibility = [ + # The dagger/spi should be the only direct dependent on this target. + # If you need to depend on :model, depend on dagger/spi instead so + # that pom files correctly pick up the spi maven dependency. + # TODO(bcorso): Consider if :model should have its own maven coordinates. + "//java/dagger/spi:__pkg__", + ], +) + +filegroup( + name = "model-srcs", + srcs = glob(["*.java"]), +) + +java_library( + name = "model", + srcs = [":model-srcs"], + javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES, + deps = [ + "//java/dagger:core", + "//java/dagger/internal/codegen/extension", + "//java/dagger/internal/codegen/xprocessing", + "//java/dagger/producers", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/error_prone:annotations", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/graph", + "//third_party/java/javapoet", + "//third_party/java/jsr305_annotations", + "//third_party/java/jsr330_inject", + "@maven//:com_google_devtools_ksp_symbol_processing_api", + ], +) diff --git a/java/dagger/spi/model/Binding.java b/java/dagger/spi/model/Binding.java new file mode 100644 index 000000000..3c6c6a165 --- /dev/null +++ b/java/dagger/spi/model/Binding.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import com.google.common.collect.ImmutableSet; +import dagger.spi.model.BindingGraph.MaybeBinding; +import java.util.Optional; + +/** + * The association between a {@link Key} and the way in which instances of the key are provided. + * Includes any {@linkplain DependencyRequest dependencies} that are needed in order to provide the + * instances. + * + * <p>If a binding is owned by more than one component, there is one {@code Binding} for every + * owning component. + */ +public interface Binding extends MaybeBinding { + @Override + ComponentPath componentPath(); + + /** @deprecated This always returns {@code Optional.of(this)}. */ + @Override + @Deprecated + default Optional<Binding> binding() { + return Optional.of(this); + } + /** + * The dependencies of this binding. The order of the dependencies corresponds to the order in + * which they will be injected when the binding is requested. + */ + ImmutableSet<DependencyRequest> dependencies(); + + /** + * The {@link DaggerElement} that declares this binding. Absent for + * {@linkplain BindingKind binding kinds} that are not always declared by exactly one element. + * + * <p>For example, consider {@link BindingKind#MULTIBOUND_SET}. A component with many + * {@code @IntoSet} bindings for the same key will have a synthetic binding that depends on all + * contributions, but with no identifiying binding element. A {@code @Multibinds} method will also + * contribute a synthetic binding, but since multiple {@code @Multibinds} methods can coexist in + * the same component (and contribute to one single binding), it has no binding element. + */ + Optional<DaggerElement> bindingElement(); + + /** + * The {@link DaggerTypeElement} of the module which contributes this binding. Absent for bindings + * that have no {@link #bindingElement() binding element}. + */ + Optional<DaggerTypeElement> contributingModule(); + + /** + * Returns {@code true} if using this binding requires an instance of the {@link + * #contributingModule()}. + */ + boolean requiresModuleInstance(); + + /** The scope of this binding if it has one. */ + Optional<Scope> scope(); + + /** + * Returns {@code true} if this binding may provide {@code null} instead of an instance of {@link + * #key()}. Nullable bindings cannot be requested from {@linkplain DependencyRequest#isNullable() + * non-nullable dependency requests}. + */ + boolean isNullable(); + + /** Returns {@code true} if this is a production binding, e.g. an {@code @Produces} method. */ + boolean isProduction(); + + /** The kind of binding this instance represents. */ + BindingKind kind(); + +} diff --git a/java/dagger/spi/model/BindingGraph.java b/java/dagger/spi/model/BindingGraph.java new file mode 100644 index 000000000..f10ffe2f6 --- /dev/null +++ b/java/dagger/spi/model/BindingGraph.java @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import static com.google.common.collect.Sets.intersection; +import static com.google.common.graph.Graphs.inducedSubgraph; +import static com.google.common.graph.Graphs.reachableNodes; +import static com.google.common.graph.Graphs.transpose; +import static dagger.internal.codegen.extension.DaggerStreams.instancesOf; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.graph.EndpointPair; +import com.google.common.graph.ImmutableNetwork; +import com.google.common.graph.MutableNetwork; +import com.google.common.graph.Network; +import com.google.common.graph.NetworkBuilder; +import dagger.Module; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * A graph of bindings, dependency requests, and components. + * + * <p>A {@link BindingGraph} represents one of the following: + * + * <ul> + * <li>an entire component hierarchy rooted at a {@link dagger.Component} or {@link + * dagger.producers.ProductionComponent} + * <li>a partial component hierarchy rooted at a {@link dagger.Subcomponent} or {@link + * dagger.producers.ProductionSubcomponent} (only when the value of {@code + * -Adagger.fullBindingGraphValidation} is not {@code NONE}) + * <li>the bindings installed by a {@link Module} or {@link dagger.producers.ProducerModule}, + * including all subcomponents generated by {@link Module#subcomponents()} ()} and {@link + * dagger.producers.ProducerModule#subcomponents()} ()} + * </ul> + * + * In the case of a {@link BindingGraph} representing a module, the root {@link ComponentNode} will + * actually represent the module type. The graph will also be a {@linkplain #isFullBindingGraph() + * full binding graph}, which means it will contain all bindings in all modules, as well as nodes + * for their dependencies. Otherwise it will contain only bindings that are reachable from at least + * one {@linkplain #entryPointEdges() entry point}. + * + * <h3>Nodes</h3> + * + * <p>There is a <b>{@link Binding}</b> for each owned binding in the graph. If a binding is owned + * by more than one component, there is one binding object for that binding for every owning + * component. + * + * <p>There is a <b>{@linkplain ComponentNode component node}</b> (without a binding) for each + * component in the graph. + * + * <h3>Edges</h3> + * + * <p>There is a <b>{@linkplain DependencyEdge dependency edge}</b> for each dependency request in + * the graph. Its target node is the binding for the binding that satisfies the request. For entry + * point dependency requests, the source node is the component node for the component for which it + * is an entry point. For other dependency requests, the source node is the binding for the binding + * that contains the request. + * + * <p>There is a <b>subcomponent edge</b> for each parent-child component relationship in the graph. + * The target node is the component node for the child component. For subcomponents defined by a + * {@linkplain SubcomponentCreatorBindingEdge subcomponent creator binding} (either a method on the + * component or a set of {@code @Module.subcomponents} annotation values), the source node is the + * binding for the {@code @Subcomponent.Builder} type. For subcomponents defined by {@linkplain + * ChildFactoryMethodEdge subcomponent factory methods}, the source node is the component node for + * the parent. + * + * <p><b>Note that this API is experimental and will change.</b> + */ +public abstract class BindingGraph { + /** Returns the graph in its {@link Network} representation. */ + public abstract ImmutableNetwork<Node, Edge> network(); + + @Override + public String toString() { + return network().toString(); + } + + /** + * Returns {@code true} if this graph was constructed from a module for full binding graph + * validation. + * + * @deprecated use {@link #isFullBindingGraph()} to tell if this is a full binding graph, or + * {@link ComponentNode#isRealComponent() rootComponentNode().isRealComponent()} to tell if + * the root component node is really a component or derived from a module. Dagger can generate + * full binding graphs for components and subcomponents as well as modules. + */ + @Deprecated + public boolean isModuleBindingGraph() { + return !rootComponentNode().isRealComponent(); + } + + /** + * Returns {@code true} if this is a full binding graph, which contains all bindings installed in + * the component, or {@code false} if it is a reachable binding graph, which contains only + * bindings that are reachable from at least one {@linkplain #entryPointEdges() entry point}. + * + * @see <a href="https://dagger.dev/compiler-options#full-binding-graph-validation">Full binding + * graph validation</a> + */ + public abstract boolean isFullBindingGraph(); + + /** + * Returns {@code true} if the {@link #rootComponentNode()} is a subcomponent. This occurs in + * when {@code -Adagger.fullBindingGraphValidation} is used in a compilation with a subcomponent. + * + * @deprecated use {@link ComponentNode#isSubcomponent() rootComponentNode().isSubcomponent()} + * instead + */ + @Deprecated + public boolean isPartialBindingGraph() { + return rootComponentNode().isSubcomponent(); + } + + /** Returns the bindings. */ + public ImmutableSet<Binding> bindings() { + return nodes(Binding.class); + } + + /** Returns the bindings for a key. */ + public ImmutableSet<Binding> bindings(Key key) { + return nodes(Binding.class).stream() + .filter(binding -> binding.key().equals(key)) + .collect(toImmutableSet()); + } + + /** Returns the nodes that represent missing bindings. */ + public ImmutableSet<MissingBinding> missingBindings() { + return nodes(MissingBinding.class); + } + + /** Returns the component nodes. */ + public ImmutableSet<ComponentNode> componentNodes() { + return nodes(ComponentNode.class); + } + + /** Returns the component node for a component. */ + public Optional<ComponentNode> componentNode(ComponentPath component) { + return componentNodes().stream() + .filter(node -> node.componentPath().equals(component)) + .findFirst(); + } + + /** Returns the component nodes for a component. */ + public ImmutableSet<ComponentNode> componentNodes(DaggerTypeElement component) { + return componentNodes().stream() + .filter(node -> node.componentPath().currentComponent().equals(component)) + .collect(toImmutableSet()); + } + + /** Returns the component node for the root component. */ + public ComponentNode rootComponentNode() { + return componentNodes().stream() + .filter(node -> node.componentPath().atRoot()) + .findFirst() + .get(); + } + + /** Returns the dependency edges. */ + public ImmutableSet<DependencyEdge> dependencyEdges() { + return dependencyEdgeStream().collect(toImmutableSet()); + } + + /** + * Returns the dependency edges for the dependencies of a binding. For valid graphs, each {@link + * DependencyRequest} will map to a single {@link DependencyEdge}. When conflicting bindings exist + * for a key, the multimap will have several edges for that {@link DependencyRequest}. Graphs that + * have no binding for a key will have an edge whose {@linkplain EndpointPair#target() target + * node} is a {@link MissingBinding}. + */ + public ImmutableSetMultimap<DependencyRequest, DependencyEdge> dependencyEdges( + Binding binding) { + return dependencyEdgeStream(binding) + .collect(toImmutableSetMultimap(DependencyEdge::dependencyRequest, edge -> edge)); + } + + /** Returns the dependency edges for a dependency request. */ + public ImmutableSet<DependencyEdge> dependencyEdges(DependencyRequest dependencyRequest) { + return dependencyEdgeStream() + .filter(edge -> edge.dependencyRequest().equals(dependencyRequest)) + .collect(toImmutableSet()); + } + + /** + * Returns the dependency edges for the entry points of a given {@code component}. Each edge's + * source node is that component's component node. + */ + public ImmutableSet<DependencyEdge> entryPointEdges(ComponentPath component) { + return dependencyEdgeStream(componentNode(component).get()).collect(toImmutableSet()); + } + + private Stream<DependencyEdge> dependencyEdgeStream(Node node) { + return network().outEdges(node).stream().flatMap(instancesOf(DependencyEdge.class)); + } + + /** + * Returns the dependency edges for all entry points for all components and subcomponents. Each + * edge's source node is a component node. + */ + public ImmutableSet<DependencyEdge> entryPointEdges() { + return entryPointEdgeStream().collect(toImmutableSet()); + } + + /** Returns the binding or missing binding nodes that directly satisfy entry points. */ + public ImmutableSet<MaybeBinding> entryPointBindings() { + return entryPointEdgeStream() + .map(edge -> (MaybeBinding) network().incidentNodes(edge).target()) + .collect(toImmutableSet()); + } + + /** + * Returns the edges for entry points that transitively depend on a binding or missing binding for + * a key. + */ + public ImmutableSet<DependencyEdge> entryPointEdgesDependingOnBinding( + MaybeBinding binding) { + ImmutableNetwork<Node, DependencyEdge> dependencyGraph = dependencyGraph(); + Network<Node, DependencyEdge> subgraphDependingOnBinding = + inducedSubgraph( + dependencyGraph, reachableNodes(transpose(dependencyGraph).asGraph(), binding)); + return intersection(entryPointEdges(), subgraphDependingOnBinding.edges()).immutableCopy(); + } + + /** Returns the bindings that directly request a given binding as a dependency. */ + public ImmutableSet<Binding> requestingBindings(MaybeBinding binding) { + return network().predecessors(binding).stream() + .flatMap(instancesOf(Binding.class)) + .collect(toImmutableSet()); + } + + /** + * Returns the bindings that a given binding directly requests as a dependency. Does not include + * any {@link MissingBinding}s. + * + * @see #requestedMaybeMissingBindings(Binding) + */ + public ImmutableSet<Binding> requestedBindings(Binding binding) { + return network().successors(binding).stream() + .flatMap(instancesOf(Binding.class)) + .collect(toImmutableSet()); + } + + /** + * Returns the bindings or missing bindings that a given binding directly requests as a + * dependency. + * + * @see #requestedBindings(Binding) + */ + public ImmutableSet<MaybeBinding> requestedMaybeMissingBindings(Binding binding) { + return network().successors(binding).stream() + .flatMap(instancesOf(MaybeBinding.class)) + .collect(toImmutableSet()); + } + + /** Returns a subnetwork that contains all nodes but only {@link DependencyEdge}s. */ + // TODO(dpb): Make public. Cache. + private ImmutableNetwork<Node, DependencyEdge> dependencyGraph() { + MutableNetwork<Node, DependencyEdge> dependencyGraph = + NetworkBuilder.from(network()) + .expectedNodeCount(network().nodes().size()) + .expectedEdgeCount((int) dependencyEdgeStream().count()) + .build(); + network().nodes().forEach(dependencyGraph::addNode); // include disconnected nodes + dependencyEdgeStream() + .forEach( + edge -> { + EndpointPair<Node> endpoints = network().incidentNodes(edge); + dependencyGraph.addEdge(endpoints.source(), endpoints.target(), edge); + }); + return ImmutableNetwork.copyOf(dependencyGraph); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private <N extends Node> ImmutableSet<N> nodes(Class<N> clazz) { + return (ImmutableSet) nodesByClass().get(clazz); + } + + private static final ImmutableSet<Class<? extends Node>> NODE_TYPES = + ImmutableSet.of(Binding.class, MissingBinding.class, ComponentNode.class); + + protected ImmutableSetMultimap<Class<? extends Node>, ? extends Node> nodesByClass() { + return network().nodes().stream() + .collect( + toImmutableSetMultimap( + node -> + NODE_TYPES.stream().filter(clazz -> clazz.isInstance(node)).findFirst().get(), + node -> node)); + } + + private Stream<DependencyEdge> dependencyEdgeStream() { + return network().edges().stream().flatMap(instancesOf(DependencyEdge.class)); + } + + private Stream<DependencyEdge> entryPointEdgeStream() { + return dependencyEdgeStream().filter(DependencyEdge::isEntryPoint); + } + + /** + * An edge in the binding graph. Either a {@link DependencyEdge}, a {@link + * ChildFactoryMethodEdge}, or a {@link SubcomponentCreatorBindingEdge}. + */ + public interface Edge {} + + /** + * An edge that represents a dependency on a binding. + * + * <p>Because one {@link DependencyRequest} may represent a dependency from two bindings (e.g., a + * dependency of {@code Foo<String>} and {@code Foo<Number>} may have the same key and request + * element), this class does not override {@link #equals(Object)} to use value semantics. + * + * <p>For entry points, the source node is the {@link ComponentNode} that contains the entry + * point. Otherwise the source node is a {@link Binding}. + * + * <p>For dependencies on missing bindings, the target node is a {@link MissingBinding}. Otherwise + * the target node is a {@link Binding}. + */ + public interface DependencyEdge extends Edge { + /** The dependency request. */ + DependencyRequest dependencyRequest(); + + /** Returns {@code true} if this edge represents an entry point. */ + boolean isEntryPoint(); + } + + /** + * An edge that represents a subcomponent factory method linking a parent component to a child + * subcomponent. + */ + public interface ChildFactoryMethodEdge extends Edge { + /** The subcomponent factory method element. */ + DaggerExecutableElement factoryMethod(); + } + + /** + * An edge that represents the link between a parent component and a child subcomponent implied by + * a subcomponent creator ({@linkplain dagger.Subcomponent.Builder builder} or {@linkplain + * dagger.Subcomponent.Factory factory}) binding. + * + * <p>The {@linkplain com.google.common.graph.EndpointPair#source() source node} of this edge is a + * {@link Binding} for the subcomponent creator {@link Key} and the {@linkplain + * com.google.common.graph.EndpointPair#target() target node} is a {@link ComponentNode} for the + * child subcomponent. + */ + public interface SubcomponentCreatorBindingEdge extends Edge { + /** + * The modules that {@linkplain Module#subcomponents() declare the subcomponent} that generated + * this edge. Empty if the parent component has a subcomponent creator method and there are no + * declaring modules. + */ + ImmutableSet<DaggerTypeElement> declaringModules(); + } + + /** A node in the binding graph. Either a {@link Binding} or a {@link ComponentNode}. */ + // TODO(dpb): Make all the node/edge types top-level. + public interface Node { + /** The component this node belongs to. */ + ComponentPath componentPath(); + } + + /** A node in the binding graph that is either a {@link Binding} or a {@link MissingBinding}. */ + public interface MaybeBinding extends Node { + + /** The component that owns the binding, or in which the binding is missing. */ + @Override + ComponentPath componentPath(); + + /** The key of the binding, or for which there is no binding. */ + Key key(); + + /** The binding, or empty if missing. */ + Optional<Binding> binding(); + } + + /** A node in the binding graph that represents a missing binding for a key in a component. */ + public abstract static class MissingBinding implements MaybeBinding { + /** The component in which the binding is missing. */ + @Override + public abstract ComponentPath componentPath(); + + /** The key for which there is no binding. */ + @Override + public abstract Key key(); + + /** @deprecated This always returns {@code Optional.empty()}. */ + @Override + @Deprecated + public Optional<Binding> binding() { + return Optional.empty(); + } + + @Override + public String toString() { + return String.format("missing binding for %s in %s", key(), componentPath()); + } + } + + /** + * A <b>component node</b> in the graph. Every entry point {@linkplain DependencyEdge dependency + * edge}'s source node is a component node for the component containing the entry point. + */ + public interface ComponentNode extends Node { + + /** The component represented by this node. */ + @Override + ComponentPath componentPath(); + + /** + * Returns {@code true} if the component is a {@code @Subcomponent} or + * {@code @ProductionSubcomponent}. + */ + boolean isSubcomponent(); + + /** + * Returns {@code true} if the component is a real component, or {@code false} if it is a + * fictional component based on a module. + */ + boolean isRealComponent(); + + /** The entry points on this component. */ + ImmutableSet<DependencyRequest> entryPoints(); + + /** The scopes declared on this component. */ + ImmutableSet<Scope> scopes(); + } +} diff --git a/java/dagger/spi/model/BindingGraphPlugin.java b/java/dagger/spi/model/BindingGraphPlugin.java new file mode 100644 index 000000000..41e421975 --- /dev/null +++ b/java/dagger/spi/model/BindingGraphPlugin.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.Filer; +import javax.annotation.processing.Messager; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +// TODO(bcorso): Move this into dagger/spi? +/** + * A pluggable visitor for {@link BindingGraph}. + * + * <p>Note: This is still experimental and will change. + */ +public interface BindingGraphPlugin { + /** + * Called once for each valid root binding graph encountered by the Dagger processor. May report + * diagnostics using {@code diagnosticReporter}. + */ + void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter); + + /** + * Initializes this plugin with a {@link Filer} that it can use to write Java or other files based + * on the binding graph. This will be called once per instance of this plugin, before any graph is + * {@linkplain #visitGraph(BindingGraph, DiagnosticReporter) visited}. + * + * @see javax.annotation.processing.ProcessingEnvironment#getFiler() + */ + default void initFiler(Filer filer) {} + + /** + * Initializes this plugin with a {@link Types} instance. This will be called once per instance of + * this plugin, before any graph is {@linkplain #visitGraph(BindingGraph, DiagnosticReporter) + * visited}. + * + * @see javax.annotation.processing.ProcessingEnvironment#getTypeUtils() + */ + default void initTypes(Types types) {} + + /** + * Initializes this plugin with a {@link Elements} instance. This will be called once per instance + * of this plugin, before any graph is {@linkplain #visitGraph(BindingGraph, DiagnosticReporter) + * visited}. + * + * @see javax.annotation.processing.ProcessingEnvironment#getElementUtils() + */ + default void initElements(Elements elements) {} + + /** + * Initializes this plugin with a filtered view of the options passed on the {@code javac} + * command-line for all keys from {@link #supportedOptions()}. This will be called once per + * instance of this plugin, before any graph is {@linkplain #visitGraph(BindingGraph, + * DiagnosticReporter) visited}. + * + * @see javax.annotation.processing.ProcessingEnvironment#getOptions() + */ + default void initOptions(Map<String, String> options) {} + + /** + * Returns the annotation-processing options that this plugin uses to configure behavior. + * + * @see javax.annotation.processing.Processor#getSupportedOptions() + */ + default Set<String> supportedOptions() { + return Collections.emptySet(); + } + + /** + * A distinguishing name of the plugin that will be used in diagnostics printed to the {@link + * Messager}. By default, the {@linkplain Class#getCanonicalName() fully qualified name} of the + * plugin is used. + */ + default String pluginName() { + return getClass().getCanonicalName(); + } +} diff --git a/java/dagger/spi/model/BindingKind.java b/java/dagger/spi/model/BindingKind.java new file mode 100644 index 000000000..b854b5300 --- /dev/null +++ b/java/dagger/spi/model/BindingKind.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 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.spi.model; + +/** Represents the different kinds of {@link Binding}s that can exist in a binding graph. */ +public enum BindingKind { + /** A binding for an {@link javax.inject.Inject}-annotated constructor. */ + INJECTION, + + /** A binding for a {@link dagger.Provides}-annotated method. */ + PROVISION, + + /** + * A binding for an {@link javax.inject.Inject}-annotated constructor that contains at least one + * {@link dagger.assisted.Assisted}-annotated parameter. + */ + ASSISTED_INJECTION, + + /** A binding for an {@link dagger.assisted.AssistedFactory}-annotated type. */ + ASSISTED_FACTORY, + + /** + * An implicit binding for a {@link dagger.Component}- or {@link + * dagger.producers.ProductionComponent}-annotated type. + */ + COMPONENT, + + /** + * A binding for a provision method on a component's {@linkplain dagger.Component#dependencies() + * dependency}. + */ + COMPONENT_PROVISION, + + /** + * A binding for an instance of a component's {@linkplain dagger.Component#dependencies() + * dependency}. + */ + COMPONENT_DEPENDENCY, + + /** A binding for a {@link dagger.MembersInjector} of a type. */ + MEMBERS_INJECTOR, + + /** + * A binding for a subcomponent creator (a {@linkplain dagger.Subcomponent.Builder builder} or + * {@linkplain dagger.Subcomponent.Factory factory}). + * + * @since 2.22 (previously named {@code SUBCOMPONENT_BUILDER}) + */ + SUBCOMPONENT_CREATOR, + + /** A binding for a {@link dagger.BindsInstance}-annotated builder method. */ + BOUND_INSTANCE, + + /** A binding for a {@link dagger.producers.Produces}-annotated method. */ + PRODUCTION, + + /** + * A binding for a production method on a production component's {@linkplain + * dagger.producers.ProductionComponent#dependencies()} dependency} that returns a {@link + * com.google.common.util.concurrent.ListenableFuture} or {@link + * com.google.common.util.concurrent.FluentFuture}. Methods on production component dependencies + * that don't return a future are considered {@linkplain #COMPONENT_PROVISION component provision + * bindings}. + */ + COMPONENT_PRODUCTION, + + /** + * A synthetic binding for a multibound set that depends on individual multibinding {@link + * #PROVISION} or {@link #PRODUCTION} contributions. + */ + MULTIBOUND_SET, + + /** + * A synthetic binding for a multibound map that depends on the individual multibinding {@link + * #PROVISION} or {@link #PRODUCTION} contributions. + */ + MULTIBOUND_MAP, + + /** + * A synthetic binding for {@code Optional} of a type or a {@link javax.inject.Provider}, {@link + * dagger.Lazy}, or {@code Provider} of {@code Lazy} of a type. Generated by a {@link + * dagger.BindsOptionalOf} declaration. + */ + OPTIONAL, + + /** + * A binding for {@link dagger.Binds}-annotated method that that delegates from requests for one + * key to another. + */ + // TODO(dpb,ronshapiro): This name is confusing and could use work. Not all usages of @Binds + // bindings are simple delegations and we should have a name that better reflects that + DELEGATE, + + /** A binding for a members injection method on a component. */ + MEMBERS_INJECTION, + ; + + /** + * Returns {@code true} if this is a kind of multibinding (not a contribution to a multibinding, + * but the multibinding itself). + */ + public boolean isMultibinding() { + switch (this) { + case MULTIBOUND_MAP: + case MULTIBOUND_SET: + return true; + + default: + return false; + } + } +} diff --git a/java/dagger/spi/model/CompilerEnvironment.java b/java/dagger/spi/model/CompilerEnvironment.java new file mode 100644 index 000000000..28553e4c3 --- /dev/null +++ b/java/dagger/spi/model/CompilerEnvironment.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 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.spi.model; + +/** Types for the compiler in use for annotation processing. */ +public enum CompilerEnvironment { + JAVA, + KSP +} diff --git a/java/dagger/spi/model/ComponentPath.java b/java/dagger/spi/model/ComponentPath.java new file mode 100644 index 000000000..74195e430 --- /dev/null +++ b/java/dagger/spi/model/ComponentPath.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.getLast; +import static java.util.stream.Collectors.joining; + +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; +import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.ClassName; + +/** A path containing a component and all of its ancestor components. */ +@AutoValue +public abstract class ComponentPath { + /** Returns a new {@link ComponentPath} from {@code components}. */ + public static ComponentPath create(Iterable<DaggerTypeElement> components) { + return new AutoValue_ComponentPath(ImmutableList.copyOf(components)); + } + + /** + * Returns the component types, starting from the {@linkplain #rootComponent() root + * component} and ending with the {@linkplain #currentComponent() current component}. + */ + public abstract ImmutableList<DaggerTypeElement> components(); + + /** + * Returns the root {@link dagger.Component}- or {@link + * dagger.producers.ProductionComponent}-annotated type + */ + public final DaggerTypeElement rootComponent() { + return components().get(0); + } + + /** Returns the component at the end of the path. */ + @Memoized + public DaggerTypeElement currentComponent() { + return getLast(components()); + } + + /** + * Returns the parent of the {@linkplain #currentComponent()} current component}. + * + * @throws IllegalStateException if the current graph is the {@linkplain #atRoot() root component} + */ + public final DaggerTypeElement parentComponent() { + checkState(!atRoot()); + return components().reverse().get(1); + } + + /** + * Returns this path's parent path. + * + * @throws IllegalStateException if the current graph is the {@linkplain #atRoot() root component} + */ + // TODO(ronshapiro): consider memoizing this + public final ComponentPath parent() { + checkState(!atRoot()); + return create(components().subList(0, components().size() - 1)); + } + + /** Returns the path from the root component to the {@code child} of the current component. */ + public final ComponentPath childPath(DaggerTypeElement child) { + return create( + ImmutableList.<DaggerTypeElement>builder().addAll(components()).add(child).build()); + } + + /** + * Returns {@code true} if the {@linkplain #currentComponent()} current component} is the + * {@linkplain #rootComponent()} root component}. + */ + public final boolean atRoot() { + return components().size() == 1; + } + + @Override + public final String toString() { + return components().stream() + .map(DaggerTypeElement::className) + .map(ClassName::canonicalName) + .collect(joining(" → ")); + } + + @Memoized + @Override + public abstract int hashCode(); + + @Override + public abstract boolean equals(Object obj); +} diff --git a/java/dagger/spi/model/DaggerAnnotation.java b/java/dagger/spi/model/DaggerAnnotation.java new file mode 100644 index 000000000..42b12d525 --- /dev/null +++ b/java/dagger/spi/model/DaggerAnnotation.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; + +import androidx.room.compiler.processing.XAnnotation; +import com.google.auto.common.AnnotationMirrors; +import com.google.auto.value.AutoValue; +import com.google.common.base.Equivalence; +import com.google.common.base.Preconditions; +import com.squareup.javapoet.ClassName; +import javax.lang.model.element.AnnotationMirror; + +/** Wrapper type for an annotation. */ +@AutoValue +public abstract class DaggerAnnotation { + private XAnnotation annotation; + + public static DaggerAnnotation from(XAnnotation annotation) { + Preconditions.checkNotNull(annotation); + DaggerAnnotation daggerAnnotation = + new AutoValue_DaggerAnnotation(AnnotationMirrors.equivalence().wrap(toJavac(annotation))); + daggerAnnotation.annotation = annotation; + return daggerAnnotation; + } + + abstract Equivalence.Wrapper<AnnotationMirror> annotationMirror(); + + public DaggerTypeElement annotationTypeElement() { + return DaggerTypeElement.from(annotation.getType().getTypeElement()); + } + + public ClassName className() { + return annotationTypeElement().className(); + } + + public XAnnotation xprocessing() { + return annotation; + } + + public AnnotationMirror java() { + return toJavac(annotation); + } + + // TODO(b/204390647): We'll need to update to auto-common to 1.2 before using AnnotationMirrors. + @SuppressWarnings("AnnotationMirrorToString") + @Override + public final String toString() { + return java().toString(); + } +} diff --git a/java/dagger/spi/model/DaggerElement.java b/java/dagger/spi/model/DaggerElement.java new file mode 100644 index 000000000..aa1b4a5eb --- /dev/null +++ b/java/dagger/spi/model/DaggerElement.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; + +import androidx.room.compiler.processing.XElement; +import com.google.auto.value.AutoValue; +import javax.lang.model.element.Element; + +/** Wrapper type for an element. */ +@AutoValue +public abstract class DaggerElement { + public static DaggerElement from(XElement element) { + return new AutoValue_DaggerElement(element); + } + + public abstract XElement xprocessing(); + + public Element java() { + return toJavac(xprocessing()); + } + + @Override + public final String toString() { + return xprocessing().toString(); + } +} diff --git a/java/dagger/spi/model/DaggerExecutableElement.java b/java/dagger/spi/model/DaggerExecutableElement.java new file mode 100644 index 000000000..e15ca375b --- /dev/null +++ b/java/dagger/spi/model/DaggerExecutableElement.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; +import static com.google.common.base.Preconditions.checkNotNull; + +import androidx.room.compiler.processing.XExecutableElement; +import com.google.auto.value.AutoValue; +import javax.lang.model.element.ExecutableElement; + +/** Wrapper type for an executable element. */ +@AutoValue +public abstract class DaggerExecutableElement { + public static DaggerExecutableElement from(XExecutableElement executableElement) { + return new AutoValue_DaggerExecutableElement(checkNotNull(executableElement)); + } + + public abstract XExecutableElement xprocessing(); + + public ExecutableElement java() { + return toJavac(xprocessing()); + } + + @Override + public final String toString() { + return xprocessing().toString(); + } +} diff --git a/java/dagger/spi/model/DaggerType.java b/java/dagger/spi/model/DaggerType.java new file mode 100644 index 000000000..34cc6dc8a --- /dev/null +++ b/java/dagger/spi/model/DaggerType.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; + +import androidx.room.compiler.processing.XType; +import com.google.auto.common.MoreTypes; +import com.google.auto.value.AutoValue; +import com.google.common.base.Equivalence; +import com.google.common.base.Preconditions; +import javax.lang.model.type.TypeMirror; + +/** Wrapper type for a type. */ +@AutoValue +public abstract class DaggerType { + private XType type; + + public static DaggerType from(XType type) { + Preconditions.checkNotNull(type); + DaggerType daggerType = new AutoValue_DaggerType(MoreTypes.equivalence().wrap(toJavac(type))); + daggerType.type = type; + return daggerType; + } + + abstract Equivalence.Wrapper<TypeMirror> typeMirror(); + + public XType xprocessing() { + return type; + } + + public TypeMirror java() { + return toJavac(type); + } + + @Override + public final String toString() { + return type.toString(); + } +} diff --git a/java/dagger/spi/model/DaggerTypeElement.java b/java/dagger/spi/model/DaggerTypeElement.java new file mode 100644 index 000000000..aa4750794 --- /dev/null +++ b/java/dagger/spi/model/DaggerTypeElement.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import static androidx.room.compiler.processing.compat.XConverters.toJavac; + +import androidx.room.compiler.processing.XTypeElement; +import com.google.auto.value.AutoValue; +import com.squareup.javapoet.ClassName; +import javax.lang.model.element.TypeElement; + +/** Wrapper type for a type element. */ +@AutoValue +public abstract class DaggerTypeElement { + public static DaggerTypeElement from(XTypeElement typeElement) { + return new AutoValue_DaggerTypeElement(typeElement); + } + + public abstract XTypeElement xprocessing(); + + public TypeElement java() { + return toJavac(xprocessing()); + } + + public ClassName className() { + return xprocessing().getClassName(); + } + + @Override + public final String toString() { + return xprocessing().toString(); + } +} diff --git a/java/dagger/spi/model/DependencyRequest.java b/java/dagger/spi/model/DependencyRequest.java new file mode 100644 index 000000000..64c0f2834 --- /dev/null +++ b/java/dagger/spi/model/DependencyRequest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.CheckReturnValue; +import dagger.Provides; +import java.util.Optional; +import javax.inject.Inject; + +/** + * Represents a request for a {@link Key} at an injection point. For example, parameters to {@link + * Inject} constructors, {@link Provides} methods, and component methods are all dependency + * requests. + * + * <p id="synthetic">A dependency request is considered to be <em>synthetic</em> if it does not have + * an {@link DaggerElement} in code that requests the key directly. For example, an {@link + * java.util.concurrent.Executor} is required for all {@code @Produces} methods to run + * asynchronously even though it is not directly specified as a parameter to the binding method. + */ +@AutoValue +public abstract class DependencyRequest { + /** The kind of this request. */ + public abstract RequestKind kind(); + + /** The key of this request. */ + public abstract Key key(); + + /** + * The element that declares this dependency request. Absent for <a href="#synthetic">synthetic + * </a> requests. + */ + public abstract Optional<DaggerElement> requestElement(); + + /** + * Returns {@code true} if this request allows null objects. A request is nullable if it is + * has an annotation with "Nullable" as its simple name. + */ + public abstract boolean isNullable(); + + /** Returns a new builder of dependency requests. */ + public static DependencyRequest.Builder builder() { + return new AutoValue_DependencyRequest.Builder().isNullable(false); + } + + /** A builder of {@link DependencyRequest}s. */ + @CanIgnoreReturnValue + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder kind(RequestKind kind); + + public abstract Builder key(Key key); + + public abstract Builder requestElement(DaggerElement element); + + public abstract Builder isNullable(boolean isNullable); + + @CheckReturnValue + public abstract DependencyRequest build(); + } +} diff --git a/java/dagger/spi/model/DiagnosticReporter.java b/java/dagger/spi/model/DiagnosticReporter.java new file mode 100644 index 000000000..a3d222965 --- /dev/null +++ b/java/dagger/spi/model/DiagnosticReporter.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import com.google.errorprone.annotations.FormatMethod; +import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge; +import dagger.spi.model.BindingGraph.ComponentNode; +import dagger.spi.model.BindingGraph.DependencyEdge; +import dagger.spi.model.BindingGraph.MaybeBinding; +import javax.tools.Diagnostic; + +// TODO(bcorso): Move this into dagger/spi? +/** + * An object that {@link BindingGraphPlugin}s can use to report diagnostics while visiting a {@link + * BindingGraph}. + * + * <p>Note: This API is still experimental and will change. + */ +public interface DiagnosticReporter { + /** + * Reports a diagnostic for a component. For non-root components, includes information about the + * path from the root component. + */ + void reportComponent(Diagnostic.Kind diagnosticKind, ComponentNode componentNode, String message); + + /** + * Reports a diagnostic for a component. For non-root components, includes information about the + * path from the root component. + */ + @FormatMethod + void reportComponent( + Diagnostic.Kind diagnosticKind, + ComponentNode componentNode, + String messageFormat, + Object firstArg, + Object... moreArgs); + + /** + * Reports a diagnostic for a binding or missing binding. Includes information about how the + * binding is reachable from entry points. + */ + void reportBinding(Diagnostic.Kind diagnosticKind, MaybeBinding binding, String message); + + /** + * Reports a diagnostic for a binding or missing binding. Includes information about how the + * binding is reachable from entry points. + */ + @FormatMethod + void reportBinding( + Diagnostic.Kind diagnosticKind, + MaybeBinding binding, + String messageFormat, + Object firstArg, + Object... moreArgs); + + /** + * Reports a diagnostic for a dependency. Includes information about how the dependency is + * reachable from entry points. + */ + void reportDependency( + Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String message); + + /** + * Reports a diagnostic for a dependency. Includes information about how the dependency is + * reachable from entry points. + */ + @FormatMethod + void reportDependency( + Diagnostic.Kind diagnosticKind, + DependencyEdge dependencyEdge, + String messageFormat, + Object firstArg, + Object... moreArgs); + + /** Reports a diagnostic for a subcomponent factory method. */ + void reportSubcomponentFactoryMethod( + Diagnostic.Kind diagnosticKind, + ChildFactoryMethodEdge childFactoryMethodEdge, + String message); + + /** Reports a diagnostic for a subcomponent factory method. */ + @FormatMethod + void reportSubcomponentFactoryMethod( + Diagnostic.Kind diagnosticKind, + ChildFactoryMethodEdge childFactoryMethodEdge, + String messageFormat, + Object firstArg, + Object... moreArgs); +} diff --git a/java/dagger/spi/model/Key.java b/java/dagger/spi/model/Key.java new file mode 100644 index 000000000..ad9b6c7de --- /dev/null +++ b/java/dagger/spi/model/Key.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; +import com.google.common.base.Joiner; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.CheckReturnValue; +import java.util.Objects; +import java.util.Optional; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + +/** + * A {@linkplain DaggerType type} and an optional {@linkplain javax.inject.Qualifier qualifier} that + * is the lookup key for a binding. + */ +@AutoValue +public abstract class Key { + /** + * A {@link javax.inject.Qualifier} annotation that provides a unique namespace prefix for the + * type of this key. + */ + public abstract Optional<DaggerAnnotation> qualifier(); + + /** The type represented by this key. */ + public abstract DaggerType type(); + + /** + * Distinguishes keys for multibinding contributions that share a {@link #type()} and {@link + * #qualifier()}. + * + * <p>Each multibound map and set has a synthetic multibinding that depends on the specific + * contributions to that map or set using keys that identify those multibinding contributions. + * + * <p>Absent except for multibinding contributions. + */ + public abstract Optional<MultibindingContributionIdentifier> multibindingContributionIdentifier(); + + /** Returns a {@link Builder} that inherits the properties of this key. */ + public abstract Builder toBuilder(); + + // The main hashCode/equality bottleneck is in MoreTypes.equivalence(). It's possible that we can + // avoid this by tuning that method. Perhaps we can also avoid the issue entirely by interning all + // Keys + @Memoized + @Override + public abstract int hashCode(); + + @Override + public abstract boolean equals(Object o); + + @Override + public final String toString() { + return Joiner.on(' ') + .skipNulls() + .join( + qualifier().map(MoreAnnotationMirrors::toStableString).orElse(null), + type(), + multibindingContributionIdentifier().orElse(null)); + } + + /** Returns a builder for {@link Key}s. */ + public static Builder builder(DaggerType type) { + return new AutoValue_Key.Builder().type(type); + } + + /** A builder for {@link Key}s. */ + @CanIgnoreReturnValue + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder type(DaggerType type); + + public abstract Builder qualifier(Optional<DaggerAnnotation> qualifier); + + public abstract Builder qualifier(DaggerAnnotation qualifier); + + public abstract Builder multibindingContributionIdentifier( + Optional<MultibindingContributionIdentifier> identifier); + + public abstract Builder multibindingContributionIdentifier( + MultibindingContributionIdentifier identifier); + + @CheckReturnValue + public abstract Key build(); + } + + /** + * An object that identifies a multibinding contribution method and the module class that + * contributes it to the graph. + * + * @see #multibindingContributionIdentifier() + */ + public static final class MultibindingContributionIdentifier { + private final String module; + private final String bindingElement; + + /** + * @deprecated This is only meant to be called from code in {@code dagger.internal.codegen}. + * It is not part of a specified API and may change at any point. + */ + @Deprecated + public MultibindingContributionIdentifier( + // TODO(ronshapiro): reverse the order of these parameters + ExecutableElement bindingMethod, TypeElement contributingModule) { + this( + bindingMethod.getSimpleName().toString(), + contributingModule.getQualifiedName().toString()); + } + + // TODO(ronshapiro,dpb): create KeyProxies so that these constructors don't need to be public. + @Deprecated + public MultibindingContributionIdentifier(String bindingElement, String module) { + this.module = module; + this.bindingElement = bindingElement; + } + + /** + * @deprecated This is only meant to be called from code in {@code dagger.internal.codegen}. + * It is not part of a specified API and may change at any point. + */ + @Deprecated + public String module() { + return module; + } + + /** + * @deprecated This is only meant to be called from code in {@code dagger.internal.codegen}. + * It is not part of a specified API and may change at any point. + */ + @Deprecated + public String bindingElement() { + return bindingElement; + } + + /** + * {@inheritDoc} + * + * <p>The returned string is human-readable and distinguishes the keys in the same way as the + * whole object. + */ + @Override + public String toString() { + return String.format("%s#%s", module, bindingElement); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof MultibindingContributionIdentifier) { + MultibindingContributionIdentifier other = (MultibindingContributionIdentifier) obj; + return module.equals(other.module) && bindingElement.equals(other.bindingElement); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(module, bindingElement); + } + } +} diff --git a/java/dagger/spi/model/MoreAnnotationMirrors.java b/java/dagger/spi/model/MoreAnnotationMirrors.java new file mode 100644 index 000000000..44c0363c9 --- /dev/null +++ b/java/dagger/spi/model/MoreAnnotationMirrors.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import static com.google.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults; +import static java.util.stream.Collectors.joining; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import com.squareup.javapoet.CodeBlock; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.SimpleAnnotationValueVisitor8; + +/** Utility class for qualifier transformations */ +final class MoreAnnotationMirrors { + /** + * Returns a String rendering of an {@link AnnotationMirror} that includes attributes in the order + * defined in the annotation type. + */ + public static String toStableString(DaggerAnnotation qualifier) { + return stableAnnotationMirrorToString(qualifier.java()); + } + + /** + * Returns a String rendering of an {@link AnnotationMirror} that includes attributes in the order + * defined in the annotation type. This will produce the same output for {@linkplain + * com.google.auto.common.AnnotationMirrors#equivalence() equal} {@link AnnotationMirror}s even if + * default values are omitted or their attributes were written in different orders, e.g. + * {@code @A(b = "b", c = "c")} and {@code @A(c = "c", b = "b", attributeWithDefaultValue = + * "default value")}. + */ + // TODO(ronshapiro): move this to auto-common + private static String stableAnnotationMirrorToString(AnnotationMirror qualifier) { + StringBuilder builder = new StringBuilder("@").append(qualifier.getAnnotationType()); + ImmutableMap<ExecutableElement, AnnotationValue> elementValues = + getAnnotationValuesWithDefaults(qualifier); + if (!elementValues.isEmpty()) { + ImmutableMap.Builder<String, String> namedValuesBuilder = ImmutableMap.builder(); + elementValues.forEach( + (key, value) -> + namedValuesBuilder.put( + key.getSimpleName().toString(), stableAnnotationValueToString(value))); + ImmutableMap<String, String> namedValues = namedValuesBuilder.build(); + builder.append('('); + if (namedValues.size() == 1 && namedValues.containsKey("value")) { + // Omit "value =" + builder.append(namedValues.get("value")); + } else { + builder.append(Joiner.on(", ").withKeyValueSeparator("=").join(namedValues)); + } + builder.append(')'); + } + return builder.toString(); + } + + private static String stableAnnotationValueToString(AnnotationValue annotationValue) { + return annotationValue.accept( + new SimpleAnnotationValueVisitor8<String, Void>() { + @Override + protected String defaultAction(Object value, Void ignore) { + return value.toString(); + } + + @Override + public String visitString(String value, Void ignore) { + return CodeBlock.of("$S", value).toString(); + } + + @Override + public String visitAnnotation(AnnotationMirror value, Void ignore) { + return stableAnnotationMirrorToString(value); + } + + @Override + public String visitArray(List<? extends AnnotationValue> value, Void ignore) { + return value.stream() + .map(MoreAnnotationMirrors::stableAnnotationValueToString) + .collect(joining(", ", "{", "}")); + } + }, + null); + } + + private MoreAnnotationMirrors() {} +} diff --git a/java/dagger/spi/model/RequestKind.java b/java/dagger/spi/model/RequestKind.java new file mode 100644 index 000000000..62f46cd65 --- /dev/null +++ b/java/dagger/spi/model/RequestKind.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import static com.google.common.base.CaseFormat.UPPER_CAMEL; +import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; + +import dagger.Lazy; +import dagger.producers.Produced; +import dagger.producers.Producer; +import javax.inject.Provider; + +/** + * Represents the different kinds of {@link javax.lang.model.type.TypeMirror types} that may be + * requested as dependencies for the same key. For example, {@code String}, {@code + * Provider<String>}, and {@code Lazy<String>} can all be requested if a key exists for {@code + * String}; they have the {@link #INSTANCE}, {@link #PROVIDER}, and {@link #LAZY} request kinds, + * respectively. + */ +public enum RequestKind { + /** A default request for an instance. E.g.: {@code FooType} */ + INSTANCE, + + /** A request for a {@link Provider}. E.g.: {@code Provider<FooType>} */ + PROVIDER, + + /** A request for a {@link Lazy}. E.g.: {@code Lazy<FooType>} */ + LAZY, + + /** A request for a {@link Provider} of a {@link Lazy}. E.g.: {@code Provider<Lazy<FooType>>} */ + PROVIDER_OF_LAZY, + + /** + * A request for a members injection. E.g. {@code void injectMembers(FooType);}. Can only be + * requested by component interfaces. + */ + MEMBERS_INJECTION, + + /** A request for a {@link Producer}. E.g.: {@code Producer<FooType>} */ + PRODUCER, + + /** A request for a {@link Produced}. E.g.: {@code Produced<FooType>} */ + PRODUCED, + + /** + * A request for a {@link com.google.common.util.concurrent.ListenableFuture}. E.g.: {@code + * ListenableFuture<FooType>}. These can only be requested by component interfaces. + */ + FUTURE, + ; + + /** Returns a string that represents requests of this kind for a key. */ + public String format(Key key) { + switch (this) { + case INSTANCE: + return key.toString(); + + case PROVIDER_OF_LAZY: + return String.format("Provider<Lazy<%s>>", key); + + case MEMBERS_INJECTION: + return String.format("injectMembers(%s)", key); + + case FUTURE: + return String.format("ListenableFuture<%s>", key); + + default: + return String.format("%s<%s>", UPPER_UNDERSCORE.to(UPPER_CAMEL, name()), key); + } + } +} diff --git a/java/dagger/spi/model/Scope.java b/java/dagger/spi/model/Scope.java new file mode 100644 index 000000000..c3a980365 --- /dev/null +++ b/java/dagger/spi/model/Scope.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2021 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.spi.model; + +import static com.google.auto.common.MoreElements.isAnnotationPresent; +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.auto.value.AutoValue; +import com.squareup.javapoet.ClassName; + +/** A representation of a {@link javax.inject.Scope}. */ +@AutoValue +public abstract class Scope { + /** + * Creates a {@link Scope} object from the {@link javax.inject.Scope}-annotated annotation type. + */ + public static Scope scope(DaggerAnnotation scopeAnnotation) { + checkArgument(isScope(scopeAnnotation)); + return new AutoValue_Scope(scopeAnnotation); + } + + /** + * Returns {@code true} if {@link #scopeAnnotation()} is a {@link javax.inject.Scope} annotation. + */ + public static boolean isScope(DaggerAnnotation scopeAnnotation) { + return isScope(scopeAnnotation.annotationTypeElement()); + } + + /** + * Returns {@code true} if {@code scopeAnnotationType} is a {@link javax.inject.Scope} annotation. + */ + public static boolean isScope(DaggerTypeElement scopeAnnotationType) { + return isAnnotationPresent(scopeAnnotationType.java(), SCOPE.canonicalName()) + || isAnnotationPresent(scopeAnnotationType.java(), SCOPE_JAVAX.canonicalName()); + } + + private static final ClassName PRODUCTION_SCOPE = + ClassName.get("dagger.producers", "ProductionScope"); + private static final ClassName SINGLETON = ClassName.get("jakarta.inject", "Singleton"); + private static final ClassName SINGLETON_JAVAX = ClassName.get("javax.inject", "Singleton"); + private static final ClassName REUSABLE = ClassName.get("dagger", "Reusable"); + private static final ClassName SCOPE = ClassName.get("jakarta.inject", "Scope"); + private static final ClassName SCOPE_JAVAX = ClassName.get("javax.inject", "Scope"); + + + /** The {@link DaggerAnnotation} that represents the scope annotation. */ + public abstract DaggerAnnotation scopeAnnotation(); + + public final ClassName className() { + return scopeAnnotation().className(); + } + + /** Returns {@code true} if this scope is the {@link javax.inject.Singleton @Singleton} scope. */ + public final boolean isSingleton() { + return isScope(SINGLETON) || isScope(SINGLETON_JAVAX); + } + + /** Returns {@code true} if this scope is the {@link dagger.Reusable @Reusable} scope. */ + public final boolean isReusable() { + return isScope(REUSABLE); + } + + /** + * Returns {@code true} if this scope is the {@link + * dagger.producers.ProductionScope @ProductionScope} scope. + */ + public final boolean isProductionScope() { + return isScope(PRODUCTION_SCOPE); + } + + private boolean isScope(ClassName annotation) { + return scopeAnnotation().className().equals(annotation); + } + + /** Returns a debug representation of the scope. */ + @Override + public final String toString() { + return scopeAnnotation().toString(); + } +} diff --git a/java/dagger/spi/model/package-info.java b/java/dagger/spi/model/package-info.java new file mode 100644 index 000000000..133a54219 --- /dev/null +++ b/java/dagger/spi/model/package-info.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 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. + */ + +/** + * This package contains the APIs that are core to Dagger's internal model of bindings and the + * binding graph. The types are shared with the Dagger processor and are exposed to clients of the + * Dagger SPI. + * + * <p>Unless otherwise specified, the types/interfaces are only intended to be implemented in this + * package (i.e. via {@code @AutoValue}) or by Dagger's processor. This applies to test code as + * well, so if you need a fake, please file a feature request instead of implementing it yourself. + */ +@CheckReturnValue +@Beta +package dagger.spi.model; + +import com.google.errorprone.annotations.CheckReturnValue; +import dagger.internal.Beta; diff --git a/java/dagger/spi/model/testing/BUILD b/java/dagger/spi/model/testing/BUILD new file mode 100644 index 000000000..939e41171 --- /dev/null +++ b/java/dagger/spi/model/testing/BUILD @@ -0,0 +1,39 @@ +# Copyright (C) 2021 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. + +# Description: +# Test utilities for the Dagger model + +load("@rules_java//java:defs.bzl", "java_library") +load( + "//:build_defs.bzl", + "DOCLINT_HTML_AND_SYNTAX", + "DOCLINT_REFERENCES", +) + +package(default_visibility = ["//:src"]) + +java_library( + name = "testing", + testonly = 1, + srcs = glob(["*.java"]), + javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES, + deps = [ + "//java/dagger/internal/codegen/extension", + "//java/dagger/spi", + "//third_party/java/checker_framework_annotations", + "//third_party/java/guava/collect", + "//third_party/java/truth", + ], +) diff --git a/java/dagger/spi/model/testing/BindingGraphSubject.java b/java/dagger/spi/model/testing/BindingGraphSubject.java new file mode 100644 index 000000000..32bf15237 --- /dev/null +++ b/java/dagger/spi/model/testing/BindingGraphSubject.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2021 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.spi.model.testing; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.truth.Truth.assertAbout; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import com.google.common.collect.ImmutableSet; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Subject; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +/** A Truth subject for making assertions on a {@link BindingGraph}. */ +public final class BindingGraphSubject extends Subject { + + /** Starts a fluent assertion about a {@link BindingGraph}. */ + public static BindingGraphSubject assertThat(BindingGraph bindingGraph) { + return assertAbout(BindingGraphSubject::new).that(bindingGraph); + } + + private final BindingGraph actual; + + private BindingGraphSubject(FailureMetadata metadata, @NullableDecl BindingGraph actual) { + super(metadata, actual); + this.actual = actual; + } + + /** + * Asserts that the graph has at least one binding with an unqualified key. + * + * @param type the canonical name of the type, as returned by {@link TypeMirror#toString()} + */ + public void hasBindingWithKey(String type) { + bindingWithKey(type); + } + + /** + * Asserts that the graph has at least one binding with a qualified key. + * + * @param qualifier the canonical string form of the qualifier, as returned by {@link + * javax.lang.model.element.AnnotationMirror AnnotationMirror.toString()} + * @param type the canonical name of the type, as returned by {@link TypeMirror#toString()} + */ + public void hasBindingWithKey(String qualifier, String type) { + bindingWithKey(qualifier, type); + } + + /** + * Returns a subject for testing the binding for an unqualified key. + * + * @param type the canonical name of the type, as returned by {@link TypeMirror#toString()} + */ + public BindingSubject bindingWithKey(String type) { + return bindingWithKeyString(keyString(type)); + } + + /** + * Returns a subject for testing the binding for a qualified key. + * + * @param qualifier the canonical string form of the qualifier, as returned by {@link + * javax.lang.model.element.AnnotationMirror AnnotationMirror.toString()} + * @param type the canonical name of the type, as returned by {@link TypeMirror#toString()} + */ + public BindingSubject bindingWithKey(String qualifier, String type) { + return bindingWithKeyString(keyString(qualifier, type)); + } + + private BindingSubject bindingWithKeyString(String keyString) { + ImmutableSet<Binding> bindings = getBindingNodes(keyString); + // TODO(dpb): Handle multiple bindings for the same key. + check("bindingsWithKey(%s)", keyString).that(bindings).hasSize(1); + return check("bindingWithKey(%s)", keyString) + .about(BindingSubject::new) + .that(getOnlyElement(bindings)); + } + + private ImmutableSet<Binding> getBindingNodes(String keyString) { + return actual.bindings().stream() + .filter(binding -> binding.key().toString().equals(keyString)) + .collect(toImmutableSet()); + } + + private static String keyString(String type) { + return type; + } + + private static String keyString(String qualifier, String type) { + return String.format("%s %s", qualifier, type); + } + + /** A Truth subject for a {@link Binding}. */ + public final class BindingSubject extends Subject { + + private final Binding actual; + + BindingSubject(FailureMetadata metadata, @NullableDecl Binding actual) { + super(metadata, actual); + this.actual = actual; + } + + /** + * Asserts that the binding depends on a binding with an unqualified key. + * + * @param type the canonical name of the type, as returned by {@link TypeMirror#toString()} + */ + public void dependsOnBindingWithKey(String type) { + dependsOnBindingWithKeyString(keyString(type)); + } + + /** + * Asserts that the binding depends on a binding with a qualified key. + * + * @param qualifier the canonical string form of the qualifier, as returned by {@link + * javax.lang.model.element.AnnotationMirror AnnotationMirror.toString()} + * @param type the canonical name of the type, as returned by {@link TypeMirror#toString()} + */ + public void dependsOnBindingWithKey(String qualifier, String type) { + dependsOnBindingWithKeyString(keyString(qualifier, type)); + } + + private void dependsOnBindingWithKeyString(String keyString) { + if (actualBindingGraph().requestedBindings(actual).stream() + .noneMatch(binding -> binding.key().toString().equals(keyString))) { + failWithActual("expected to depend on binding with key", keyString); + } + } + + private BindingGraph actualBindingGraph() { + return BindingGraphSubject.this.actual; + } + } +} diff --git a/java/dagger/testing/compile/BUILD b/java/dagger/testing/compile/BUILD index 20e6a3d20..dbad09f02 100644 --- a/java/dagger/testing/compile/BUILD +++ b/java/dagger/testing/compile/BUILD @@ -24,9 +24,11 @@ java_library( testonly = 1, srcs = ["CompilerTests.java"], deps = [ - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:io", - "@google_bazel_common//third_party/java/compile_testing", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/io", + "//third_party/java/compile_testing", + "//java/dagger/internal/codegen:processor", + "@maven//:com_github_tschuchortdev_kotlin_compile_testing", ], ) diff --git a/java/dagger/testing/compile/CompilerTests.java b/java/dagger/testing/compile/CompilerTests.java index 6750d0c65..a7fbef71b 100644 --- a/java/dagger/testing/compile/CompilerTests.java +++ b/java/dagger/testing/compile/CompilerTests.java @@ -29,10 +29,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map; import java.util.NoSuchElementException; +import com.tschuchort.compiletesting.KotlinCompilation; +import dagger.internal.codegen.ComponentProcessor; -/** - * A helper class for working with java compiler tests. - */ +/** A helper class for working with java compiler tests. */ public final class CompilerTests { private CompilerTests() {} @@ -53,6 +53,18 @@ public final class CompilerTests { return javac().withClasspath(ImmutableList.of(compilerDepsJar())); } + public static KotlinCompilation kotlinCompiler() { + KotlinCompilation compilation = new KotlinCompilation(); + compilation.setAnnotationProcessors(ImmutableList.of(new ComponentProcessor())); + compilation.setClasspaths( + ImmutableList.<java.io.File>builder() + .addAll(compilation.getClasspaths()) + .add(compilerDepsJar()) + .build() + ); + return compilation; + } + private static File getRunfilesDir() { return getRunfilesPath().toFile(); } diff --git a/javatests/artifacts/dagger-android/simple/build.gradle b/javatests/artifacts/dagger-android/simple/build.gradle index d29aa0c7a..3fedf3b5a 100644 --- a/javatests/artifacts/dagger-android/simple/build.gradle +++ b/javatests/artifacts/dagger-android/simple/build.gradle @@ -16,11 +16,11 @@ buildscript { ext { - agp_version = System.getenv('AGP_VERSION') ?: "4.2.0-beta04" + agp_version = System.getenv('AGP_VERSION') ?: "4.2.0" } repositories { google() - jcenter() + mavenCentral() } dependencies { classpath "com.android.tools.build:gradle:$agp_version" @@ -30,7 +30,6 @@ buildscript { allprojects { repositories { google() - jcenter() mavenCentral() mavenLocal() } diff --git a/javatests/artifacts/dagger-android/simple/gradle/wrapper/gradle-wrapper.properties b/javatests/artifacts/dagger-android/simple/gradle/wrapper/gradle-wrapper.properties index 4d9ca1649..0f80bbf51 100644 --- a/javatests/artifacts/dagger-android/simple/gradle/wrapper/gradle-wrapper.properties +++ b/javatests/artifacts/dagger-android/simple/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/dagger/example/gradle/simple/build.gradle b/javatests/artifacts/dagger/build-tests/build.gradle index c9e59ef23..407c7ef0f 100644 --- a/java/dagger/example/gradle/simple/build.gradle +++ b/javatests/artifacts/dagger/build-tests/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Dagger Authors. + * Copyright (C) 2021 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. @@ -19,22 +19,13 @@ plugins { id 'application' } -repositories { - mavenCentral() - mavenLocal() -} - -sourceSets { - main { - java { - srcDir '.' - } - } +// Set the versions in a system property so that tests can access it. +test { + systemProperty 'dagger_version', "$dagger_version" } dependencies { - implementation 'com.google.dagger:dagger:LOCAL-SNAPSHOT' - annotationProcessor 'com.google.dagger:dagger-compiler:LOCAL-SNAPSHOT' -} - -mainClassName = 'dagger.example.gradle.simple.SimpleApplication'
\ No newline at end of file + testImplementation "com.google.truth:truth:$truth_version" + testImplementation "junit:junit:$junit_version" + testImplementation gradleTestKit() +}
\ No newline at end of file diff --git a/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleFile.java b/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleFile.java new file mode 100644 index 000000000..6cd279c91 --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleFile.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 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 buildtests; + +/** Stores the name and content of a file. */ +public final class GradleFile { + /** Creates a {@link GradleFile} with the given name and content */ + public static GradleFile create(String fileName, String... fileContent) { + return new GradleFile(fileName, fileContent); + } + + private final String fileName; + private final String[] fileContent; + + GradleFile(String fileName, String... fileContent) { + this.fileName = fileName; + this.fileContent = fileContent; + } + + String fileName() { + return fileName; + } + + String[] fileContent() { + return fileContent; + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleModule.java b/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleModule.java new file mode 100644 index 000000000..d02dd91ea --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleModule.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 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 buildtests; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +/** Used to create files for a Gradle module in a particular directory. */ +public final class GradleModule { + public static GradleModule create(File moduleDir) { + return new GradleModule(moduleDir); + } + + public static GradleModule create(File projectDir, String moduleName) { + return new GradleModule(new File(projectDir, moduleName)); + } + + private final File moduleDir; + private final File moduleSrcDir; + + private GradleModule(File moduleDir) { + this.moduleDir = moduleDir; + this.moduleSrcDir = new File(moduleDir, "src/main/java/"); + } + + public GradleModule addBuildFile(String... content) throws IOException { + writeFile(createFile(moduleDir, "build.gradle"), content); + return this; + } + + public GradleModule addSettingsFile(String... content) throws IOException { + writeFile(createFile(moduleDir, "settings.gradle"), content); + return this; + } + + public GradleModule addFile(GradleFile gradleFile) throws IOException { + return addFile(gradleFile.fileName(), gradleFile.fileContent()); + } + + public GradleModule addFile(String fileName, String... content) throws IOException { + writeFile(createFile(moduleDir, fileName), content); + return this; + } + + public GradleModule addSrcFiles(GradleFile... gradleFiles) throws IOException { + for (GradleFile gradleFile : gradleFiles) { + addSrcFile(gradleFile.fileName(), gradleFile.fileContent()); + } + return this; + } + + public GradleModule addSrcFile(GradleFile gradleFile) throws IOException { + return addSrcFile(gradleFile.fileName(), gradleFile.fileContent()); + } + + public GradleModule addSrcFile(String fileName, String... content) throws IOException { + writeFile(createFile(moduleSrcDir, fileName), content); + return this; + } + + private static File createFile(File dir, String fileName) { + File file = new File(dir, fileName); + file.getParentFile().mkdirs(); + return file; + } + + private static void writeFile(File destination, String... content) throws IOException { + BufferedWriter output = null; + try { + output = new BufferedWriter(new FileWriter(destination)); + for (String line : content) { + output.write(line + "\n"); + } + } finally { + if (output != null) { + output.close(); + } + } + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveBindsQualifierTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveBindsQualifierTest.java new file mode 100644 index 000000000..6be8e3d3a --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveBindsQualifierTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2022 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// This is a regression test for https://github.com/google/dagger/issues/3136 +@RunWith(Parameterized.class) +public class TransitiveBindsQualifierTest { + @Parameters(name = "{0}") + public static Collection<Object[]> parameters() { + return Arrays.asList(new Object[][] {{ "implementation" }, { "api" }}); + } + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + private final String transitiveDependencyType; + + public TransitiveBindsQualifierTest(String transitiveDependencyType) { + this.transitiveDependencyType = transitiveDependencyType; + } + + @Test + public void testQualifierOnBindsMethod() throws IOException { + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = setupRunner().buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + assertThat(result.getOutput()) + .contains( + "ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MyQualifier' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (INTERFACE): library1.MyModule" + + "\n => element (METHOD): bindObject(java.lang.Number)" + + "\n => element (PARAMETER): arg0" + + "\n => annotation: @library2.MyQualifier"); + break; + case "api": + result = setupRunner().build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()) + .contains("BINDINGS: [" + + "@library2.MyQualifier java.lang.Object, " + + "@library2.MyQualifier java.lang.Number, " + + "java.lang.Integer" + + "]"); + break; + } + } + + private GradleRunner setupRunner() throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile( + "include 'app'", + "include 'library1'", + "include 'library2'", + "include 'spi-plugin'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "compileJava {", + " options.compilerArgs << '-Adagger.pluginsVisitFullBindingGraphs=ENABLED'", + "}", + "dependencies {", + " implementation project(':library1')", + " annotationProcessor project(':spi-plugin')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyComponent.java", + "package app;", + "", + "import dagger.BindsInstance;", + "import dagger.Component;", + "import library1.MyModule;", + "", + "@Component(modules = MyModule.class)", + "public interface MyComponent {", + " @Component.Factory", + " interface Factory {", + " MyComponent create(@BindsInstance int i);", + " }", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + transitiveDependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyModule.java", + "package library1;", + "", + "import dagger.Binds;", + "import dagger.Module;", + "import dagger.Provides;", + "import library2.MyQualifier;", + "", + "@Module", + "public interface MyModule {", + " @Binds", + " @MyQualifier", + " Object bindObject(@MyQualifier Number number);", + "", + " @Binds", + " @MyQualifier", + " Number bindNumber(int i);", + "}"); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation 'javax.inject:javax.inject:1'", + "}") + .addSrcFile( + "MyQualifier.java", + "package library2;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "public @interface MyQualifier {}"); + + // This plugin is used to print output about bindings that we can assert on in tests. + GradleModule.create(projectDir, "spi-plugin") + .addBuildFile( + "plugins {", + " id 'java'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", + " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", + " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", + "}") + .addSrcFile( + "TestBindingGraphPlugin.java", + "package spiplugin;", + "", + "import com.google.auto.service.AutoService;", + "import dagger.model.Binding;", + "import dagger.model.BindingGraph;", + "import dagger.model.BindingGraph.DependencyEdge;", + "import dagger.model.DependencyRequest;", + "import dagger.spi.BindingGraphPlugin;", + "import dagger.spi.DiagnosticReporter;", + "import java.util.stream.Collectors;", + "", + "@AutoService(BindingGraphPlugin.class)", + "public class TestBindingGraphPlugin implements BindingGraphPlugin {", + " @Override", + " public void visitGraph(", + " BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {", + " if (!bindingGraph.isFullBindingGraph() || bindingGraph.isModuleBindingGraph()) {", + " return;", + " }", + " System.out.print(", + " \"BINDINGS: \"", + " + bindingGraph.bindings().stream()", + " .map(Binding::key)", + " .collect(Collectors.toList()));", + " }", + "}"); + + return GradleRunner.create() + .withArguments("--stacktrace", "build") + .withProjectDir(projectDir); + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveBindsScopeTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveBindsScopeTest.java new file mode 100644 index 000000000..34716fef3 --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveBindsScopeTest.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2022 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// This is a regression test for https://github.com/google/dagger/issues/3136 +@RunWith(Parameterized.class) +public class TransitiveBindsScopeTest { + @Parameters(name = "{0}") + public static Collection<Object[]> parameters() { + return Arrays.asList(new Object[][] {{ "implementation" }, { "api" }}); + } + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + private final String transitiveDependencyType; + + public TransitiveBindsScopeTest(String transitiveDependencyType) { + this.transitiveDependencyType = transitiveDependencyType; + } + + @Test + public void testScopeOnBindsMethod() throws IOException { + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = setupRunner().buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + assertThat(result.getOutput()) + .contains( + "ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MyScope' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (INTERFACE): library1.MyModule" + + "\n => element (METHOD): bindObject(java.lang.String)" + + "\n => annotation: @library2.MyScope"); + break; + case "api": + result = setupRunner().build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()) + .contains( + "@Binds @library2.MyScope Object library1.MyModule.bindObject(String): SCOPED"); + break; + } + } + + private GradleRunner setupRunner() throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile( + "include 'app'", + "include 'library1'", + "include 'library2'", + "include 'spi-plugin'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "tasks.withType(JavaCompile) {", + " options.compilerArgs += '-Adagger.experimentalDaggerErrorMessages=ENABLED'", + "}", + "dependencies {", + " implementation project(':library1')", + " annotationProcessor project(':spi-plugin')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyComponent.java", + "package app;", + "", + "import dagger.Component;", + "import library1.MySubcomponent;", + "", + "@Component", + "public interface MyComponent {", + " MySubcomponent subcomponent();", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + transitiveDependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + // Note: In order to repro the issue we place MyScope on a subcomponent so that it can be a + // transitive dependency of the component. If MyScope was placed on directly on the + // component, it would need to be a direct dependency of the component. + .addSrcFile( + "MySubcomponent.java", + "package library1;", + "", + "import dagger.Subcomponent;", + "import library2.MyScope;", + "", + "@MyScope", + "@Subcomponent(modules = MyModule.class)", + "public interface MySubcomponent {", + " Object object();", + "}") + .addSrcFile( + "MyModule.java", + "package library1;", + "", + "import dagger.Binds;", + "import dagger.Module;", + "import dagger.Provides;", + "import library2.MyScope;", + "", + "@Module", + "public interface MyModule {", + " @MyScope", + " @Binds", + " Object bindObject(String string);", + "", + " @Provides", + " static String provideString() {", + " return \"\";", + " }", + "}"); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation 'javax.inject:javax.inject:1'", + "}") + .addSrcFile( + "MyScope.java", + "package library2;", + "", + "import javax.inject.Scope;", + "", + "@Scope", + "public @interface MyScope {}"); + + // This plugin is used to print output about bindings that we can assert on in tests. + GradleModule.create(projectDir, "spi-plugin") + .addBuildFile( + "plugins {", + " id 'java'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", + " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", + " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", + "}") + .addSrcFile( + "TestBindingGraphPlugin.java", + "package spiplugin;", + "", + "import com.google.auto.service.AutoService;", + "import dagger.model.BindingGraph;", + "import dagger.model.BindingGraph.DependencyEdge;", + "import dagger.model.DependencyRequest;", + "import dagger.spi.BindingGraphPlugin;", + "import dagger.spi.DiagnosticReporter;", + "", + "@AutoService(BindingGraphPlugin.class)", + "public class TestBindingGraphPlugin implements BindingGraphPlugin {", + " @Override", + " public void visitGraph(", + " BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {", + " bindingGraph.bindings().stream()", + " .filter(binding -> binding.scope().isPresent())", + " .forEach(binding -> System.out.println(binding + \": SCOPED\"));", + " bindingGraph.bindings().stream()", + " .filter(binding -> !binding.scope().isPresent())", + " .forEach(binding -> System.out.println(binding + \": UNSCOPED\"));", + " }", + "}"); + + return GradleRunner.create() + .withArguments("--stacktrace", "build") + .withProjectDir(projectDir); + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveComponentDependenciesTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveComponentDependenciesTest.java new file mode 100644 index 000000000..9a1adcf1e --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveComponentDependenciesTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2022 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// This is a regression test for https://github.com/google/dagger/issues/3136 +@RunWith(Parameterized.class) +public class TransitiveComponentDependenciesTest { + @Parameters(name = "{0}") + public static Collection<Object[]> parameters() { + return Arrays.asList(new Object[][] {{"implementation"}, {"api"}}); + } + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + private final String transitiveDependencyType; + + public TransitiveComponentDependenciesTest(String transitiveDependencyType) { + this.transitiveDependencyType = transitiveDependencyType; + } + + @Test + public void testsComponentDependencies() throws IOException { + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = setupRunner().buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + String expectedErrorMsg = + "error: ComponentProcessingStep was unable to process 'app.ComponentC' because" + + " 'libraryA.ComponentA' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (CLASS): libraryB.ComponentB" + + "\n => annotation:" + + " @dagger.Component(dependencies = libraryA.ComponentA.class)" + + "\n => annotation method: java.lang.Class<?>[] dependencies()" + + "\n => annotation value (ARRAY):" + + " value 'libraryA.ComponentA.class' with expected type java.lang.Class<?>[]" + + "\n => annotation value (TYPE):" + + " value 'libraryA.ComponentA' with expected type java.lang.Class<?>" + + "\n => type (ERROR annotation value type): libraryA.ComponentA"; + assertThat(result.getOutput()).contains(expectedErrorMsg); + break; + case "api": + result = setupRunner().build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + break; + } + } + + private GradleRunner setupRunner() throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile("include 'app'", "include 'libraryB'", "include 'libraryA'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "tasks.withType(JavaCompile) {", + " options.compilerArgs += '-Adagger.experimentalDaggerErrorMessages=ENABLED'", + "}", + "dependencies {", + " implementation project(':libraryB')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "ComponentC.java", + "package app;", + "", + "import dagger.Component;", + "import libraryB.ComponentB;", + "", + "@Component(dependencies = ComponentB.class)", + "public interface ComponentC {", + " public abstract C getC();", + "}") + .addSrcFile( + "C.java", + "package app;", + "", + "import javax.inject.Inject;", + "import libraryB.B;", + "", + "public class C {", + " @Inject C(B b) {}", + "}"); + + GradleModule.create(projectDir, "libraryB") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + transitiveDependencyType + " project(':libraryA')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "ComponentB.java", + "package libraryB;", + "", + "import dagger.Component;", + "import libraryA.ComponentA;", + "", + "@Component(dependencies = ComponentA.class)", + "public abstract class ComponentB {", + " public abstract B getB();", + "}") + .addSrcFile( + "B.java", + "package libraryB;", + "", + "import javax.inject.Inject;", + "import libraryA.A;", + "", + "public class B {", + " @Inject B(A a) {}", + "}"); + + GradleModule.create(projectDir, "libraryA") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "ComponentA.java", + "package libraryA;", + "", + "import dagger.Component;", + "", + "@Component", + "public abstract class ComponentA {", + " public abstract A getA();", + "}") + .addSrcFile( + "A.java", + "package libraryA;", + "", + "import javax.inject.Inject;", + "", + "public class A {", + " @Inject A() {}", + "}") + .addSrcFile( + "AScope.java", + "package libraryA;", + "", + "import javax.inject.Scope;", + "", + "@Scope", + "public @interface AScope {}"); + + return GradleRunner.create().withArguments("--stacktrace", "build").withProjectDir(projectDir); + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveMapKeyTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveMapKeyTest.java new file mode 100644 index 000000000..0458b0874 --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveMapKeyTest.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2021 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +// This is a regression test for https://github.com/google/dagger/issues/3133 +@RunWith(JUnit4.class) +public class TransitiveMapKeyTest { + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void testTransitiveMapKey_WithImplementation() throws IOException { + BuildResult result = setupRunnerWith("implementation").buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + assertThat(result.getOutput()) + .contains( + "Missing map key annotation for method: library1.MyModule#provideString(). " + + "That method was annotated with: " + + "@dagger.Provides," + + "@dagger.multibindings.IntoMap," + + "@library2.MyMapKey(\"some-key\")"); + } + + @Test + public void testTransitiveMapKey_WithApi() throws IOException { + // Test that if we use an "api" dependency for the custom map key things work properly. + BuildResult result = setupRunnerWith("api").build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + } + + private GradleRunner setupRunnerWith(String dependencyType) throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile( + "include 'app'", + "include 'library1'", + "include 'library2'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "dependencies {", + " implementation project(':library1')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyComponent.java", + "package app;", + "", + "import dagger.Component;", + "import library1.MyModule;", + "import java.util.Map;", + "", + "@Component(modules = MyModule.class)", + "public interface MyComponent {", + " Map<String, String> multiMap();", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + dependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyModule.java", + "package library1;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import dagger.multibindings.IntoMap;", + "import library2.MyMapKey;", + "", + "@Module", + "public interface MyModule {", + " @Provides", + " @IntoMap", + " @MyMapKey(\"some-key\")", + " static String provideString() {", + " return \"\";", + " }", + "}"); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyMapKey.java", + "package library2;", + "", + "import dagger.MapKey;", + "", + "@MapKey", + "public @interface MyMapKey {", + " String value();", + "}"); + + return GradleRunner.create() + .withArguments("--stacktrace", "build") + .withProjectDir(projectDir); + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveProvidesParameterizedTypeTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveProvidesParameterizedTypeTest.java new file mode 100644 index 000000000..181c9d2d0 --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveProvidesParameterizedTypeTest.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2022 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// This is a regression test for https://github.com/google/dagger/issues/3163 +@RunWith(Parameterized.class) +public class TransitiveProvidesParameterizedTypeTest { + @Parameters(name = "{0}") + public static Collection<Object[]> parameters() { + return Arrays.asList(new Object[][] {{ "implementation" }, { "api" }}); + } + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + private final String transitiveDependencyType; + + public TransitiveProvidesParameterizedTypeTest(String transitiveDependencyType) { + this.transitiveDependencyType = transitiveDependencyType; + } + + @Test + public void testQualifierOnProvidesMethodParameter() throws IOException { + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = setupRunner().buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + assertThat(result.getOutput()) + .contains( + "error: ComponentProcessingStep was unable to process 'app.MyComponent' because" + + " 'library2.TransitiveType' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (INTERFACE): library1.MyModule" + + "\n => element (METHOD):" + + " provideInt(library2.TransitiveType<java.lang.String>)" + + "\n => type (EXECUTABLE method):" + + " (library2.TransitiveType<java.lang.String>)java.lang.String" + + "\n => type (ERROR parameter type):" + + " library2.TransitiveType<java.lang.String>"); + break; + case "api": + result = setupRunner().build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()) + .contains("Binding: library2.TransitiveType<java.lang.String>"); + break; + } + } + + private GradleRunner setupRunner() throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile( + "include 'app'", + "include 'library1'", + "include 'library2'", + "include 'spi-plugin'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "tasks.withType(JavaCompile) {", + " options.compilerArgs += '-Adagger.experimentalDaggerErrorMessages=ENABLED'", + "}", + "dependencies {", + " implementation project(':library1')", + " annotationProcessor project(':spi-plugin')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyComponent.java", + "package app;", + "", + "import dagger.Component;", + "import library1.MyModule;", + "", + "@Component(modules = MyModule.class)", + "public interface MyComponent {", + " String string();", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + transitiveDependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyModule.java", + "package library1;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import library2.TransitiveType;", + "", + "@Module", + "public interface MyModule {", + " @Provides", + " static String provideInt(TransitiveType<String> transitiveType) {", + " return \"\";", + " }", + "", + " @Provides", + " static TransitiveType<String> provideTransitiveType() {", + " return new TransitiveType<>();", + " }", + "}"); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation 'javax.inject:javax.inject:1'", + "}") + .addSrcFile( + "TransitiveType.java", + "package library2;", + "", + "public class TransitiveType<T> {}"); + + // This plugin is used to print output about bindings that we can assert on in tests. + GradleModule.create(projectDir, "spi-plugin") + .addBuildFile( + "plugins {", + " id 'java'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", + " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", + " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", + "}") + .addSrcFile( + "TestBindingGraphPlugin.java", + "package spiplugin;", + "", + "import com.google.auto.service.AutoService;", + "import dagger.model.Binding;", + "import dagger.model.BindingGraph;", + "import dagger.model.BindingGraph.DependencyEdge;", + "import dagger.model.DependencyRequest;", + "import dagger.spi.BindingGraphPlugin;", + "import dagger.spi.DiagnosticReporter;", + "", + "@AutoService(BindingGraphPlugin.class)", + "public class TestBindingGraphPlugin implements BindingGraphPlugin {", + " @Override", + " public void visitGraph(", + " BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {", + " bindingGraph.bindings().stream()", + " .map(Binding::key)", + " .forEach(key -> System.out.println(\"Binding: \" + key));", + " }", + "}"); + + return GradleRunner.create() + .withArguments("--stacktrace", "build") + .withProjectDir(projectDir); + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveProvidesQualifierTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveProvidesQualifierTest.java new file mode 100644 index 000000000..aaf7745d8 --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveProvidesQualifierTest.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2022 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// This is a regression test for https://github.com/google/dagger/issues/3136 +@RunWith(Parameterized.class) +public class TransitiveProvidesQualifierTest { + @Parameters(name = "{0}") + public static Collection<Object[]> parameters() { + return Arrays.asList(new Object[][] {{ "implementation" }, { "api" }}); + } + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + private final String transitiveDependencyType; + + public TransitiveProvidesQualifierTest(String transitiveDependencyType) { + this.transitiveDependencyType = transitiveDependencyType; + } + + @Test + public void testQualifierOnProvidesMethodParameter() throws IOException { + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = setupRunner().buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + assertThat(result.getOutput()) + .contains( + "ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MyQualifier' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (INTERFACE): library1.MyModule" + + "\n => element (METHOD): provideString(int)" + + "\n => element (PARAMETER): i" + + "\n => annotation: @library2.MyQualifier"); + break; + case "api": + result = setupRunner().build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: @library2.MyQualifier java.lang.Integer"); + break; + } + } + + private GradleRunner setupRunner() throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile( + "include 'app'", + "include 'library1'", + "include 'library2'", + "include 'spi-plugin'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "tasks.withType(JavaCompile) {", + " options.compilerArgs += '-Adagger.experimentalDaggerErrorMessages=ENABLED'", + "}", + "dependencies {", + " implementation project(':library1')", + " annotationProcessor project(':spi-plugin')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyComponent.java", + "package app;", + "", + "import dagger.Component;", + "import library1.MyModule;", + "", + "@Component(modules = MyModule.class)", + "public interface MyComponent {", + " String string();", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + transitiveDependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyModule.java", + "package library1;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import library2.MyQualifier;", + "", + "@Module", + "public interface MyModule {", + " @Provides", + " static String provideString(@MyQualifier int i) {", + " return \"\";", + " }", + "", + " @Provides", + " @MyQualifier", + " static int provideInt() {", + " return 0;", + " }", + "}"); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation 'javax.inject:javax.inject:1'", + "}") + .addSrcFile( + "MyQualifier.java", + "package library2;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "public @interface MyQualifier {}"); + + // This plugin is used to print output about bindings that we can assert on in tests. + GradleModule.create(projectDir, "spi-plugin") + .addBuildFile( + "plugins {", + " id 'java'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", + " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", + " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", + "}") + .addSrcFile( + "TestBindingGraphPlugin.java", + "package spiplugin;", + "", + "import com.google.auto.service.AutoService;", + "import dagger.model.BindingGraph;", + "import dagger.model.BindingGraph.DependencyEdge;", + "import dagger.model.DependencyRequest;", + "import dagger.spi.BindingGraphPlugin;", + "import dagger.spi.DiagnosticReporter;", + "", + "@AutoService(BindingGraphPlugin.class)", + "public class TestBindingGraphPlugin implements BindingGraphPlugin {", + " @Override", + " public void visitGraph(", + " BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {", + " bindingGraph.dependencyEdges().stream()", + " .map(DependencyEdge::dependencyRequest)", + " .map(DependencyRequest::key)", + " .forEach(key -> System.out.println(\"REQUEST: \" + key));", + " }", + "}"); + + return GradleRunner.create() + .withArguments("--stacktrace", "build") + .withProjectDir(projectDir); + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveProvidesScopeTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveProvidesScopeTest.java new file mode 100644 index 000000000..36f3cdc2a --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveProvidesScopeTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2022 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// This is a regression test for https://github.com/google/dagger/issues/3136 +@RunWith(Parameterized.class) +public class TransitiveProvidesScopeTest { + @Parameters(name = "{0}") + public static Collection<Object[]> parameters() { + return Arrays.asList(new Object[][] {{ "implementation" }, { "api" }}); + } + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + private final String transitiveDependencyType; + + public TransitiveProvidesScopeTest(String transitiveDependencyType) { + this.transitiveDependencyType = transitiveDependencyType; + } + + @Test + public void testScopeOnProvidesMethod() throws IOException { + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = setupRunner().buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + assertThat(result.getOutput()) + .contains( + "ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MyScope' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (INTERFACE): library1.MyModule" + + "\n => element (METHOD): provideString()" + + "\n => annotation: @library2.MyScope"); + break; + case "api": + result = setupRunner().build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()) + .contains( + "@Provides @library2.MyScope String library1.MyModule.provideString(): SCOPED"); + break; + } + } + + private GradleRunner setupRunner() throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile( + "include 'app'", + "include 'library1'", + "include 'library2'", + "include 'spi-plugin'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "tasks.withType(JavaCompile) {", + " options.compilerArgs += '-Adagger.experimentalDaggerErrorMessages=ENABLED'", + "}", + "dependencies {", + " implementation project(':library1')", + " annotationProcessor project(':spi-plugin')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyComponent.java", + "package app;", + "", + "import dagger.Component;", + "import library1.MySubcomponent;", + "", + "@Component", + "public interface MyComponent {", + " MySubcomponent subcomponent();", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + transitiveDependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + // Note: In order to repro the issue we place MyScope on a subcomponent so that it can be a + // transitive dependency of the component. If MyScope was placed on directly on the + // component, it would need to be a direct dependency of the component. + .addSrcFile( + "MySubcomponent.java", + "package library1;", + "", + "import dagger.Subcomponent;", + "import library2.MyScope;", + "", + "@MyScope", + "@Subcomponent(modules = MyModule.class)", + "public interface MySubcomponent {", + " String string();", + "}") + .addSrcFile( + "MyModule.java", + "package library1;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import library2.MyScope;", + "", + "@Module", + "public interface MyModule {", + " @MyScope", + " @Provides", + " static String provideString() {", + " return \"\";", + " }", + "}"); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation 'javax.inject:javax.inject:1'", + "}") + .addSrcFile( + "MyScope.java", + "package library2;", + "", + "import javax.inject.Scope;", + "", + "@Scope", + "public @interface MyScope {}"); + + // This plugin is used to print output about bindings that we can assert on in tests. + GradleModule.create(projectDir, "spi-plugin") + .addBuildFile( + "plugins {", + " id 'java'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", + " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", + " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", + "}") + .addSrcFile( + "TestBindingGraphPlugin.java", + "package spiplugin;", + "", + "import com.google.auto.service.AutoService;", + "import dagger.model.BindingGraph;", + "import dagger.model.BindingGraph.DependencyEdge;", + "import dagger.model.DependencyRequest;", + "import dagger.spi.BindingGraphPlugin;", + "import dagger.spi.DiagnosticReporter;", + "", + "@AutoService(BindingGraphPlugin.class)", + "public class TestBindingGraphPlugin implements BindingGraphPlugin {", + " @Override", + " public void visitGraph(", + " BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {", + " bindingGraph.bindings().stream()", + " .filter(binding -> binding.scope().isPresent())", + " .forEach(binding -> System.out.println(binding + \": SCOPED\"));", + " bindingGraph.bindings().stream()", + " .filter(binding -> !binding.scope().isPresent())", + " .forEach(binding -> System.out.println(binding + \": UNSCOPED\"));", + " }", + "}"); + + return GradleRunner.create() + .withArguments("--stacktrace", "build") + .withProjectDir(projectDir); + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveQualifierTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveQualifierTest.java new file mode 100644 index 000000000..1cf1ba553 --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveQualifierTest.java @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2022 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// This is a regression test for https://github.com/google/dagger/issues/3136 +@RunWith(Parameterized.class) +public class TransitiveQualifierTest { + @Parameters(name = "transitiveDependencyType = {0}, strictSuperficialValidationMode = {1}") + public static Collection<Object[]> parameters() { + return Arrays.asList( + new Object[][] { + { "implementation", "ENABLED" }, + { "implementation", "DISABLED" }, + { "api", "ENABLED" }, + { "api", "DISABLED" } + }); + } + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + private final String transitiveDependencyType; + private final String strictSuperficialValidationMode; + + public TransitiveQualifierTest( + String transitiveDependencyType, String strictSuperficialValidationMode) { + this.transitiveDependencyType = transitiveDependencyType; + this.strictSuperficialValidationMode = strictSuperficialValidationMode; + } + + @Test + public void testQualifierOnInjectConstructorParameter() throws IOException { + GradleRunner runner = + setupRunnerWith( + GradleFile.create( + "QualifierUsage.java", + "package library1;", + "", + "import javax.inject.Inject;", + "import library2.MyQualifier;", + "", + "public class QualifierUsage {", + " @Inject QualifierUsage(@MyQualifier int i) {}", + "}")); + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + switch (strictSuperficialValidationMode) { + case "ENABLED": + result = runner.buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + // TODO(bcorso): Give more context about what couldn't be resolved once we've fixed the + // issue described in https://github.com/google/dagger/issues/2208. + assertThat(result.getOutput()) + .contains( + "ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MyQualifier' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (INTERFACE): library1.MyModule" + + "\n => element (METHOD): provideInt()" + + "\n => annotation: @library2.MyQualifier"); + break; + case "DISABLED": + // When strict mode is disabled we fall back to the old behavior where the qualifier is + // missing and we do not throw an exception. + result = runner.build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: java.lang.Integer"); + break; + default: throw new AssertionError("Unexpected mode: " + strictSuperficialValidationMode); + } + break; + case "api": + result = runner.build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: @library2.MyQualifier java.lang.Integer"); + break; + } + } + + @Test + public void testQualifierOnInjectField() throws IOException { + GradleRunner runner = + setupRunnerWith( + GradleFile.create( + "QualifierUsage.java", + "package library1;", + "", + "import javax.inject.Inject;", + "import library2.MyQualifier;", + "", + "public class QualifierUsage {", + " @Inject @MyQualifier int i;", + "", + " @Inject QualifierUsage() {}", + "}")); + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + switch (strictSuperficialValidationMode) { + case "ENABLED": + result = runner.buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + // TODO(bcorso): Give more context about what couldn't be resolved once we've fixed the + // issue described in https://github.com/google/dagger/issues/2208. + assertThat(result.getOutput()) + .contains( + "ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MyQualifier' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (INTERFACE): library1.MyModule" + + "\n => element (METHOD): provideInt()" + + "\n => annotation: @library2.MyQualifier"); + break; + case "DISABLED": + // When strict mode is disabled we fall back to the old behavior where the qualifier is + // missing and we do not throw an exception. + result = runner.build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: java.lang.Integer"); + break; + default: throw new AssertionError("Unexpected mode: " + strictSuperficialValidationMode); + } + break; + case "api": + result = runner.build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: @library2.MyQualifier java.lang.Integer"); + break; + } + } + + @Test + public void testQualifierOnInjectMethodParameter() throws IOException { + GradleRunner runner = + setupRunnerWith( + GradleFile.create( + "QualifierUsage.java", + "package library1;", + "", + "import javax.inject.Inject;", + "import library2.MyQualifier;", + "", + "public class QualifierUsage {", + " @Inject QualifierUsage() {}", + "", + " @Inject void injectMethod(@MyQualifier int i) {}", + "}")); + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + switch (strictSuperficialValidationMode) { + case "ENABLED": + result = runner.buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + // TODO(bcorso): Give more context about what couldn't be resolved once we've fixed the + // issue described in https://github.com/google/dagger/issues/2208. + assertThat(result.getOutput()) + .contains( + "ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MyQualifier' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (INTERFACE): library1.MyModule" + + "\n => element (METHOD): provideInt()" + + "\n => annotation: @library2.MyQualifier"); + break; + case "DISABLED": + // When strict mode is disabled we fall back to the old behavior where the qualifier is + // missing and we do not throw an exception. + result = runner.build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: java.lang.Integer"); + break; + default: throw new AssertionError("Unexpected mode: " + strictSuperficialValidationMode); + } + break; + case "api": + result = runner.build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: @library2.MyQualifier java.lang.Integer"); + break; + } + } + + private GradleRunner setupRunnerWith(GradleFile qualifierUsage) throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile( + "include 'app'", + "include 'library1'", + "include 'library2'", + "include 'spi-plugin'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "tasks.withType(JavaCompile) {", + String.format( + " options.compilerArgs += '-Adagger.strictSuperficialValidation=%s'", + strictSuperficialValidationMode), + "}", + "dependencies {", + " implementation project(':library1')", + " annotationProcessor project(':spi-plugin')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyComponent.java", + "package app;", + "", + "import dagger.Component;", + "import library1.MyModule;", + "import library1.QualifierUsage;", + "", + "@Component(modules = MyModule.class)", + "public interface MyComponent {", + " QualifierUsage qualifierUsage();", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + transitiveDependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyModule.java", + "package library1;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import library2.MyQualifier;", + "", + "@Module", + "public interface MyModule {", + " @Provides", + " @MyQualifier", + " static int provideInt() {", + " return 0;", + " }", + "}") + .addSrcFile(qualifierUsage); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation 'javax.inject:javax.inject:1'", + "}") + .addSrcFile( + "MyQualifier.java", + "package library2;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "public @interface MyQualifier {}"); + + // This plugin is used to print output about bindings that we can assert on in tests. + GradleModule.create(projectDir, "spi-plugin") + .addBuildFile( + "plugins {", + " id 'java'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", + " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", + " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", + "}") + .addSrcFile( + "TestBindingGraphPlugin.java", + "package spiplugin;", + "", + "import com.google.auto.service.AutoService;", + "import dagger.model.BindingGraph;", + "import dagger.model.BindingGraph.DependencyEdge;", + "import dagger.model.DependencyRequest;", + "import dagger.spi.BindingGraphPlugin;", + "import dagger.spi.DiagnosticReporter;", + "", + "@AutoService(BindingGraphPlugin.class)", + "public class TestBindingGraphPlugin implements BindingGraphPlugin {", + " @Override", + " public void visitGraph(", + " BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {", + " bindingGraph.dependencyEdges().stream()", + " .map(DependencyEdge::dependencyRequest)", + " .map(DependencyRequest::key)", + " .forEach(key -> System.out.println(\"REQUEST: \" + key));", + " }", + "}"); + + return GradleRunner.create() + .withArguments("--stacktrace", "build") + .withProjectDir(projectDir); + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveScopeTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveScopeTest.java new file mode 100644 index 000000000..ec54605dd --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveScopeTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2021 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +// This is a regression test for https://github.com/google/dagger/issues/3136 +@RunWith(JUnit4.class) +public class TransitiveScopeTest { + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void testTransitiveScope_WithImplementation() throws IOException { + BuildResult result = setupRunnerWith("implementation").buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + assertThat(result.getOutput()) + .contains( + "ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MyScope' could not be resolved." + + "\n " + + "\n Dependency trace:" + // Note: this fails on the subcomponent rather than Foo because the subcomponent is + // validated before any of its dependencies. + + "\n => element (INTERFACE): library1.MySubcomponent" + + "\n => annotation: @library2.MyScope"); + } + + @Test + public void testTransitiveScope_WithApi() throws IOException { + BuildResult result = setupRunnerWith("api").build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("@Inject library1.Foo(): SCOPED"); + } + + private GradleRunner setupRunnerWith(String dependencyType) throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile( + "include 'app'", + "include 'library1'", + "include 'library2'", + "include 'spi-plugin'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "dependencies {", + " implementation project(':library1')", + " annotationProcessor project(':spi-plugin')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyComponent.java", + "package app;", + "", + "import dagger.Component;", + "import library1.MySubcomponent;", + "", + "@Component", + "public interface MyComponent {", + " MySubcomponent subcomponent();", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + dependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "Foo.java", + "package library1;", + "", + "import javax.inject.Inject;", + "import library2.MyScope;", + "", + "@MyScope", + "public class Foo {", + " @Inject Foo() {}", + "}") + // Note: In order to repro the issue we place MyScope on a subcomponent so that it can be a + // transitive dependency of the component. If MyScope was placed on directly on the + // component, it would need to be a direct dependency of the component. + .addSrcFile( + "MySubcomponent.java", + "package library1;", + "", + "import dagger.Subcomponent;", + "import library2.MyScope;", + "", + "@MyScope", + "@Subcomponent", + "public interface MySubcomponent {", + " Foo foo();", + "}"); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation 'javax.inject:javax.inject:1'", + "}") + .addSrcFile( + "MyScope.java", + "package library2;", + "", + "import javax.inject.Scope;", + "", + "@Scope", + "public @interface MyScope {}"); + + // This plugin is used to print output about bindings that we can assert on in tests. + GradleModule.create(projectDir, "spi-plugin") + .addBuildFile( + "plugins {", + " id 'java'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", + " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", + " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", + "}") + .addSrcFile( + "TestBindingGraphPlugin.java", + "package spiplugin;", + "", + "import com.google.auto.service.AutoService;", + "import dagger.model.BindingGraph;", + "import dagger.spi.BindingGraphPlugin;", + "import dagger.spi.DiagnosticReporter;", + "", + "@AutoService(BindingGraphPlugin.class)", + "public class TestBindingGraphPlugin implements BindingGraphPlugin {", + " @Override", + " public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter" + + " diagnosticReporter) {", + " bindingGraph.bindings().stream()", + " .filter(binding -> binding.scope().isPresent())", + " .forEach(binding -> System.out.println(binding + \": SCOPED\"));", + " bindingGraph.bindings().stream()", + " .filter(binding -> !binding.scope().isPresent())", + " .forEach(binding -> System.out.println(binding + \": UNSCOPED\"));", + " }", + "}"); + + return GradleRunner.create() + .withArguments("--stacktrace", "build") + .withProjectDir(projectDir); + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentModulesTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentModulesTest.java new file mode 100644 index 000000000..31376c09d --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentModulesTest.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2022 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// This is a regression test for https://github.com/google/dagger/issues/3136 +@RunWith(Parameterized.class) +public class TransitiveSubcomponentModulesTest { + @Parameters(name = "{0}") + public static Collection<Object[]> parameters() { + return Arrays.asList(new Object[][] {{"implementation"}, {"api"}}); + } + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + private final String transitiveDependencyType; + + public TransitiveSubcomponentModulesTest(String transitiveDependencyType) { + this.transitiveDependencyType = transitiveDependencyType; + } + + @Test + public void testSubcomponentAnnotationWithTransitiveModule() throws IOException { + GradleRunner runner = + setupRunner( + GradleFile.create( + "MySubcomponent.java", + "package library1;", + "", + "import dagger.Subcomponent;", + "import library2.TransitiveModule;", + "", + "@Subcomponent(modules = TransitiveModule.class)", + "public abstract class MySubcomponent {", + " public abstract int getInt();", + "}")); + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = runner.buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + String expectedErrorMsg = + "error: ComponentProcessingStep was unable to process 'app.MyComponent' because" + + " 'library2.TransitiveModule' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (CLASS): library1.MySubcomponent" + + "\n => annotation:" + + " @dagger.Subcomponent(modules = library2.TransitiveModule.class)" + + "\n => annotation method: java.lang.Class<?>[] modules()" + + "\n => annotation value (ARRAY):" + + " value 'library2.TransitiveModule.class' with expected type java.lang.Class<?>[]" + + "\n => annotation value (TYPE):" + + " value 'library2.TransitiveModule' with expected type java.lang.Class<?>" + + "\n => type (ERROR annotation value type): library2.TransitiveModule"; + assertThat(result.getOutput()).contains(expectedErrorMsg); + break; + case "api": + result = runner.build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + break; + } + } + + @Test + public void testSubcomponentAnnotationWithModuleIncludesTransitiveModuleDependencies() + throws IOException { + GradleRunner runner = + setupRunner( + GradleFile.create( + "MySubcomponent.java", + "package library1;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent(modules = IncludesTransitiveModule.class)", + "public abstract class MySubcomponent {", + " public abstract int getInt();", + "}")); + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = runner.buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + String expectedErrorMsg = + "error: ComponentProcessingStep was unable to process 'app.MyComponent' because" + + " 'library2.TransitiveModule' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (INTERFACE): library1.IncludesTransitiveModule" + + "\n => annotation:" + + " @dagger.Module(includes = library2.TransitiveModule.class)" + + "\n => annotation method: java.lang.Class<?>[] includes()" + + "\n => annotation value (ARRAY):" + + " value 'library2.TransitiveModule.class' with expected type java.lang.Class<?>[]" + + "\n => annotation value (TYPE):" + + " value 'library2.TransitiveModule' with expected type java.lang.Class<?>"; + assertThat(result.getOutput()).contains(expectedErrorMsg); + break; + case "api": + result = runner.build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + break; + } + } + + private GradleRunner setupRunner(GradleFile subcomponent) throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile("include 'app'", "include 'library1'", "include 'library2'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "tasks.withType(JavaCompile) {", + " options.compilerArgs += '-Adagger.experimentalDaggerErrorMessages=ENABLED'", + "}", + "dependencies {", + " implementation project(':library1')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyComponent.java", + "package app;", + "", + "import dagger.Component;", + "import library1.MySubcomponent;", + "", + "@Component", + "public interface MyComponent {", + " MySubcomponent mySubcomponent();", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + transitiveDependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "IncludesTransitiveModule.java", + "package library1;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import library2.TransitiveModule;", + "", + "@Module(includes = TransitiveModule.class)", + "public interface IncludesTransitiveModule {}") + .addSrcFile(subcomponent); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "TransitiveModule.java", + "package library2;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "", + "@Module", + "public interface TransitiveModule {", + " @Provides", + " static int provideInt() {", + " return 0;", + " }", + "}"); + + return GradleRunner.create().withArguments("--stacktrace", "build").withProjectDir(projectDir); + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentQualifierTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentQualifierTest.java new file mode 100644 index 000000000..c135d742b --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentQualifierTest.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2022 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// This is a regression test for https://github.com/google/dagger/issues/3136 +@RunWith(Parameterized.class) +public class TransitiveSubcomponentQualifierTest { + @Parameters(name = "{0}") + public static Collection<Object[]> parameters() { + return Arrays.asList(new Object[][] {{"implementation"}, {"api"}}); + } + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + private final String transitiveDependencyType; + + public TransitiveSubcomponentQualifierTest(String transitiveDependencyType) { + this.transitiveDependencyType = transitiveDependencyType; + } + + @Test + public void testQualifierWithFactory() throws IOException { + GradleRunner runner = + setupRunnerWith( + GradleFile.create( + "MySubcomponent.java", + "package library1;", + "", + "import dagger.BindsInstance;", + "import dagger.Subcomponent;", + "import library2.MyQualifier;", + "", + "@Subcomponent", + "public abstract class MySubcomponent {", + " @MyQualifier", + " public abstract int getQualifiedInt();", + "", + " @Subcomponent.Factory", + " public abstract static class Creator {", + " public abstract MySubcomponent create(", + " @BindsInstance @MyQualifier int qualifiedInt);", + " }", + "}")); + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = runner.buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + assertThat(result.getOutput()) + .contains( + "error: ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MyQualifier' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (CLASS): library1.MySubcomponent" + + "\n => element (METHOD): getQualifiedInt()" + + "\n => annotation: @library2.MyQualifier"); + break; + case "api": + result = runner.build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()) + .contains("ENTRY_POINT_REQUEST: @library2.MyQualifier java.lang.Integer"); + break; + } + } + + @Test + public void testQualifierOnBaseClassWithFactory() throws IOException { + GradleRunner runner = + setupRunnerWith( + GradleFile.create( + "MySubcomponent.java", + "package library1;", + "", + "import dagger.Subcomponent;", + "import library2.MyQualifier;", + "", + "@Subcomponent", + "public abstract class MySubcomponent extends MyBaseSubcomponent {", + " @Subcomponent.Factory", + " public abstract static class Creator extends MyBaseSubcomponent.Creator {}", + "}"), + GradleFile.create( + "MyBaseSubcomponent.java", + "package library1;", + "", + "import dagger.BindsInstance;", + "import library2.MyQualifier;", + "", + "public abstract class MyBaseSubcomponent {", + " @MyQualifier", + " public abstract int getQualifiedInt();", + "", + " public abstract static class Creator {", + " public abstract MySubcomponent create(", + " @BindsInstance @MyQualifier int qualifiedInt);", + " }", + "}")); + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = runner.buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + assertThat(result.getOutput()) + .contains( + "error: ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MyQualifier' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (CLASS): library1.MyBaseSubcomponent" + + "\n => element (METHOD): getQualifiedInt()" + + "\n => annotation: @library2.MyQualifier"); + break; + case "api": + result = runner.build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()) + .contains("ENTRY_POINT_REQUEST: @library2.MyQualifier java.lang.Integer"); + break; + } + } + + @Test + public void testQualifierWithBuilder() throws IOException { + GradleRunner runner = + setupRunnerWith( + GradleFile.create( + "MySubcomponent.java", + "package library1;", + "", + "import dagger.BindsInstance;", + "import dagger.Subcomponent;", + "import library2.MyQualifier;", + "", + "@Subcomponent", + "public abstract class MySubcomponent {", + " @MyQualifier", + " public abstract int getQualifiedInt();", + "", + " @Subcomponent.Builder", + " public abstract static class Creator {", + " public abstract MySubcomponent build();", + " public abstract Creator qualifiedInt(", + " @BindsInstance @MyQualifier int qualifiedInt);", + " }", + "}")); + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = runner.buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + assertThat(result.getOutput()) + .contains( + "error: ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MyQualifier' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (CLASS): library1.MySubcomponent" + + "\n => element (METHOD): getQualifiedInt()" + + "\n => annotation: @library2.MyQualifier"); + break; + case "api": + result = runner.build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()) + .contains("ENTRY_POINT_REQUEST: @library2.MyQualifier java.lang.Integer"); + break; + } + } + + @Test + public void testQualifierOnBaseClassWithBuilder() throws IOException { + GradleRunner runner = + setupRunnerWith( + GradleFile.create( + "MySubcomponent.java", + "package library1;", + "", + "import dagger.Subcomponent;", + "import library2.MyQualifier;", + "", + "@Subcomponent", + "public abstract class MySubcomponent extends MyBaseSubcomponent {", + " @Subcomponent.Builder", + " public abstract static class Creator extends MyBaseSubcomponent.Creator {}", + "}"), + GradleFile.create( + "MyBaseSubcomponent.java", + "package library1;", + "", + "import dagger.BindsInstance;", + "import library2.MyQualifier;", + "", + "public abstract class MyBaseSubcomponent {", + " @MyQualifier", + " public abstract int getQualifiedInt();", + "", + " public abstract static class Creator {", + " public abstract MySubcomponent build();", + " public abstract Creator qualifiedInt(", + " @BindsInstance @MyQualifier int qualifiedInt);", + " }", + "}")); + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = runner.buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + // TODO(bcorso): Give more context about what couldn't be resolved once we've fixed the + // issue described in https://github.com/google/dagger/issues/2208. + assertThat(result.getOutput()) + .contains( + "error: ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MyQualifier' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (CLASS): library1.MyBaseSubcomponent" + + "\n => element (METHOD): getQualifiedInt()" + + "\n => annotation: @library2.MyQualifier"); + break; + case "api": + result = runner.build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()) + .contains("ENTRY_POINT_REQUEST: @library2.MyQualifier java.lang.Integer"); + break; + } + } + + private GradleRunner setupRunnerWith(GradleFile... library1Files) throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile( + "include 'app'", "include 'library1'", "include 'library2'", "include 'spi-plugin'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "tasks.withType(JavaCompile) {", + " options.compilerArgs += '-Adagger.experimentalDaggerErrorMessages=ENABLED'", + "}", + "dependencies {", + " implementation project(':library1')", + " annotationProcessor project(':spi-plugin')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyComponent.java", + "package app;", + "", + "import dagger.Component;", + "import library1.MySubcomponent;", + "", + "@Component", + "public interface MyComponent {", + " MySubcomponent.Creator mySubcomponentCreator();", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + transitiveDependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFiles(library1Files); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation 'javax.inject:javax.inject:1'", + "}") + .addSrcFile( + "MyQualifier.java", + "package library2;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "public @interface MyQualifier {}"); + + // This plugin is used to print output about bindings that we can assert on in tests. + GradleModule.create(projectDir, "spi-plugin") + .addBuildFile( + "plugins {", + " id 'java'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", + " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", + " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", + "}") + .addSrcFile( + "TestBindingGraphPlugin.java", + "package spiplugin;", + "", + "import com.google.auto.service.AutoService;", + "import dagger.model.BindingGraph;", + "import dagger.model.BindingGraph.DependencyEdge;", + "import dagger.model.DependencyRequest;", + "import dagger.spi.BindingGraphPlugin;", + "import dagger.spi.DiagnosticReporter;", + "", + "@AutoService(BindingGraphPlugin.class)", + "public class TestBindingGraphPlugin implements BindingGraphPlugin {", + " @Override", + " public void visitGraph(", + " BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {", + " bindingGraph.entryPointEdges().stream()", + " .map(DependencyEdge::dependencyRequest)", + " .map(DependencyRequest::key)", + " .forEach(key -> System.out.println(\"ENTRY_POINT_REQUEST: \" + key));", + " }", + "}"); + + return GradleRunner.create().withArguments("--stacktrace", "build").withProjectDir(projectDir); + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentScopeTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentScopeTest.java new file mode 100644 index 000000000..82d089504 --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentScopeTest.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2022 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// This is a regression test for https://github.com/google/dagger/issues/3136 +@RunWith(Parameterized.class) +public class TransitiveSubcomponentScopeTest { + @Parameters(name = "{0}") + public static Collection<Object[]> parameters() { + return Arrays.asList(new Object[][] {{"implementation"}, {"api"}}); + } + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + private final String transitiveDependencyType; + + public TransitiveSubcomponentScopeTest(String transitiveDependencyType) { + this.transitiveDependencyType = transitiveDependencyType; + } + + @Test + public void testScopeWithSubcomponent() throws IOException { + BuildResult result; + switch (transitiveDependencyType) { + case "implementation": + result = setupRunner().buildAndFail(); + assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); + assertThat(result.getOutput()) + .contains( + "error: ComponentProcessingStep was unable to process 'app.MyComponent' because " + + "'library2.MySubcomponentScope' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (INTERFACE): library1.MySubcomponent.MySubcomponentModule" + + "\n => element (METHOD): provideScopedInt()" + + "\n => annotation: @library2.MySubcomponentScope"); + break; + case "api": + result = setupRunner().build(); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()) + .contains( + "@Provides @library2.MySubcomponentScope int" + + " library1.MySubcomponent.MySubcomponentModule.provideScopedInt(): SCOPED"); + break; + } + } + + private GradleRunner setupRunner() throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile( + "include 'app'", "include 'library1'", "include 'library2'", "include 'spi-plugin'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "tasks.withType(JavaCompile) {", + " options.compilerArgs += '-Adagger.experimentalDaggerErrorMessages=ENABLED'", + "}", + "dependencies {", + " implementation project(':library1')", + " annotationProcessor project(':spi-plugin')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyComponent.java", + "package app;", + "", + "import dagger.Component;", + "import library1.MySubcomponent;", + "", + "@Component", + "public interface MyComponent {", + " MySubcomponent mySubcomponent();", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + transitiveDependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MySubcomponent.java", + "package library1;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import dagger.Subcomponent;", + "import library2.MySubcomponentScope;", + "", + "@MySubcomponentScope", + "@Subcomponent(modules = MySubcomponent.MySubcomponentModule.class)", + "public abstract class MySubcomponent {", + " public abstract int getScopedInt();", + "", + " @Module", + " public interface MySubcomponentModule {", + " @Provides", + " @MySubcomponentScope", + " static int provideScopedInt() {", + " return 0;", + " }", + " }", + "}"); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation 'javax.inject:javax.inject:1'", + "}") + .addSrcFile( + "MySubcomponentScope.java", + "package library2;", + "", + "import javax.inject.Scope;", + "", + "@Scope", + "public @interface MySubcomponentScope {}"); + + // This plugin is used to print output about bindings that we can assert on in tests. + GradleModule.create(projectDir, "spi-plugin") + .addBuildFile( + "plugins {", + " id 'java'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", + " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", + " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", + "}") + .addSrcFile( + "TestBindingGraphPlugin.java", + "package spiplugin;", + "", + "import com.google.auto.service.AutoService;", + "import dagger.model.BindingGraph;", + "import dagger.spi.BindingGraphPlugin;", + "import dagger.spi.DiagnosticReporter;", + "", + "@AutoService(BindingGraphPlugin.class)", + "public class TestBindingGraphPlugin implements BindingGraphPlugin {", + " @Override", + " public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter" + + " diagnosticReporter) {", + " bindingGraph.bindings().stream()", + " .filter(binding -> binding.scope().isPresent())", + " .forEach(binding -> System.out.println(binding + \": SCOPED\"));", + " bindingGraph.bindings().stream()", + " .filter(binding -> !binding.scope().isPresent())", + " .forEach(binding -> System.out.println(binding + \": UNSCOPED\"));", + " }", + "}"); + + return GradleRunner.create().withArguments("--stacktrace", "build").withProjectDir(projectDir); + } +} diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/build.gradle b/javatests/artifacts/dagger/build.gradle index 7a02482df..89f9efe0b 100644 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/build.gradle +++ b/javatests/artifacts/dagger/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2021 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. @@ -15,27 +15,27 @@ */ buildscript { - ext { - kotlin_version = '1.3.61' - agp_version = "4.2.0-beta04" - } - repositories { - google() - jcenter() - mavenLocal() - } - dependencies { - classpath "com.android.tools.build:gradle:$agp_version" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.google.dagger:hilt-android-gradle-plugin:LOCAL-SNAPSHOT' - } + ext { + dagger_version = "LOCAL-SNAPSHOT" + kotlin_version = "1.5.32" + junit_version = "4.13" + truth_version = "1.0.1" + } } allprojects { - repositories { - google() - jcenter() - mavenCentral() - mavenLocal() + repositories { + mavenCentral() + mavenLocal() + } + + configurations.all { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + if (details.requested.group == 'com.google.dagger' + && "$dagger_version" == 'LOCAL-SNAPSHOT') { + details.useVersion 'LOCAL-SNAPSHOT' + details.because 'LOCAL-SNAPSHOT should act as latest version.' + } } + } } diff --git a/java/dagger/example/gradle/android/simple/gradle/wrapper/gradle-wrapper.jar b/javatests/artifacts/dagger/gradle/wrapper/gradle-wrapper.jar Binary files differindex 5c2d1cf01..5c2d1cf01 100644 --- a/java/dagger/example/gradle/android/simple/gradle/wrapper/gradle-wrapper.jar +++ b/javatests/artifacts/dagger/gradle/wrapper/gradle-wrapper.jar diff --git a/java/dagger/example/gradle/android/simple/gradle/wrapper/gradle-wrapper.properties b/javatests/artifacts/dagger/gradle/wrapper/gradle-wrapper.properties index 4d9ca1649..0f80bbf51 100644 --- a/java/dagger/example/gradle/android/simple/gradle/wrapper/gradle-wrapper.properties +++ b/javatests/artifacts/dagger/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/dagger/example/gradle/android/simple/gradlew b/javatests/artifacts/dagger/gradlew index b0d6d0ab5..b0d6d0ab5 100755 --- a/java/dagger/example/gradle/android/simple/gradlew +++ b/javatests/artifacts/dagger/gradlew diff --git a/javatests/artifacts/dagger/simple/build.gradle b/javatests/artifacts/dagger/java-app/build.gradle index 97c966e50..e709d7db5 100644 --- a/javatests/artifacts/dagger/simple/build.gradle +++ b/javatests/artifacts/dagger/java-app/build.gradle @@ -19,19 +19,14 @@ plugins { id 'application' } -repositories { - mavenCentral() - mavenLocal() -} - java { // Make sure the generated source is compatible with Java 7. sourceCompatibility = JavaVersion.VERSION_1_7 } dependencies { - implementation 'com.google.dagger:dagger:LOCAL-SNAPSHOT' - annotationProcessor 'com.google.dagger:dagger-compiler:LOCAL-SNAPSHOT' + implementation "com.google.dagger:dagger:$dagger_version" + annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" } -mainClassName = 'dagger.simple.SimpleApplication'
\ No newline at end of file +mainClassName = 'app.SimpleApplication'
\ No newline at end of file diff --git a/javatests/artifacts/dagger/simple/src/main/java/dagger/simple/AssistedInjects.java b/javatests/artifacts/dagger/java-app/src/main/java/app/AssistedInjects.java index eb4946fe3..246c541c4 100644 --- a/javatests/artifacts/dagger/simple/src/main/java/dagger/simple/AssistedInjects.java +++ b/javatests/artifacts/dagger/java-app/src/main/java/app/AssistedInjects.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package dagger.simple; +package app; import dagger.Component; import dagger.assisted.Assisted; diff --git a/javatests/artifacts/dagger/simple/src/main/java/dagger/simple/SimpleApplication.java b/javatests/artifacts/dagger/java-app/src/main/java/app/SimpleApplication.java index f13610101..dfe95ca0f 100644 --- a/javatests/artifacts/dagger/simple/src/main/java/dagger/simple/SimpleApplication.java +++ b/javatests/artifacts/dagger/java-app/src/main/java/app/SimpleApplication.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package dagger.simple; +package app; import dagger.Component; import dagger.Module; @@ -44,5 +44,8 @@ public class SimpleApplication { public static void main(String[] args) { Foo foo = DaggerSimpleApplication_SimpleComponent.create().foo(); + + // Execute other classes + AssistedInjects.main(args); } } diff --git a/javatests/artifacts/dagger/kotlin-app/build.gradle b/javatests/artifacts/dagger/kotlin-app/build.gradle new file mode 100644 index 000000000..2f4f7f3c5 --- /dev/null +++ b/javatests/artifacts/dagger/kotlin-app/build.gradle @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 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. + */ + +plugins { + id 'application' + id 'org.jetbrains.kotlin.jvm' version "$kotlin_version" + id 'org.jetbrains.kotlin.kapt' version "$kotlin_version" +} + +java { + // Make sure the generated source is compatible with Java 8. + sourceCompatibility = JavaVersion.VERSION_1_8 +} + +dependencies { + implementation project(path: ':kotlin-app:kotlin-library') + + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "com.google.dagger:dagger:$dagger_version" + kapt "com.google.dagger:dagger-compiler:$dagger_version" + + // This is testImplementation rather than kaptTest because we're actually + // testing the reference to ComponentProcessor. + // See https://github.com/google/dagger/issues/2765 + testImplementation "com.google.dagger:dagger-compiler:$dagger_version" + testImplementation "com.google.truth:truth:$truth_version" + testImplementation "junit:junit:$junit_version" +} + +application { + mainClass = 'app.SimpleApplicationKt' +}
\ No newline at end of file diff --git a/javatests/artifacts/dagger/kotlin-app/kotlin-library/build.gradle b/javatests/artifacts/dagger/kotlin-app/kotlin-library/build.gradle new file mode 100644 index 000000000..954bd9f5a --- /dev/null +++ b/javatests/artifacts/dagger/kotlin-app/kotlin-library/build.gradle @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 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. + */ + +plugins { + id 'org.jetbrains.kotlin.jvm' + id 'org.jetbrains.kotlin.kapt' +} + +java { + // Make sure the generated source is compatible with Java 8. + sourceCompatibility = JavaVersion.VERSION_1_8 +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "com.google.dagger:dagger:$dagger_version" + kapt "com.google.dagger:dagger-compiler:$dagger_version" + + // This is testImplementation rather than kaptTest because we're actually + // testing the reference to ComponentProcessor. + // See https://github.com/google/dagger/issues/2765 + testImplementation "com.google.dagger:dagger-compiler:$dagger_version" + testImplementation "com.google.truth:truth:$truth_version" + testImplementation "junit:junit:$junit_version" +} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/ModelModule.java b/javatests/artifacts/dagger/kotlin-app/kotlin-library/src/main/kotlin/library/MySubcomponent.kt index d933c813e..70e9d3b70 100644 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/ModelModule.java +++ b/javatests/artifacts/dagger/kotlin-app/kotlin-library/src/main/kotlin/library/MySubcomponent.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2021 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. @@ -14,23 +14,22 @@ * limitations under the License. */ -package dagger.hilt.android.example.gradle.simple; +package library -import static android.os.Build.MODEL; +import dagger.BindsInstance +import dagger.Subcomponent -import dagger.Module; -import dagger.Provides; -import dagger.hilt.InstallIn; -import dagger.hilt.components.SingletonComponent; - -@Module -@InstallIn(SingletonComponent.class) -final class ModelModule { - @Provides - @Model - static String provideModel() { - return MODEL; - } +/** + * This subcomponent reproduces a regression in https://github.com/google/dagger/issues/2997. + */ +@Subcomponent +abstract class MySubcomponent { + abstract fun instance(): InstanceType - private ModelModule() {} + @Subcomponent.Factory + interface Factory { + fun create(@BindsInstance instance: InstanceType): MySubcomponent + } } + +class InstanceType diff --git a/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/AssistedInjects.kt b/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/AssistedInjects.kt new file mode 100644 index 000000000..0fe65fd4f --- /dev/null +++ b/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/AssistedInjects.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021 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 app + +import dagger.Component +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import javax.inject.Inject + +// This is a regression test for https://github.com/google/dagger/issues/2309 +/** A simple, skeletal application that defines an assisted inject binding. */ +class AssistedInjects { + @Component + interface MyComponent { + fun fooFactory(): FooFactory + + fun parameterizedFooFactory(): ParameterizedFooFactory<Bar, String> + } + + class Bar @Inject constructor() + + class Foo @AssistedInject constructor(val bar: Bar, @Assisted val str: String) + + @AssistedFactory + interface FooFactory { + fun create(str: String): Foo + } + + class ParameterizedFoo<T1, T2> @AssistedInject constructor(val t1: T1, @Assisted val t2: T2) + + @AssistedFactory + interface ParameterizedFooFactory<T1, T2> { + fun create(t2: T2): ParameterizedFoo<T1, T2> + } + + companion object { + // Called from SimpleApplication.main() + fun main() { + val foo: Foo = DaggerAssistedInjects_MyComponent.create().fooFactory().create("") + + val parameterizedFoo: ParameterizedFoo<Bar, String> = + DaggerAssistedInjects_MyComponent.create().parameterizedFooFactory().create("") + } + } +} diff --git a/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/SimpleApplication.kt b/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/SimpleApplication.kt new file mode 100644 index 000000000..599d7039d --- /dev/null +++ b/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/SimpleApplication.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 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 app + +import dagger.Component +import dagger.Module +import dagger.Provides +import javax.inject.Inject +import javax.inject.Singleton +import library.MySubcomponent + +/** A simple, skeletal application that defines a simple component. */ +class SimpleApplication { + class Foo @Inject constructor() + + @Module + object SimpleModule { + @Provides + fun provideFoo(): Foo { + return Foo() + } + } + + @Singleton + @Component(modules = [SimpleModule::class]) + interface SimpleComponent { + fun foo(): Foo + + // Reproduces a regression in https://github.com/google/dagger/issues/2997. + fun mySubcomponentFactory(): MySubcomponent.Factory + } + + companion object { + fun main() { + val foo: Foo = DaggerSimpleApplication_SimpleComponent.create().foo() + } + } +} + +fun main() { + SimpleApplication.main() + AssistedInjects.main() +} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SimpleGreeter.java b/javatests/artifacts/dagger/kotlin-app/src/test/kotlin/app/ComponentProcessorBuildTest.kt index 6c35600b3..1b050f82d 100644 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SimpleGreeter.java +++ b/javatests/artifacts/dagger/kotlin-app/src/test/kotlin/app/ComponentProcessorBuildTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2021 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. @@ -14,23 +14,23 @@ * limitations under the License. */ -package dagger.hilt.android.example.gradle.simple; +package app -import android.app.Activity; -import javax.inject.Inject; +import com.google.common.truth.Truth.assertThat +import dagger.internal.codegen.ComponentProcessor +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 -/** A class that returns a greeting for {@link SimpleActivity}. */ -final class SimpleGreeter { - private final Activity activity; - private final String userName; +@RunWith(JUnit4::class) +class ComponentProcessorBuildTest { - @Inject - SimpleGreeter(Activity activity, @UserName String userName) { - this.activity = activity; - this.userName = userName; - } + // This is a regression test for https://github.com/google/dagger/issues/2765 + // to make sure ComponentProcessor builds in kotlin. + @Test + fun testComponentProcessor() { + val processor = ComponentProcessor.forTesting() - public String greet() { - return activity.getResources().getString(R.string.welcome, userName); + assertThat(processor).isNotNull() } } diff --git a/javatests/artifacts/dagger/settings.gradle b/javatests/artifacts/dagger/settings.gradle new file mode 100644 index 000000000..6ba1baebe --- /dev/null +++ b/javatests/artifacts/dagger/settings.gradle @@ -0,0 +1,8 @@ +rootProject.name = 'Dagger Apps' +include ':build-tests' +include ':java-app' +include ':kotlin-app' +include ':kotlin-app:kotlin-library' +include ':transitive-annotation-app' +include ':transitive-annotation-app:library1' +include ':transitive-annotation-app:library2' diff --git a/javatests/artifacts/dagger/simple/gradle/wrapper/gradle-wrapper.jar b/javatests/artifacts/dagger/simple/gradle/wrapper/gradle-wrapper.jar Binary files differdeleted file mode 100644 index 5c2d1cf01..000000000 --- a/javatests/artifacts/dagger/simple/gradle/wrapper/gradle-wrapper.jar +++ /dev/null diff --git a/javatests/artifacts/dagger/simple/gradle/wrapper/gradle-wrapper.properties b/javatests/artifacts/dagger/simple/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 4d9ca1649..000000000 --- a/javatests/artifacts/dagger/simple/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/javatests/artifacts/dagger/simple/gradlew b/javatests/artifacts/dagger/simple/gradlew deleted file mode 100755 index b0d6d0ab5..000000000 --- a/javatests/artifacts/dagger/simple/gradlew +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or 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. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/javatests/artifacts/dagger/transitive-annotation-app/build.gradle b/javatests/artifacts/dagger/transitive-annotation-app/build.gradle new file mode 100644 index 000000000..22933a4a3 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/build.gradle @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 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. + */ + +plugins { + id 'java' + id 'application' +} + +java { + // Make sure the generated source is compatible with Java 7. + sourceCompatibility = JavaVersion.VERSION_1_7 +} + +dependencies { + implementation project(":transitive-annotation-app:library1") + implementation "com.google.dagger:dagger:$dagger_version" + annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" + + testImplementation "junit:junit:$junit_version" + testImplementation "com.google.truth:truth:$truth_version" +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/build.gradle b/javatests/artifacts/dagger/transitive-annotation-app/library1/build.gradle new file mode 100644 index 000000000..2126f0923 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/build.gradle @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 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. + */ + +plugins { + id 'java' + id 'java-library' +} + +java { + // Make sure the generated source is compatible with Java 7. + sourceCompatibility = JavaVersion.VERSION_1_7 +} + +dependencies { + implementation project(":transitive-annotation-app:library2") + implementation "com.google.dagger:dagger:$dagger_version" + annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/AssistedFoo.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/AssistedFoo.java new file mode 100644 index 000000000..8377d1c24 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/AssistedFoo.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2022 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 library1; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import javax.inject.Inject; +import library2.MyTransitiveAnnotation; +import library2.MyTransitiveType; + +/** + * A class used to test that Dagger won't fail when non-dagger related annotations cannot be + * resolved. + * + * <p>During the compilation of {@code :app}, {@link MyTransitiveAnnotation} will no longer be on + * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath + */ +@MyTransitiveAnnotation +@MyAnnotation(MyTransitiveType.VALUE) +@MyOtherAnnotation(MyTransitiveType.class) +public final class AssistedFoo extends FooBase { + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerField; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Inject + @MyQualifier + Dep daggerField; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + AssistedFoo( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + super(nonDaggerParameter); + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @AssistedInject + AssistedFoo( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Assisted + int i, + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @MyQualifier + Dep dep) { + super(dep); + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Inject + void daggerMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @MyQualifier + Dep dep) {} + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @AssistedFactory + public interface Factory { + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + AssistedFoo create( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + int i); + } +} diff --git a/java/dagger/internal/MemoizedSentinel.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/Dep.java index dd24dcd85..fa64e3428 100644 --- a/java/dagger/internal/MemoizedSentinel.java +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/Dep.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Dagger Authors. + * Copyright (C) 2022 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. @@ -14,7 +14,6 @@ * limitations under the License. */ -package dagger.internal; +package library1; -/** A sentinel used to memoize a scoped binding in a component. */ -public final class MemoizedSentinel {} +public final class Dep {} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/Foo.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/Foo.java new file mode 100644 index 000000000..062acbe5a --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/Foo.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 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 library1; + +import javax.inject.Inject; +import javax.inject.Singleton; +import library2.MyTransitiveAnnotation; +import library2.MyTransitiveType; + +/** + * A class used to test that Dagger won't fail when non-dagger related annotations cannot be + * resolved. + * + * <p>During the compilation of {@code :app}, {@link MyTransitiveAnnotation} will no longer be on + * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath + */ +@Singleton +@MyTransitiveAnnotation +@MyAnnotation(MyTransitiveType.VALUE) +@MyOtherAnnotation(MyTransitiveType.class) +public final class Foo extends FooBase { + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerField; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Inject + @MyQualifier + Dep daggerField; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + Foo( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + super(nonDaggerParameter); + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Inject + Foo( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @MyQualifier + Dep dep) { + super(dep); + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Inject + void daggerMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @MyQualifier + Dep dep) {} +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/FooBase.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/FooBase.java new file mode 100644 index 000000000..ad113dd91 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/FooBase.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 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 library1; + +import javax.inject.Inject; +import library2.MyTransitiveBaseAnnotation; +import library2.MyTransitiveType; + +/** A baseclass for {@link Foo}. */ +@MyTransitiveBaseAnnotation +@MyAnnotation(MyTransitiveType.VALUE) +@MyOtherAnnotation(MyTransitiveType.class) +public class FooBase { + @MyTransitiveBaseAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + int baseNonDaggerField; + + @MyTransitiveBaseAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Inject + @MyQualifier + Dep baseDaggerField; + + @MyTransitiveBaseAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + FooBase( + @MyTransitiveBaseAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) {} + + @MyTransitiveBaseAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Inject + FooBase( + @MyTransitiveBaseAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @MyQualifier + Dep dep) {} + + @MyTransitiveBaseAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + void baseNonDaggerMethod( + @MyTransitiveBaseAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + int i) {} + + @MyTransitiveBaseAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Inject + void baseDaggerMethod( + @MyTransitiveBaseAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @MyQualifier + Dep dep) {} +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyAnnotation.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyAnnotation.java new file mode 100644 index 000000000..58ba4e7f5 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyAnnotation.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 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 library1; + +/** An annotation that is a direct dependency of the app. */ +public @interface MyAnnotation { + int value(); +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyBaseComponent.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyBaseComponent.java new file mode 100644 index 000000000..d647f54f6 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyBaseComponent.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2022 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 library1; + +import library2.MyTransitiveAnnotation; +import library2.MyTransitiveType; + +/** + * A class used to test that Dagger won't fail on unresolvable transitive types used in non-dagger + * related elements and annotations. + */ +// @MyTransitiveAnnotation: Not yet supported +@MyAnnotation(MyTransitiveType.VALUE) +@MyOtherAnnotation(MyTransitiveType.class) +public abstract class MyBaseComponent { + @MyQualifier + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract MyComponentModule.UnscopedQualifiedBindsType unscopedQualifiedBindsTypeBase(); + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract MyComponentModule.UnscopedUnqualifiedBindsType unscopedUnqualifiedBindsTypeBase(); + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract void injectFooBase( + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) @MyOtherAnnotation(MyTransitiveType.class) Foo binding); + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract static class Factory { + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract MyBaseComponent create( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyComponentModule myComponentModule, + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyComponentDependency myComponentDependency); + + // Non-dagger factory code + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyTransitiveType nonDaggerField = null; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static MyTransitiveType nonDaggerStaticField = null; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyTransitiveType nonDaggerMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static MyTransitiveType nonDaggerStaticMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } + } + + // Non-dagger code + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyTransitiveType nonDaggerField = null; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static MyTransitiveType nonDaggerStaticField = null; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyTransitiveType nonDaggerMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static MyTransitiveType nonDaggerStaticMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyComponentDependency.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyComponentDependency.java new file mode 100644 index 000000000..bb17021b7 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyComponentDependency.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2022 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 library1; + +import library2.MyTransitiveAnnotation; +import library2.MyTransitiveType; + +/** + * A class used to test that Dagger won't fail on unresolvable transitive types used in non-dagger + * related elements and annotations. + */ +// @MyTransitiveAnnotation: Not yet supported +@MyAnnotation(MyTransitiveType.VALUE) +@MyOtherAnnotation(MyTransitiveType.class) +public final class MyComponentDependency { + private final MyComponentDependencyBinding qualifiedMyComponentDependencyBinding = + new MyComponentDependencyBinding(); + private final MyComponentDependencyBinding unqualifiedMyComponentDependencyBinding = + new MyComponentDependencyBinding(); + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyComponentDependency() {} + + @MyQualifier + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyComponentDependencyBinding qualifiedMyComponentDependencyBinding() { + return qualifiedMyComponentDependencyBinding; + } + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyComponentDependencyBinding unqualifiedMyComponentDependencyBinding() { + return unqualifiedMyComponentDependencyBinding; + } + + // Non-dagger code + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyTransitiveType nonDaggerField = null; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static MyTransitiveType nonDaggerStaticField = null; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyTransitiveType nonDaggerMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static MyTransitiveType nonDaggerStaticMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyComponentDependencyBinding.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyComponentDependencyBinding.java new file mode 100644 index 000000000..c5d6bc917 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyComponentDependencyBinding.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 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 library1; + +/** Used as a binding in {@link MyComponentDependency}. */ +public final class MyComponentDependencyBinding {} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyComponentModule.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyComponentModule.java new file mode 100644 index 000000000..2c7a36334 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyComponentModule.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2021 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 library1; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import javax.inject.Singleton; +import library2.MyTransitiveAnnotation; +import library2.MyTransitiveType; + +/** + * A class used to test that Dagger won't fail when non-dagger related annotations cannot be + * resolved. + * + * <p>During the compilation of {@code :app}, {@link MyTransitiveAnnotation} will no longer be on + * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath + */ +@MyTransitiveAnnotation +@MyAnnotation(MyTransitiveType.VALUE) +@MyOtherAnnotation(MyTransitiveType.class) +@Module(includes = {MyComponentModule.MyAbstractModule.class}) +public final class MyComponentModule { + // Define bindings for each configuration: Scoped/Unscoped, Qualified/UnQualified, Provides/Binds + public static class ScopedQualifiedBindsType {} + public static final class ScopedQualifiedProvidesType extends ScopedQualifiedBindsType {} + public static class ScopedUnqualifiedBindsType {} + public static final class ScopedUnqualifiedProvidesType extends ScopedUnqualifiedBindsType {} + public static class UnscopedQualifiedBindsType {} + public static final class UnscopedQualifiedProvidesType extends UnscopedQualifiedBindsType {} + public static class UnscopedUnqualifiedBindsType {} + public static final class UnscopedUnqualifiedProvidesType extends UnscopedUnqualifiedBindsType {} + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Provides + @Singleton + @MyQualifier + ScopedQualifiedProvidesType scopedQualifiedProvidesType( + @MyQualifier + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + Dep dep) { + return new ScopedQualifiedProvidesType(); + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Provides + @Singleton + ScopedUnqualifiedProvidesType scopedUnqualifiedProvidesType( + @MyQualifier + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + Dep dep) { + return new ScopedUnqualifiedProvidesType(); + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Provides + @MyQualifier + UnscopedQualifiedProvidesType unscopedQualifiedProvidesType( + @MyQualifier + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + Dep dep) { + return new UnscopedQualifiedProvidesType(); + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Provides + UnscopedUnqualifiedProvidesType unscopedUnqualifiedProvidesType( + @MyQualifier + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + Dep dep) { + return new UnscopedUnqualifiedProvidesType(); + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Module + interface MyAbstractModule { + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Binds + @Singleton + @MyQualifier + ScopedQualifiedBindsType scopedQualifiedBindsType( + // @MyTransitiveAnnotation: Not yet supported + @MyQualifier + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + ScopedQualifiedProvidesType scopedQualifiedProvidesType); + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Binds + @Singleton + ScopedUnqualifiedBindsType scopedUnqualifiedBindsType( + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) @MyOtherAnnotation(MyTransitiveType.class) + ScopedUnqualifiedProvidesType scopedUnqualifiedProvidesType); + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Binds + @MyQualifier + UnscopedQualifiedBindsType unscopedQualifiedBindsType( + // @MyTransitiveAnnotation: Not yet supported + @MyQualifier + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + UnscopedQualifiedProvidesType unscopedQualifiedProvidesType); + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Binds + UnscopedUnqualifiedBindsType unscopedUnqualifiedBindsType( + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) @MyOtherAnnotation(MyTransitiveType.class) + UnscopedUnqualifiedProvidesType unscopedUnqualifiedProvidesType); + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Provides + @MyQualifier + Dep provideQualifiedDep() { + return new Dep(); + } + + // Provide an unqualified Dep to ensure that if we accidentally drop the qualifier + // we'll get a runtime exception. + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Provides + Dep provideDep() { + throw new UnsupportedOperationException(); + } + + // Non-Dagger elements + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + private Dep dep; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + private MyTransitiveType nonDaggerField; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyComponentModule( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + Dep dep) { + this.dep = dep; + this.nonDaggerField = new MyTransitiveType(); + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + static MyTransitiveType nonDaggerStaticMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyOtherAnnotation.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyOtherAnnotation.java new file mode 100644 index 000000000..c1cb6ae95 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyOtherAnnotation.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 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 library1; + +/** An annotation that is a direct dependency of the app. */ +public @interface MyOtherAnnotation { + Class<?> value(); +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyQualifier.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyQualifier.java new file mode 100644 index 000000000..c08ac9864 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MyQualifier.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 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 library1; + +import javax.inject.Qualifier; + +@Qualifier +public @interface MyQualifier {} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentBinding.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentBinding.java new file mode 100644 index 000000000..61bb3f7b3 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentBinding.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 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 library1; + +/** A simple binding that needs to be passed in when creating this component. */ +public final class MySubcomponentBinding {} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentModule.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentModule.java new file mode 100644 index 000000000..da8de7bea --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentModule.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 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 library1; + +import dagger.Module; +import library2.MyTransitiveAnnotation; +import library2.MyTransitiveType; + +/** A simple module that needs to be passed in when creating this component. */ +@MyTransitiveAnnotation +@MyAnnotation(MyTransitiveType.VALUE) +@MyOtherAnnotation(MyTransitiveType.class) +@Module +public final class MySubcomponentModule { + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MySubcomponentModule( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + int i) {} +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentScope.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentScope.java new file mode 100644 index 000000000..e76b0b658 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentScope.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 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 library1; + +import javax.inject.Scope; + +/** A scope for {@link MySubcomponent}. */ +@Scope +public @interface MySubcomponentScope {} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentWithBuilder.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentWithBuilder.java new file mode 100644 index 000000000..456eb2539 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentWithBuilder.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2022 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 library1; + +import dagger.BindsInstance; +import dagger.Subcomponent; +import library2.MyTransitiveAnnotation; +import library2.MyTransitiveType; + +/** + * A class used to test that Dagger won't fail when non-dagger related annotations cannot be + * resolved. + * + * <p>During the compilation of {@code :app}, {@link MyTransitiveAnnotation} will no longer be on + * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath + */ +// @MyTransitiveAnnotation: Not yet supported +@MyAnnotation(MyTransitiveType.VALUE) +@MyOtherAnnotation(MyTransitiveType.class) +@MySubcomponentScope +@Subcomponent(modules = MySubcomponentModule.class) +public abstract class MySubcomponentWithBuilder { + @MyQualifier + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract MySubcomponentBinding qualifiedMySubcomponentBinding(); + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract MySubcomponentBinding unqualifiedMySubcomponentBinding(); + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract void injectFoo( + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) @MyOtherAnnotation(MyTransitiveType.class) Foo foo); + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Subcomponent.Builder + public abstract static class Builder { + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract Builder mySubcomponentModule( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MySubcomponentModule mySubcomponentModule); + + @BindsInstance + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract Builder qualifiedMySubcomponentBinding( + @MyQualifier + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MySubcomponentBinding subcomponentBinding); + + @BindsInstance + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract Builder unqualifiedMySubcomponentBinding( + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) @MyOtherAnnotation(MyTransitiveType.class) + MySubcomponentBinding subcomponentBinding); + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract MySubcomponentWithBuilder build(); + + // Non-dagger code + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public String nonDaggerField = ""; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static String nonDaggerStaticField = ""; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public void nonDaggerMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + String str) {} + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static void nonDaggerStaticMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + String str) {} + } + + // Non-dagger code + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyTransitiveType nonDaggerField = null; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static MyTransitiveType nonDaggerStaticField = null; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyTransitiveType nonDaggerMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static MyTransitiveType nonDaggerStaticMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentWithFactory.java b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentWithFactory.java new file mode 100644 index 000000000..f3c6f930a --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentWithFactory.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2022 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 library1; + +import dagger.BindsInstance; +import dagger.Subcomponent; +import library2.MyTransitiveAnnotation; +import library2.MyTransitiveType; + +/** + * A class used to test that Dagger won't fail when non-dagger related annotations cannot be + * resolved. + * + * <p>During the compilation of {@code :app}, {@link MyTransitiveAnnotation} will no longer be on + * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath + */ +// @MyTransitiveAnnotation: Not yet supported +@MyAnnotation(MyTransitiveType.VALUE) +@MyOtherAnnotation(MyTransitiveType.class) +@MySubcomponentScope +@Subcomponent(modules = MySubcomponentModule.class) +public abstract class MySubcomponentWithFactory { + // @MyTransitiveAnnotation: Not yet supported + @MyQualifier + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract MySubcomponentBinding qualifiedMySubcomponentBinding(); + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract MySubcomponentBinding unqualifiedMySubcomponentBinding(); + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract void injectFoo( + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) @MyOtherAnnotation(MyTransitiveType.class) Foo foo); + + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + @Subcomponent.Factory + public abstract static class Factory { + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public abstract MySubcomponentWithFactory create( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MySubcomponentModule mySubcomponentModule, + @BindsInstance + @MyQualifier + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MySubcomponentBinding qualifiedSubcomponentBinding, + @BindsInstance + // @MyTransitiveAnnotation: Not yet supported + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MySubcomponentBinding unqualifiedSubcomponentBinding); + + // Non-dagger code + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyTransitiveType nonDaggerField = null; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static MyTransitiveType nonDaggerStaticField = null; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyTransitiveType nonDaggerMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static MyTransitiveType nonDaggerStaticMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } + } + + // Non-dagger code + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyTransitiveType nonDaggerField = null; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static MyTransitiveType nonDaggerStaticField = null; + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public MyTransitiveType nonDaggerMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } + + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + public static MyTransitiveType nonDaggerStaticMethod( + @MyTransitiveAnnotation + @MyAnnotation(MyTransitiveType.VALUE) + @MyOtherAnnotation(MyTransitiveType.class) + MyTransitiveType nonDaggerParameter) { + return nonDaggerParameter; + } +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library2/build.gradle b/javatests/artifacts/dagger/transitive-annotation-app/library2/build.gradle new file mode 100644 index 000000000..b4d45e475 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library2/build.gradle @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 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. + */ + +plugins { + id 'java' + id 'java-library' +} + +java { + // Make sure the generated source is compatible with Java 7. + sourceCompatibility = JavaVersion.VERSION_1_7 +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveAnnotation.java b/javatests/artifacts/dagger/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveAnnotation.java new file mode 100644 index 000000000..5f1f4bc8f --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveAnnotation.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 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 library2; + +/** A simple annotation that is a transitive dependency of the app. */ +public @interface MyTransitiveAnnotation {} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveBaseAnnotation.java b/javatests/artifacts/dagger/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveBaseAnnotation.java new file mode 100644 index 000000000..dcc6739da --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveBaseAnnotation.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 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 library2; + +/** A simple annotation that is a transitive dependency of the app. */ +public @interface MyTransitiveBaseAnnotation {} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveType.java b/javatests/artifacts/dagger/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveType.java new file mode 100644 index 000000000..c6ecd5d93 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveType.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 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 library2; + + +/** A class that is a transitive dependency of the app. */ +public final class MyTransitiveType { + public static final int VALUE = 3; +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/src/main/java/app/MyComponent.java b/javatests/artifacts/dagger/transitive-annotation-app/src/main/java/app/MyComponent.java new file mode 100644 index 000000000..ee55d3a48 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/src/main/java/app/MyComponent.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 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 app; + +import dagger.Component; +import javax.inject.Singleton; +import library1.AssistedFoo; +import library1.Foo; +import library1.MyBaseComponent; +import library1.MyComponentDependency; +import library1.MyComponentDependencyBinding; +import library1.MyComponentModule; +import library1.MyQualifier; +import library1.MySubcomponentWithBuilder; +import library1.MySubcomponentWithFactory; + +@Singleton +@Component(dependencies = MyComponentDependency.class, modules = MyComponentModule.class) +abstract class MyComponent extends MyBaseComponent { + abstract Foo foo(); + + abstract AssistedFoo.Factory assistedFooFactory(); + + @MyQualifier + abstract MyComponentModule.ScopedQualifiedBindsType scopedQualifiedBindsType(); + + abstract MyComponentModule.ScopedUnqualifiedBindsType scopedUnqualifiedBindsType(); + + @MyQualifier + abstract MyComponentModule.UnscopedQualifiedBindsType unscopedQualifiedBindsType(); + + abstract MyComponentModule.UnscopedUnqualifiedBindsType unscopedUnqualifiedBindsType(); + + @MyQualifier + abstract MyComponentModule.ScopedQualifiedProvidesType scopedQualifiedProvidesType(); + + abstract MyComponentModule.ScopedUnqualifiedProvidesType scopedUnqualifiedProvidesType(); + + @MyQualifier + abstract MyComponentModule.UnscopedQualifiedProvidesType unscopedQualifiedProvidesType(); + + abstract MyComponentModule.UnscopedUnqualifiedProvidesType unscopedUnqualifiedProvidesType(); + + abstract MySubcomponentWithFactory.Factory mySubcomponentWithFactory(); + + abstract MySubcomponentWithBuilder.Builder mySubcomponentWithBuilder(); + + @MyQualifier + abstract MyComponentDependencyBinding qualifiedMyComponentDependencyBinding(); + + abstract MyComponentDependencyBinding unqualifiedMyComponentDependencyBinding(); + + @Component.Factory + abstract static class Factory extends MyBaseComponent.Factory { + public abstract MyComponent create( + MyComponentModule myComponentModule, + MyComponentDependency myComponentDependency); + } +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/src/test/java/app/MyComponentTest.java b/javatests/artifacts/dagger/transitive-annotation-app/src/test/java/app/MyComponentTest.java new file mode 100644 index 000000000..438d5db60 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/src/test/java/app/MyComponentTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2021 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 app; + +import static com.google.common.truth.Truth.assertThat; + +import library1.Dep; +import library1.MyComponentDependency; +import library1.MyComponentModule; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class MyComponentTest { + private MyComponent component; + + @Before + public void setup() { + component = DaggerMyComponent.factory() + .create(new MyComponentModule(new Dep()), new MyComponentDependency()); + } + + @Test + public void testFooIsScoped() { + assertThat(component.foo()).isEqualTo(component.foo()); + } + + @Test + public void testAssistedFoo() { + assertThat(component.assistedFooFactory().create(5)).isNotNull(); + } + + @Test + public void testScopedQualifiedBindsTypeIsScoped() { + assertThat(component.scopedQualifiedBindsType()) + .isEqualTo(component.scopedQualifiedBindsType()); + } + + @Test + public void testScopedUnqualifiedBindsTypeIsScoped() { + assertThat(component.scopedUnqualifiedBindsType()) + .isEqualTo(component.scopedUnqualifiedBindsType()); + } + + @Test + public void testUnscopedQualifiedBindsTypeIsNotScoped() { + assertThat(component.unscopedQualifiedBindsType()) + .isNotEqualTo(component.unscopedQualifiedBindsType()); + } + + @Test + public void testUnscopedUnqualifiedBindsTypeIsNotScoped() { + assertThat(component.unscopedUnqualifiedBindsType()) + .isNotEqualTo(component.unscopedUnqualifiedBindsType()); + } + + @Test + public void testScopedQualifiedProvidesTypeIsScoped() { + assertThat(component.scopedQualifiedProvidesType()) + .isEqualTo(component.scopedQualifiedProvidesType()); + } + + @Test + public void testScopedUnqualifiedProvidesTypeIsScoped() { + assertThat(component.scopedUnqualifiedProvidesType()) + .isEqualTo(component.scopedUnqualifiedProvidesType()); + } + + @Test + public void testUnscopedQualifiedProvidesTypeIsNotScoped() { + assertThat(component.unscopedQualifiedProvidesType()) + .isNotEqualTo(component.unscopedQualifiedProvidesType()); + } + + @Test + public void testUnscopedUnqualifiedProvidesTypeIsNotScoped() { + assertThat(component.unscopedUnqualifiedProvidesType()) + .isNotEqualTo(component.unscopedUnqualifiedProvidesType()); + } + + @Test + public void testMyComponentDependencyBinding() { + assertThat(component.qualifiedMyComponentDependencyBinding()) + .isNotEqualTo(component.unqualifiedMyComponentDependencyBinding()); + } +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/src/test/java/app/MySubcomponentWithBuilderTest.java b/javatests/artifacts/dagger/transitive-annotation-app/src/test/java/app/MySubcomponentWithBuilderTest.java new file mode 100644 index 000000000..3db241b43 --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/src/test/java/app/MySubcomponentWithBuilderTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 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 app; + +import static com.google.common.truth.Truth.assertThat; + +import library1.Dep; +import library1.MyComponentDependency; +import library1.MyComponentModule; +import library1.MySubcomponentBinding; +import library1.MySubcomponentModule; +import library1.MySubcomponentWithBuilder; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class MySubcomponentWithBuilderTest { + private MySubcomponentWithBuilder subcomponentWithBuilder; + + @Before + public void setup() { + subcomponentWithBuilder = + DaggerMyComponent.factory() + .create(new MyComponentModule(new Dep()), new MyComponentDependency()) + .mySubcomponentWithBuilder() + .mySubcomponentModule(new MySubcomponentModule(3)) + .qualifiedMySubcomponentBinding(new MySubcomponentBinding()) + .unqualifiedMySubcomponentBinding(new MySubcomponentBinding()) + .build(); + } + + // Test that the qualified and unqualified bindings are two separate objects + @Test + public void testMySubcomponentBinding() { + assertThat(subcomponentWithBuilder.qualifiedMySubcomponentBinding()) + .isNotEqualTo(subcomponentWithBuilder.unqualifiedMySubcomponentBinding()); + } +} diff --git a/javatests/artifacts/dagger/transitive-annotation-app/src/test/java/app/MySubcomponentWithFactoryTest.java b/javatests/artifacts/dagger/transitive-annotation-app/src/test/java/app/MySubcomponentWithFactoryTest.java new file mode 100644 index 000000000..a45dcc75f --- /dev/null +++ b/javatests/artifacts/dagger/transitive-annotation-app/src/test/java/app/MySubcomponentWithFactoryTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 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 app; + +import static com.google.common.truth.Truth.assertThat; + +import library1.Dep; +import library1.MyComponentDependency; +import library1.MyComponentModule; +import library1.MySubcomponentBinding; +import library1.MySubcomponentModule; +import library1.MySubcomponentWithFactory; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class MySubcomponentWithFactoryTest { + private MySubcomponentWithFactory subcomponentWithFactory; + + @Before + public void setup() { + subcomponentWithFactory = + DaggerMyComponent.factory() + .create(new MyComponentModule(new Dep()), new MyComponentDependency()) + .mySubcomponentWithFactory() + .create( + new MySubcomponentModule(1), + new MySubcomponentBinding(), + new MySubcomponentBinding()); + } + + // Test that the qualified and unqualified bindings are two separate objects + @Test + public void testMySubcomponentBinding() { + assertThat(subcomponentWithFactory.qualifiedMySubcomponentBinding()) + .isNotEqualTo(subcomponentWithFactory.unqualifiedMySubcomponentBinding()); + } +} diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/app/build.gradle b/javatests/artifacts/hilt-android/gradleConfigCache/app/build.gradle deleted file mode 100644 index 4f21d0a38..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/app/build.gradle +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' -apply plugin: 'dagger.hilt.android.plugin' - -android { - compileSdkVersion 30 - buildToolsVersion "30.0.2" - - defaultConfig { - applicationId "dagger.hilt.android.gradleConfigCache" - minSdkVersion 15 - targetSdkVersion 30 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "dagger.hilt.android.gradleConfigCache.TestRunner" - } - compileOptions { - sourceCompatibility 1.8 - targetCompatibility 1.8 - } - kotlinOptions { - jvmTarget = '1.8' - } - lintOptions { - checkReleaseBuilds = false - } - testOptions { - unitTests.includeAndroidResources = true - } -} - -hilt { - enableTransformForLocalTests = true - enableExperimentalClasspathAggregation = true -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.activity:activity-ktx:1.1.0' - implementation 'androidx.multidex:multidex:2.0.0' - implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT' - kapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT' - - testImplementation 'junit:junit:4.13' - testImplementation 'androidx.test.ext:junit:1.1.2' - testImplementation 'androidx.test:runner:1.3.0' - testImplementation 'org.robolectric:robolectric:4.5-alpha-3' - testImplementation 'com.google.dagger:hilt-android-testing:LOCAL-SNAPSHOT' - kaptTest 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT' - - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test:runner:1.3.0' - androidTestImplementation 'com.google.dagger:hilt-android-testing:LOCAL-SNAPSHOT' - kaptAndroidTest 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT' -} diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/androidTest/java/dagger/hilt/android/gradleConfigCache/EmulatorTest.kt b/javatests/artifacts/hilt-android/gradleConfigCache/app/src/androidTest/java/dagger/hilt/android/gradleConfigCache/EmulatorTest.kt deleted file mode 100644 index 9099db42e..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/androidTest/java/dagger/hilt/android/gradleConfigCache/EmulatorTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.gradleConfigCache - -import androidx.test.core.app.ActivityScenario -import androidx.test.ext.junit.runners.AndroidJUnit4 -import dagger.hilt.android.testing.HiltAndroidRule -import dagger.hilt.android.testing.HiltAndroidTest -import org.junit.Assert.assertNotNull -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@HiltAndroidTest -@RunWith(AndroidJUnit4::class) -class EmulatorTest { - - @get:Rule - val rule = HiltAndroidRule(this) - - @Test - fun testFooInjected() { - ActivityScenario.launch(MainActivity::class.java).use { - it.onActivity { activity -> - assertNotNull(activity.foo) - } - } - } -} diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/androidTest/java/dagger/hilt/android/gradleConfigCache/TestRunner.kt b/javatests/artifacts/hilt-android/gradleConfigCache/app/src/androidTest/java/dagger/hilt/android/gradleConfigCache/TestRunner.kt deleted file mode 100644 index 9fce7cdb0..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/androidTest/java/dagger/hilt/android/gradleConfigCache/TestRunner.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.gradleConfigCache - -import android.app.Application -import android.content.Context -import androidx.test.runner.AndroidJUnitRunner -import dagger.hilt.android.testing.HiltTestApplication - -class TestRunner : AndroidJUnitRunner() { - override fun newApplication(cl: ClassLoader, appName: String, context: Context): Application { - return super.newApplication(cl, HiltTestApplication::class.java.name, context) - } -} diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/AndroidManifest.xml b/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/AndroidManifest.xml deleted file mode 100644 index ce1fb7129..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2020 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. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="dagger.hilt.android.gradleConfigCache"> - - <application - android:name=".App" - android:allowBackup="true" - android:label="@string/app_name" - android:supportsRtl="true" - android:theme="@style/Theme.AppCompat.Light"> - <activity android:name=".MainActivity" android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - </application> -</manifest> diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/java/dagger/hilt/android/gradleConfigCache/Foo.kt b/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/java/dagger/hilt/android/gradleConfigCache/Foo.kt deleted file mode 100644 index 969e183eb..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/java/dagger/hilt/android/gradleConfigCache/Foo.kt +++ /dev/null @@ -1,5 +0,0 @@ -package dagger.hilt.android.gradleConfigCache - -import javax.inject.Inject - -class Foo @Inject constructor() diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/test/java/dagger/hilt/android/gradleConfigCache/LocalTest.kt b/javatests/artifacts/hilt-android/gradleConfigCache/app/src/test/java/dagger/hilt/android/gradleConfigCache/LocalTest.kt deleted file mode 100644 index 527d88739..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/test/java/dagger/hilt/android/gradleConfigCache/LocalTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.gradleConfigCache - -import androidx.test.core.app.ActivityScenario -import androidx.test.ext.junit.runners.AndroidJUnit4 -import dagger.hilt.android.testing.HiltAndroidRule -import dagger.hilt.android.testing.HiltAndroidTest -import dagger.hilt.android.testing.HiltTestApplication -import org.junit.Assert.assertNotNull -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.annotation.Config - -@HiltAndroidTest -@RunWith(AndroidJUnit4::class) -@Config(application = HiltTestApplication::class) -class LocalTest { - - @get:Rule - val rule = HiltAndroidRule(this) - - @Test - fun testFooInjected() { - ActivityScenario.launch(MainActivity::class.java).use { - it.onActivity { activity -> - assertNotNull(activity.foo) - } - } - } -} diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/build.gradle b/javatests/artifacts/hilt-android/gradleConfigCache/build.gradle deleted file mode 100644 index de0566c5d..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/build.gradle +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -buildscript { - ext { - kotlin_version = '1.4.20' - agp_version = "4.2.0-beta04" - } - repositories { - google() - jcenter() - mavenLocal() - } - dependencies { - classpath "com.android.tools.build:gradle:$agp_version" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.google.dagger:hilt-android-gradle-plugin:LOCAL-SNAPSHOT' - } -} - -allprojects { - repositories { - google() - jcenter() - mavenCentral() - mavenLocal() - } - // TODO(bcorso): Consider organizing all projects under a single project - // that share a build.gradle configuration to reduce the copy-paste. - // Local tests: Adds logs for individual local tests - tasks.withType(Test) { - testLogging { - exceptionFormat "full" - showCauses true - showExceptions true - showStackTraces true - showStandardStreams true - events = ["passed", "skipped", "failed", "standardOut", "standardError"] - } - } -} - -// Instrumentation tests: Combines test reports for all modules -apply plugin: 'android-reporting'
\ No newline at end of file diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/gradle.properties b/javatests/artifacts/hilt-android/gradleConfigCache/gradle.properties deleted file mode 100644 index 1813493a2..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/gradle.properties +++ /dev/null @@ -1,8 +0,0 @@ -android.useAndroidX=true -android.enableJetifier=true - -# Error out if an issue is found that disallows the configuration cache. -# These options along with this app being built in presubmit helps us cache -# changes that would cause config cache to be disabled via the HiltGradlePlugin. -org.gradle.unsafe.configuration-cache=ERROR -org.gradle.unsafe.configuration-cache.max-problems=0 diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/gradle/wrapper/gradle-wrapper.jar b/javatests/artifacts/hilt-android/gradleConfigCache/gradle/wrapper/gradle-wrapper.jar Binary files differdeleted file mode 100644 index 62d4c0535..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/gradle/wrapper/gradle-wrapper.jar +++ /dev/null diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/gradle/wrapper/gradle-wrapper.properties b/javatests/artifacts/hilt-android/gradleConfigCache/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 4d9ca1649..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/gradlew b/javatests/artifacts/hilt-android/gradleConfigCache/gradlew deleted file mode 100755 index fbd7c5158..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/gradlew +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or 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 -# -# https://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. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -exec "$JAVACMD" "$@" diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/gradlew.bat b/javatests/artifacts/hilt-android/gradleConfigCache/gradlew.bat deleted file mode 100644 index a9f778a7a..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/gradlew.bat +++ /dev/null @@ -1,104 +0,0 @@ -@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/settings.gradle b/javatests/artifacts/hilt-android/gradleConfigCache/settings.gradle deleted file mode 100644 index d0d5fee1d..000000000 --- a/javatests/artifacts/hilt-android/gradleConfigCache/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name='Gradle Configuration Cache App' -include ':app' diff --git a/java/dagger/hilt/android/example/gradle/simple/feature/build.gradle b/javatests/artifacts/hilt-android/pluginMarker/app/build.gradle index 462aefc1e..b288628be 100644 --- a/java/dagger/hilt/android/example/gradle/simple/feature/build.gradle +++ b/javatests/artifacts/hilt-android/pluginMarker/app/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2022 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. @@ -14,37 +14,28 @@ * limitations under the License. */ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' -apply plugin: 'dagger.hilt.android.plugin' +plugins { + id 'com.android.application' version '7.0.0' + id 'com.google.dagger.hilt.android' version 'LOCAL-SNAPSHOT' +} android { compileSdkVersion 30 buildToolsVersion "30.0.2" defaultConfig { - minSdkVersion 15 + applicationId "dagger.hilt.android.simple" + minSdkVersion 21 targetSdkVersion 30 - versionCode 1 - versionName "1.0" } + compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 } } -kapt { - correctErrorTypes true -} - dependencies { - // This is api instead of implementation since Kotlin modules here consumed - // by the app need to expose @kotlin.Metadata - api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT' - kapt 'com.google.dagger:hilt-android-compiler:LOCAL-SNAPSHOT' -} + annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT' +}
\ No newline at end of file diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/res/layout/activity_main.xml b/javatests/artifacts/hilt-android/pluginMarker/app/src/main/AndroidManifest.xml index cb5a8df9c..60fb594b9 100644 --- a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/res/layout/activity_main.xml +++ b/javatests/artifacts/hilt-android/pluginMarker/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2020 The Dagger Authors. + ~ Copyright (C) 2022 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. @@ -14,9 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/background_light"> -</RelativeLayout> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="dagger.hilt.android.simple"> +</manifest>
\ No newline at end of file diff --git a/javatests/artifacts/hilt-android/pluginMarker/gradle.properties b/javatests/artifacts/hilt-android/pluginMarker/gradle.properties new file mode 100644 index 000000000..5bac8ac50 --- /dev/null +++ b/javatests/artifacts/hilt-android/pluginMarker/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/java/dagger/example/gradle/simple/gradle/wrapper/gradle-wrapper.jar b/javatests/artifacts/hilt-android/pluginMarker/gradle/wrapper/gradle-wrapper.jar Binary files differindex 5c2d1cf01..5c2d1cf01 100644 --- a/java/dagger/example/gradle/simple/gradle/wrapper/gradle-wrapper.jar +++ b/javatests/artifacts/hilt-android/pluginMarker/gradle/wrapper/gradle-wrapper.jar diff --git a/java/dagger/example/gradle/simple/gradle/wrapper/gradle-wrapper.properties b/javatests/artifacts/hilt-android/pluginMarker/gradle/wrapper/gradle-wrapper.properties index 4d9ca1649..0f80bbf51 100644 --- a/java/dagger/example/gradle/simple/gradle/wrapper/gradle-wrapper.properties +++ b/javatests/artifacts/hilt-android/pluginMarker/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/dagger/example/gradle/simple/gradlew b/javatests/artifacts/hilt-android/pluginMarker/gradlew index b0d6d0ab5..b0d6d0ab5 100755 --- a/java/dagger/example/gradle/simple/gradlew +++ b/javatests/artifacts/hilt-android/pluginMarker/gradlew diff --git a/javatests/artifacts/hilt-android/pluginMarker/settings.gradle b/javatests/artifacts/hilt-android/pluginMarker/settings.gradle new file mode 100644 index 000000000..f32d22538 --- /dev/null +++ b/javatests/artifacts/hilt-android/pluginMarker/settings.gradle @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + mavenLocal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + mavenLocal() + } +} +rootProject.name = "PluginMarkerCheck" +include ':app'
\ No newline at end of file diff --git a/javatests/artifacts/hilt-android/simple/app-java-only/build.gradle b/javatests/artifacts/hilt-android/simple/app-java-only/build.gradle new file mode 100644 index 000000000..9d17cd917 --- /dev/null +++ b/javatests/artifacts/hilt-android/simple/app-java-only/build.gradle @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2021 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. + */ + +apply plugin: 'com.android.application' +apply plugin: 'dagger.hilt.android.plugin' + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.2" + + defaultConfig { + applicationId "dagger.hilt.android.simple" + minSdkVersion 15 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + testOptions { + unitTests.includeAndroidResources = true + } + lintOptions { + checkReleaseBuilds = false + } +} + +hilt { + enableTransformForLocalTests = true +} + +configurations.all { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + if ("$dagger_version" == 'LOCAL-SNAPSHOT' + && details.requested.group == 'com.google.dagger') { + details.useVersion 'LOCAL-SNAPSHOT' + details.because 'LOCAL-SNAPSHOT should act as latest version.' + } + } +} + +dependencies { + implementation "com.google.dagger:hilt-android:$dagger_version" + annotationProcessor "com.google.dagger:hilt-compiler:$dagger_version" + + testImplementation 'com.google.truth:truth:1.0.1' + testImplementation 'junit:junit:4.13' + testImplementation 'org.robolectric:robolectric:4.5-alpha-3' + testImplementation 'androidx.core:core:1.3.2' + testImplementation 'androidx.test.ext:junit:1.1.2' + testImplementation 'androidx.test:runner:1.3.0' + testImplementation 'androidx.test.espresso:espresso-core:3.3.0' +} diff --git a/javatests/artifacts/hilt-android/simple/app-java-only/src/main/AndroidManifest.xml b/javatests/artifacts/hilt-android/simple/app-java-only/src/main/AndroidManifest.xml new file mode 100644 index 000000000..cae4e88fa --- /dev/null +++ b/javatests/artifacts/hilt-android/simple/app-java-only/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ +<!-- + ~ Copyright (C) 2021 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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="dagger.hilt.android.simple"> + + <application + android:name=".SimpleApplication" + android:label="Java-only Hilt Android" + android:taskAffinity=""> + </application> +</manifest> diff --git a/javatests/artifacts/hilt-android/simple/app-java-only/src/main/java/dagger/hilt/android/simple/SimpleApplication.java b/javatests/artifacts/hilt-android/simple/app-java-only/src/main/java/dagger/hilt/android/simple/SimpleApplication.java new file mode 100644 index 000000000..2a0d8be9a --- /dev/null +++ b/javatests/artifacts/hilt-android/simple/app-java-only/src/main/java/dagger/hilt/android/simple/SimpleApplication.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 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.hilt.android.simple; + +import android.app.Application; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.EntryPoint; +import dagger.hilt.InstallIn; +import dagger.hilt.android.EntryPointAccessors; +import dagger.hilt.android.HiltAndroidApp; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; + +/** A java-only application that uses Hilt. */ +@HiltAndroidApp +public class SimpleApplication extends Application { + @Module + @InstallIn(SingletonComponent.class) + interface MyModule { + @Provides + static String provideString() { + return "some string"; + } + } + + @EntryPoint + @InstallIn(SingletonComponent.class) + interface MyEntryPoint { + String getString(); + } + + @Inject String str; + + public String getStringEntryPoint() { + return EntryPointAccessors.fromApplication(this, MyEntryPoint.class).getString(); + } +} diff --git a/javatests/artifacts/hilt-android/simple/app-java-only/src/test/java/dagger/hilt/android/simple/BuildTest.java b/javatests/artifacts/hilt-android/simple/app-java-only/src/test/java/dagger/hilt/android/simple/BuildTest.java new file mode 100644 index 000000000..9411852d2 --- /dev/null +++ b/javatests/artifacts/hilt-android/simple/app-java-only/src/test/java/dagger/hilt/android/simple/BuildTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 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.hilt.android.simple; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** + * Regression test for https://github.com/google/dagger/issues/3119 + */ +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = SimpleApplication.class) +public class BuildTest { + @Test + public void useAppContext() { + assertThat((Object) ApplicationProvider.getApplicationContext()) + .isInstanceOf(SimpleApplication.class); + SimpleApplication app = (SimpleApplication) ApplicationProvider.getApplicationContext(); + assertThat(app.str).isNotNull(); + assertThat(app.str).isEqualTo(app.getStringEntryPoint()); + } +} diff --git a/javatests/artifacts/hilt-android/simple/app/build.gradle b/javatests/artifacts/hilt-android/simple/app/build.gradle index 1d76ec26c..34043ad29 100644 --- a/javatests/artifacts/hilt-android/simple/app/build.gradle +++ b/javatests/artifacts/hilt-android/simple/app/build.gradle @@ -13,10 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import org.gradle.util.VersionNumber apply plugin: 'com.android.application' apply plugin: 'dagger.hilt.android.plugin' +// Gets additional test directories to be added to test and androidTest source +// sets. If the directory name is appended with '-agp-x.x.x' then the directory +// is conditionally added based on the AGP version of the project. +def getAdditionalTestDirs(String variant) { + def testDirs = [ + 'androidTest': [], + 'sharedTest': ['src/sharedTest/java'], + 'test': [] + ] + def suffix = '-agp-' + def agpVersion = VersionNumber.parse(agp_version) + file("${getProjectDir().absolutePath}/src").eachFile { file -> + int indexOf = file.name.indexOf(suffix) + if (file.isDirectory() && indexOf != -1) { + def dirAgpVersion = + VersionNumber.parse(file.name.substring(indexOf + suffix.length())) + if (agpVersion >= dirAgpVersion) { + testDirs[file.name.substring(0, indexOf)].add("src/${file.name}/java") + } + } + } + return testDirs[variant] + testDirs['sharedTest'] +} + android { compileSdkVersion 30 buildToolsVersion "30.0.2" @@ -40,19 +65,18 @@ android { checkReleaseBuilds = false } sourceSets { - String sharedTestDir = 'src/sharedTest/java' test { - java.srcDirs += sharedTestDir + java.srcDirs += getAdditionalTestDirs("test") } androidTest { - java.srcDirs += sharedTestDir + java.srcDirs += getAdditionalTestDirs("androidTest") } } } hilt { - enableExperimentalClasspathAggregation = true enableTransformForLocalTests = true + enableAggregatingTask = true } configurations.all { @@ -97,8 +121,8 @@ dependencies { // To help us catch version skew related issues in hilt extensions. // TODO(bcorso): Add examples testing the actual API. - implementation 'androidx.hilt:hilt-work:1.0.0-alpha01' - annotationProcessor 'androidx.hilt:hilt-compiler:1.0.0-alpha01' - testAnnotationProcessor 'androidx.hilt:hilt-compiler:1.0.0-alpha01' - androidTestAnnotationProcessor 'androidx.hilt:hilt-compiler:1.0.0-alpha01' + implementation 'androidx.hilt:hilt-work:1.0.0' + annotationProcessor 'androidx.hilt:hilt-compiler:1.0.0' + testAnnotationProcessor 'androidx.hilt:hilt-compiler:1.0.0' + androidTestAnnotationProcessor 'androidx.hilt:hilt-compiler:1.0.0' } diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/BroadcastReceiverTest.java b/javatests/artifacts/hilt-android/simple/app/src/androidTest-agp-4.2.0/java/dagger/hilt/android/simple/BroadcastReceiverTest.java index 7585bbb1d..265869ab5 100644 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/sharedTest/java/dagger/hilt/android/example/gradle/simple/BroadcastReceiverTest.java +++ b/javatests/artifacts/hilt-android/simple/app/src/androidTest-agp-4.2.0/java/dagger/hilt/android/simple/BroadcastReceiverTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package dagger.hilt.android.example.gradle.simple; +package dagger.hilt.android.simple; import static com.google.common.truth.Truth.assertThat; @@ -111,7 +111,7 @@ public class BroadcastReceiverTest { /** Test receiver */ @AndroidEntryPoint - static class TestReceiverOne extends BroadcastReceiver { + public static class TestReceiverOne extends BroadcastReceiver { final CountDownLatch latch = new CountDownLatch(1); @@ -126,7 +126,7 @@ public class BroadcastReceiverTest { /** Test receiver */ @AndroidEntryPoint - static class TestReceiverTwo extends BaseReceiverAbstractMethod { + public static class TestReceiverTwo extends BaseReceiverAbstractMethod { final CountDownLatch latch = new CountDownLatch(1); @@ -141,7 +141,7 @@ public class BroadcastReceiverTest { /** Test receiver */ @AndroidEntryPoint - static class TestReceiverThree extends BaseReceiverConcreteMethod { + public static class TestReceiverThree extends BaseReceiverConcreteMethod { final CountDownLatch latch = new CountDownLatch(1); @@ -157,7 +157,7 @@ public class BroadcastReceiverTest { /** Complex-ish test receiver */ @AndroidEntryPoint - static class TestReceiverFour extends BroadcastReceiver { + public static class TestReceiverFour extends BroadcastReceiver { final CountDownLatch latch = new CountDownLatch(1); diff --git a/javatests/artifacts/hilt-android/simple/app/src/debug/AndroidManifest.xml b/javatests/artifacts/hilt-android/simple/app/src/debug/AndroidManifest.xml index 14d33b0c7..76c6afad3 100644 --- a/javatests/artifacts/hilt-android/simple/app/src/debug/AndroidManifest.xml +++ b/javatests/artifacts/hilt-android/simple/app/src/debug/AndroidManifest.xml @@ -18,6 +18,10 @@ <application> <activity + android:name=".AliasOfMultipleScopesTest$TestActivity" + android:theme="@style/Theme.AppCompat.Light" + android:exported="false" /> + <activity android:name=".Injection1Test$TestActivity" android:theme="@style/Theme.AppCompat.Light" android:exported="false" /> @@ -26,6 +30,10 @@ android:theme="@style/Theme.AppCompat.Light" android:exported="false"/> <activity + android:name=".InvokeSpecialTransformTest$TestActivity" + android:theme="@style/Theme.AppCompat.Light" + android:exported="false"/> + <activity android:name=".ActivityScenarioRuleTest$TestActivity" android:theme="@style/Theme.AppCompat.Light" android:exported="false"/> diff --git a/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/AliasOfMultipleScopesTest.java b/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/AliasOfMultipleScopesTest.java new file mode 100644 index 000000000..7175f78a9 --- /dev/null +++ b/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/AliasOfMultipleScopesTest.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2021 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.hilt.android.simple; + +import static com.google.common.truth.Truth.assertThat; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import android.content.Context; +import androidx.activity.ComponentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.DefineComponent; +import dagger.hilt.EntryPoint; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.android.AndroidEntryPoint; +import dagger.hilt.android.components.ActivityComponent; +import dagger.hilt.android.qualifiers.ApplicationContext; +import dagger.hilt.android.scopes.ActivityScoped; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.components.SingletonComponent; +import dagger.hilt.migration.AliasOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Scope; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +public final class AliasOfMultipleScopesTest { + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject @ApplicationContext Context context; + @Inject CustomComponent.Builder customComponentBuilder; + + @Scope + @Retention(CLASS) + @Target({ElementType.METHOD, ElementType.TYPE}) + public @interface CustomScoped {} + + @DefineComponent(parent = SingletonComponent.class) + @CustomScoped + public interface CustomComponent { + @DefineComponent.Builder + public interface Builder { + CustomComponent build(); + } + } + + @Scope + @AliasOf({ActivityScoped.class, CustomScoped.class}) + public @interface AliasScoped {} + + public interface UnscopedDep {} + + public interface ActivityScopedDep {} + + public interface CustomScopedDep {} + + public interface AliasScopedDep {} + + @Module + @InstallIn(SingletonComponent.class) + interface SingletonTestModule { + @Provides + static UnscopedDep unscopedDep() { + return new UnscopedDep() {}; + } + } + + @Module + @InstallIn(ActivityComponent.class) + interface ActivityTestModule { + @Provides + @ActivityScoped + static ActivityScopedDep activityScopedDep() { + return new ActivityScopedDep() {}; + } + + @Provides + @AliasScoped + static AliasScopedDep aliasScopedDep() { + return new AliasScopedDep() {}; + } + } + + @Module + @InstallIn(CustomComponent.class) + interface CustomTestModule { + @Provides + @CustomScoped + static CustomScopedDep customScopedDep() { + return new CustomScopedDep() {}; + } + + @Provides + @AliasScoped + static AliasScopedDep aliasScopedDep() { + return new AliasScopedDep() {}; + } + } + + /** An activity to test injection. */ + @AndroidEntryPoint(ComponentActivity.class) + public static final class TestActivity extends Hilt_AliasOfMultipleScopesTest_TestActivity { + @Inject Provider<UnscopedDep> unscopedDep; + @Inject Provider<ActivityScopedDep> activityScopedDep; + @Inject Provider<AliasScopedDep> aliasScopedDep; + } + + @EntryPoint + @InstallIn(SingletonComponent.class) + interface CustomComponentBuilderEntryPoint { + CustomComponent.Builder customComponentBuilder(); + } + + @EntryPoint + @InstallIn(CustomComponent.class) + interface CustomComponentEntryPoint { + Provider<UnscopedDep> unscopedDep(); + + Provider<CustomScopedDep> customScopedDep(); + + Provider<AliasScopedDep> aliasScopedDep(); + } + + @Before + public void setUp() { + rule.inject(); + } + + @Test + public void testActivityScoped() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.onActivity( + activity -> { + assertThat(activity.unscopedDep.get()).isNotSameInstanceAs(activity.unscopedDep.get()); + assertThat(activity.activityScopedDep.get()) + .isSameInstanceAs(activity.activityScopedDep.get()); + assertThat(activity.aliasScopedDep.get()) + .isSameInstanceAs(activity.aliasScopedDep.get()); + }); + } + } + + @Test + public void testCustomScoped() { + CustomComponent customComponent = + EntryPoints.get(context, CustomComponentBuilderEntryPoint.class) + .customComponentBuilder() + .build(); + CustomComponentEntryPoint entryPoint = + EntryPoints.get(customComponent, CustomComponentEntryPoint.class); + assertThat(entryPoint.unscopedDep().get()).isNotSameInstanceAs(entryPoint.unscopedDep().get()); + assertThat(entryPoint.customScopedDep().get()) + .isSameInstanceAs(entryPoint.customScopedDep().get()); + assertThat(entryPoint.aliasScopedDep().get()) + .isSameInstanceAs(entryPoint.aliasScopedDep().get()); + } +} diff --git a/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/InvokeSpecialTransformTest.java b/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/InvokeSpecialTransformTest.java new file mode 100644 index 000000000..9fed04a6a --- /dev/null +++ b/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/InvokeSpecialTransformTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 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.hilt.android.simple; + +import android.content.Context; +import android.os.Bundle; +import android.widget.FrameLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.Lifecycle.State; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.AndroidEntryPoint; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Test that verifies edge cases of invokespecial instructions transformation. */ +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +public class InvokeSpecialTransformTest { + + @Rule + public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void constructorCallOfOldSuperclass() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.moveToState(State.DESTROYED); + } + } + + /** A test activity */ + @AndroidEntryPoint + public static final class TestActivity extends AppCompatActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(new CustomView(this).createBrother()); + } + } + + /** A custom view for testing */ + @AndroidEntryPoint + public static class CustomView extends FrameLayout { + + public CustomView(@NonNull Context context) { + // This super call is an invokespecial that should be transformed + super(context); + // This invokespecial that should not be transformed. + FrameLayout secondInvokeSpecial = new FrameLayout(getContext()); + } + + FrameLayout createBrother() { + // This invokespecial that should not be transformed. + return new FrameLayout(getContext()); + } + } +}
\ No newline at end of file diff --git a/javatests/artifacts/hilt-android/simple/build.gradle b/javatests/artifacts/hilt-android/simple/build.gradle index 59336bcbf..d869b39ee 100644 --- a/javatests/artifacts/hilt-android/simple/build.gradle +++ b/javatests/artifacts/hilt-android/simple/build.gradle @@ -17,12 +17,12 @@ buildscript { ext { dagger_version = 'LOCAL-SNAPSHOT' - kotlin_version = '1.3.61' - agp_version = System.getenv('AGP_VERSION') ?: "4.2.0-beta04" + kotlin_version = '1.5.32' + agp_version = System.getenv('AGP_VERSION') ?: "4.2.0" } repositories { google() - jcenter() + mavenCentral() mavenLocal() } dependencies { @@ -35,7 +35,6 @@ buildscript { allprojects { repositories { google() - jcenter() mavenCentral() mavenLocal() } diff --git a/javatests/artifacts/hilt-android/simple/earlyentrypoint/build.gradle b/javatests/artifacts/hilt-android/simple/earlyentrypoint/build.gradle new file mode 100644 index 000000000..1428fb125 --- /dev/null +++ b/javatests/artifacts/hilt-android/simple/earlyentrypoint/build.gradle @@ -0,0 +1,68 @@ +plugins { + id 'com.android.library' + id 'dagger.hilt.android.plugin' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.2" + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + } + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + testOptions { + unitTests.includeAndroidResources = true + } +} + +hilt { + enableTransformForLocalTests = true + enableAggregatingTask = true +} + +// This is a regression test for https://github.com/google/dagger/issues/2789. +// Reproducing this issue requires that we don't have unexpected tests, so this +// check validates that. In particular, if we accidentally add a test with no +// test-specific bindings the EarlyEntryPoints will use the component for that +// test instead of generating a component just for the EarlyEntryPoints, which +// causes this issue. +task checkSourceSetTask(){ + sourceSets { + test { + def actualSrcs = allSource.files.name as Set + def expectedSrcs = [ + 'EarlyEntryPointWithBindValueTest.java', + 'EarlyEntryPointWithBindValueObjects.java' + ] as Set + if (!actualSrcs.equals(expectedSrcs)) { + throw new StopExecutionException( + 'Unexpected test sources: ' + allSource.files.name) + } + } + } +} + +gradle.projectsEvaluated { + preBuild.dependsOn checkSourceSetTask +} + +dependencies { + implementation "com.google.dagger:hilt-android:$dagger_version" + annotationProcessor "com.google.dagger:hilt-compiler:$dagger_version" + + testImplementation 'com.google.truth:truth:1.0.1' + testImplementation 'junit:junit:4.13' + testImplementation 'org.robolectric:robolectric:4.5-alpha-3' + testImplementation 'androidx.core:core:1.3.2' + testImplementation 'androidx.test.ext:junit:1.1.2' + testImplementation 'androidx.test:runner:1.3.0' + testImplementation "com.google.dagger:hilt-android-testing:$dagger_version" + testAnnotationProcessor "com.google.dagger:hilt-compiler:$dagger_version" +}
\ No newline at end of file diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/debug/AndroidManifest.xml b/javatests/artifacts/hilt-android/simple/earlyentrypoint/src/main/AndroidManifest.xml index 081c7ade7..7197111ea 100644 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/debug/AndroidManifest.xml +++ b/javatests/artifacts/hilt-android/simple/earlyentrypoint/src/main/AndroidManifest.xml @@ -16,9 +16,6 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="dagger.hilt.android.example.gradle.simpleKotlin"> + package="dagger.hilt.android.simple.earlyentrypoint"> - <application> - <activity android:name=".SimpleTest$TestActivity" android:exported="false"/> - </application> </manifest> diff --git a/java/dagger/example/gradle/simple/SimpleApplication.java b/javatests/artifacts/hilt-android/simple/earlyentrypoint/src/test/java/dagger/hilt/android/simple/earlyentrypoint/EarlyEntryPointWithBindValueObjects.java index 11552f869..c86f90025 100644 --- a/java/dagger/example/gradle/simple/SimpleApplication.java +++ b/javatests/artifacts/hilt-android/simple/earlyentrypoint/src/test/java/dagger/hilt/android/simple/earlyentrypoint/EarlyEntryPointWithBindValueObjects.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Dagger Authors. + * Copyright (C) 2021 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. @@ -14,35 +14,28 @@ * limitations under the License. */ -package dagger.example.gradle.simple; +package dagger.hilt.android.simple; -import dagger.Component; -import dagger.Module; -import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.EarlyEntryPoint; +import dagger.hilt.components.SingletonComponent; import javax.inject.Inject; import javax.inject.Singleton; -/** A simple, skeletal application that defines a simple component. */ -public class SimpleApplication { - static final class Foo { - @Inject Foo() {} - } - - @Module - static final class SimpleModule { - @Provides - static Foo provideFoo() { - return new Foo(); - } - } +// This is defined outside of the tests since EarlyEntryPoint cannot be nested in tests. +public final class EarlyEntryPointWithBindValueObjects { + private EarlyEntryPointWithBindValueObjects() {} @Singleton - @Component(modules = { SimpleModule.class }) - interface SimpleComponent { - Foo foo(); + public static final class Foo { + @Inject + Foo() {} } - public static void main(String[] args) { - Foo foo = DaggerSimpleApplication_SimpleComponent.create().foo(); + /** Used to test {@link EarlyEntryPoint} */ + @EarlyEntryPoint + @InstallIn(SingletonComponent.class) + public interface FooEarlyEntryPoint { + Foo getEarlyFoo(); } } diff --git a/javatests/artifacts/hilt-android/simple/earlyentrypoint/src/test/java/dagger/hilt/android/simple/earlyentrypoint/EarlyEntryPointWithBindValueTest.java b/javatests/artifacts/hilt-android/simple/earlyentrypoint/src/test/java/dagger/hilt/android/simple/earlyentrypoint/EarlyEntryPointWithBindValueTest.java new file mode 100644 index 000000000..0b2acfd0e --- /dev/null +++ b/javatests/artifacts/hilt-android/simple/earlyentrypoint/src/test/java/dagger/hilt/android/simple/earlyentrypoint/EarlyEntryPointWithBindValueTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2021 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.hilt.android.simple; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoint; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.android.EarlyEntryPoints; +import dagger.hilt.android.simple.EarlyEntryPointWithBindValueObjects.Foo; +import dagger.hilt.android.simple.EarlyEntryPointWithBindValueObjects.FooEarlyEntryPoint; +import dagger.hilt.android.testing.BindValue; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.components.SingletonComponent; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class EarlyEntryPointWithBindValueTest { + @EntryPoint + @InstallIn(SingletonComponent.class) + interface FooEntryPoint { + Foo foo(); + } + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @BindValue String bindString = "TestValue"; + + @Test + public void testBindValue() throws Exception { + rule.inject(); + assertThat(bindString).isNotNull(); + assertThat(bindString).isEqualTo("TestValue"); + } + + @Test + public void testEarlyEntryPoint() throws Exception { + Context appContext = getApplicationContext(); + + // Assert that all scoped Foo instances from EarlyEntryPoint are equal + Foo earlyFoo1 = EarlyEntryPoints.get(appContext, FooEarlyEntryPoint.class).getEarlyFoo(); + Foo earlyFoo2 = EarlyEntryPoints.get(appContext, FooEarlyEntryPoint.class).getEarlyFoo(); + assertThat(earlyFoo1).isNotNull(); + assertThat(earlyFoo2).isNotNull(); + assertThat(earlyFoo1).isEqualTo(earlyFoo2); + + // Assert that all scoped Foo instances from EntryPoint are equal + Foo foo1 = EntryPoints.get(appContext, FooEntryPoint.class).foo(); + Foo foo2 = EntryPoints.get(appContext, FooEntryPoint.class).foo(); + assertThat(foo1).isNotNull(); + assertThat(foo2).isNotNull(); + assertThat(foo1).isEqualTo(foo2); + + // Assert that scoped Foo instances from EarlyEntryPoint and EntryPoint are not equal + assertThat(foo1).isNotEqualTo(earlyFoo1); + } +} diff --git a/javatests/artifacts/hilt-android/simple/gradle.properties b/javatests/artifacts/hilt-android/simple/gradle.properties index 646c51b97..cc9cc5521 100644 --- a/javatests/artifacts/hilt-android/simple/gradle.properties +++ b/javatests/artifacts/hilt-android/simple/gradle.properties @@ -1,2 +1,9 @@ android.useAndroidX=true android.enableJetifier=true + +# Enable and fail the build if an issue is found that disallows the +# configuration cache. These options along with this app being built in +# presubmit helps us cache changes that would cause config cache to be disabled +# via the HiltGradlePlugin. +org.gradle.unsafe.configuration-cache-problems=fail +org.gradle.unsafe.configuration-cache.max-problems=0 diff --git a/javatests/artifacts/hilt-android/simple/gradle/wrapper/gradle-wrapper.properties b/javatests/artifacts/hilt-android/simple/gradle/wrapper/gradle-wrapper.properties index 4d9ca1649..0f80bbf51 100644 --- a/javatests/artifacts/hilt-android/simple/gradle/wrapper/gradle-wrapper.properties +++ b/javatests/artifacts/hilt-android/simple/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/javatests/artifacts/hilt-android/simple/settings.gradle b/javatests/artifacts/hilt-android/simple/settings.gradle index bad895d28..9dc59db7e 100644 --- a/javatests/artifacts/hilt-android/simple/settings.gradle +++ b/javatests/artifacts/hilt-android/simple/settings.gradle @@ -1,6 +1,8 @@ rootProject.name='Simple Hilt Android' include ':app' +include ':app-java-only' include ':feature' include ':lib' include ':deep-android-lib' -include ':deep-lib'
\ No newline at end of file +include ':deep-lib' +include ':earlyentrypoint'
\ No newline at end of file diff --git a/javatests/artifacts/hilt-android/simpleKotlin/android-library/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/android-library/build.gradle index 383c4be2f..f682247a8 100644 --- a/javatests/artifacts/hilt-android/simpleKotlin/android-library/build.gradle +++ b/javatests/artifacts/hilt-android/simpleKotlin/android-library/build.gradle @@ -26,6 +26,7 @@ android { dependencies { implementation project(':deep-android-lib') implementation project(':deep-kotlin-lib') + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT' kapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT' }
\ No newline at end of file diff --git a/javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle index ecfaf6e92..d8104d712 100644 --- a/javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle +++ b/javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle @@ -62,8 +62,8 @@ android { } hilt { - enableExperimentalClasspathAggregation = true enableTransformForLocalTests = true + enableAggregatingTask = true } dependencies { diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/java/dagger/hilt/android/gradleConfigCache/MainActivity.kt b/javatests/artifacts/hilt-android/simpleKotlin/app/src/sharedTest/java/dagger/hilt/android/simpleKotlin/KotlinSuppressClasses.kt index 0011bdefc..c9bc671c3 100644 --- a/javatests/artifacts/hilt-android/gradleConfigCache/app/src/main/java/dagger/hilt/android/gradleConfigCache/MainActivity.kt +++ b/javatests/artifacts/hilt-android/simpleKotlin/app/src/sharedTest/java/dagger/hilt/android/simpleKotlin/KotlinSuppressClasses.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2021 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. @@ -14,23 +14,16 @@ * limitations under the License. */ -package dagger.hilt.android.gradleConfigCache +package dagger.hilt.android.simpleKotlin -import android.os.Bundle -import android.util.Log -import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.FragmentActivity import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject -@AndroidEntryPoint -class MainActivity : AppCompatActivity() { - - @Inject - lateinit var foo: Foo - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - Log.d("Hilt", "Foo is $foo") - } +// Regression test for https://github.com/google/dagger/issues/2898 +// TODO(bcorso):Replace this with a Kotlin compiler test and verify the SuppressWarnings is +// propagated to the generated Hilt class. +class KotlinSuppressClasses { + @Suppress("deprecation") + @AndroidEntryPoint + class MyActivity : FragmentActivity() } diff --git a/javatests/artifacts/hilt-android/simpleKotlin/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/build.gradle index e7eb9b252..249387c46 100644 --- a/javatests/artifacts/hilt-android/simpleKotlin/build.gradle +++ b/javatests/artifacts/hilt-android/simpleKotlin/build.gradle @@ -16,12 +16,12 @@ buildscript { ext { - kotlin_version = '1.3.61' - agp_version = System.getenv('AGP_VERSION') ?: "4.2.0-beta04" + kotlin_version = '1.5.32' + agp_version = System.getenv('AGP_VERSION') ?: "4.2.0" } repositories { google() - jcenter() + mavenCentral() mavenLocal() } dependencies { @@ -34,7 +34,6 @@ buildscript { allprojects { repositories { google() - jcenter() mavenCentral() mavenLocal() } diff --git a/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/build.gradle index 3a1923aff..8bfadf048 100644 --- a/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/build.gradle +++ b/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/build.gradle @@ -30,6 +30,8 @@ android { } dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT' kapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT' @@ -42,5 +44,5 @@ dependencies { } hilt { - enableExperimentalClasspathAggregation = true + enableAggregatingTask = true }
\ No newline at end of file diff --git a/javatests/artifacts/hilt-android/simpleKotlin/gradle.properties b/javatests/artifacts/hilt-android/simpleKotlin/gradle.properties index 646c51b97..cc9cc5521 100644 --- a/javatests/artifacts/hilt-android/simpleKotlin/gradle.properties +++ b/javatests/artifacts/hilt-android/simpleKotlin/gradle.properties @@ -1,2 +1,9 @@ android.useAndroidX=true android.enableJetifier=true + +# Enable and fail the build if an issue is found that disallows the +# configuration cache. These options along with this app being built in +# presubmit helps us cache changes that would cause config cache to be disabled +# via the HiltGradlePlugin. +org.gradle.unsafe.configuration-cache-problems=fail +org.gradle.unsafe.configuration-cache.max-problems=0 diff --git a/javatests/artifacts/hilt-android/simpleKotlin/gradle/wrapper/gradle-wrapper.properties b/javatests/artifacts/hilt-android/simpleKotlin/gradle/wrapper/gradle-wrapper.properties index 4d9ca1649..0f80bbf51 100644 --- a/javatests/artifacts/hilt-android/simpleKotlin/gradle/wrapper/gradle-wrapper.properties +++ b/javatests/artifacts/hilt-android/simpleKotlin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/javatests/dagger/BUILD b/javatests/dagger/BUILD index c6ab360b7..f3153d945 100644 --- a/javatests/dagger/BUILD +++ b/javatests/dagger/BUILD @@ -27,10 +27,10 @@ GenJavaTests( javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES, deps = [ "//java/dagger:core", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/guava/collect", + "//third_party/java/guava/util/concurrent", + "//third_party/java/jsr330_inject", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/android/BUILD b/javatests/dagger/android/BUILD index 38efb3afa..09dc4ae40 100644 --- a/javatests/dagger/android/BUILD +++ b/javatests/dagger/android/BUILD @@ -29,8 +29,8 @@ GenRobolectricTests( deps = [ "//:dagger_with_compiler", "//java/dagger/android", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/guava/collect", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/android/processor/BUILD b/javatests/dagger/android/processor/BUILD index c639a4191..4dbd26074 100644 --- a/javatests/dagger/android/processor/BUILD +++ b/javatests/dagger/android/processor/BUILD @@ -30,12 +30,12 @@ GenJavaTests( "//java/dagger/android", "//java/dagger/android/processor", "//java/dagger/internal/codegen:processor", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", + "//third_party/java/compile_testing", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/junit", + "//third_party/java/truth", "@androidsdk//:platforms/android-30/android.jar", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", "@maven//:androidx_lifecycle_lifecycle_common", diff --git a/javatests/dagger/android/support/BUILD b/javatests/dagger/android/support/BUILD index 2ef1ece6e..de1b2d3cf 100644 --- a/javatests/dagger/android/support/BUILD +++ b/javatests/dagger/android/support/BUILD @@ -32,11 +32,11 @@ GenRobolectricTests( "//:dagger_with_compiler", "//java/dagger/android", "//java/dagger/android/support", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/util/concurrent", + "//third_party/java/junit", + "//third_party/java/truth", "@maven//:androidx_activity_activity", "@maven//:androidx_appcompat_appcompat", "@maven//:androidx_fragment_fragment", diff --git a/javatests/dagger/android/support/functional/BUILD b/javatests/dagger/android/support/functional/BUILD index 459d55fee..0281ea10e 100644 --- a/javatests/dagger/android/support/functional/BUILD +++ b/javatests/dagger/android/support/functional/BUILD @@ -40,7 +40,7 @@ android_library( "//:android", "//:android-support", # TODO(ronshapiro): figure out why strict deps is failing without this - "@google_bazel_common//third_party/java/jsr250_annotations", + "//third_party/java/jsr250_annotations", ], ) @@ -55,7 +55,7 @@ GenRobolectricTests( "//:android", "//:android-support", "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/truth", "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", "@maven//:androidx_lifecycle_lifecycle_common", diff --git a/javatests/dagger/functional/BUILD b/javatests/dagger/functional/BUILD index 8e6a5551e..81469a241 100644 --- a/javatests/dagger/functional/BUILD +++ b/javatests/dagger/functional/BUILD @@ -28,19 +28,19 @@ GenJavaTests( javacopts = DOCLINT_HTML_AND_SYNTAX, lib_javacopts = SOURCE_7_TARGET_7, test_only_deps = [ - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", - "@google_bazel_common//third_party/java/guava:testlib", - "@google_bazel_common//third_party/java/truth", - "@google_bazel_common//third_party/java/junit", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/util/concurrent", + "//third_party/java/guava:testlib", + "//third_party/java/truth", + "//third_party/java/junit", ], # NOTE: This should not depend on Guava or jsr305 to ensure that Dagger can be # used without Guava and jsr305 deps. deps = [ "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/auto:factory", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/auto:factory", + "//third_party/java/auto:value", + "//third_party/java/jsr330_inject", ], ) diff --git a/javatests/dagger/functional/ComponentNestedTypeTest.java b/javatests/dagger/functional/ComponentNestedTypeTest.java new file mode 100644 index 000000000..e39c42994 --- /dev/null +++ b/javatests/dagger/functional/ComponentNestedTypeTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 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.functional; + +import static com.google.common.truth.Truth.assertThat; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Regression test for b/212604806. */ +@RunWith(JUnit4.class) +public final class ComponentNestedTypeTest { + @Component(modules = TestModule.class) + interface TestComponent { + + // Dagger generated component implementation that extends TestComponent will implement this + // method, so the component implementation will keep a reference to the {@link + // dagger.functional.sub.NestedType}. The reference to {@link dagger.functional.sub.NestedType} + // may collide with the NestedType defined inside of TestComponent, because javapoet may strip + // the package prefix of the type as it does not have enough information about the super + // class/interfaces. + dagger.functional.sub.NestedType nestedType(); + + interface NestedType {} + } + + public static final class SomeType implements dagger.functional.sub.NestedType {} + + @Module + static final class TestModule { + @Provides + static dagger.functional.sub.NestedType provideSomeType() { + return new SomeType(); + } + } + + @Test + public void typeNameWontClashWithNestedTypeName() { + TestComponent component = + DaggerComponentNestedTypeTest_TestComponent.builder().testModule(new TestModule()).build(); + assertThat(component.nestedType()).isNotNull(); + } +} diff --git a/javatests/dagger/functional/assisted/AssistedFactoryDuplicatedParamNamesTest.java b/javatests/dagger/functional/assisted/AssistedFactoryDuplicatedParamNamesTest.java new file mode 100644 index 000000000..af9b40e10 --- /dev/null +++ b/javatests/dagger/functional/assisted/AssistedFactoryDuplicatedParamNamesTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 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.functional.assisted; + +import static com.google.common.truth.Truth.assertThat; + +import dagger.BindsInstance; +import dagger.Component; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class AssistedFactoryDuplicatedParamNamesTest { + static final class Foo { + private final String arg; + private final Bar bar; + + @AssistedInject + Foo(@Assisted String arg, Bar bar) { + this.arg = arg; + this.bar = bar; + } + + Bar getBar() { + return bar; + } + + String getArg() { + return arg; + } + } + + static final class Bar {} + + @AssistedFactory + interface FooFactory { + Foo create(String arg); + } + + @Component + interface TestComponent { + @Component.Factory + interface Factory { + TestComponent create(@BindsInstance Bar arg); + } + + FooFactory fooFactory(); + } + + @Test + public void duplicatedParameterNames_doesNotConflict() { + String str = "test"; + Bar bar = new Bar(); + + Foo foo = + DaggerAssistedFactoryDuplicatedParamNamesTest_TestComponent.factory() + .create(bar) + .fooFactory() + .create(str); + + assertThat(foo.getArg()).isEqualTo(str); + assertThat(foo.getBar()).isEqualTo(bar); + } +} diff --git a/javatests/dagger/functional/assisted/AssistedFactoryTest.java b/javatests/dagger/functional/assisted/AssistedFactoryTest.java index 3176add46..d8ef53ad6 100644 --- a/javatests/dagger/functional/assisted/AssistedFactoryTest.java +++ b/javatests/dagger/functional/assisted/AssistedFactoryTest.java @@ -35,6 +35,8 @@ public final class AssistedFactoryTest { // Simple factory using a nested factory. SimpleFoo.Factory nestedSimpleFooFactory(); + Provider<SimpleFoo.Factory> nestedSimpleFooFactoryProvider(); + // Simple factory using a non-nested factory. SimpleFooFactory nonNestedSimpleFooFactory(); @@ -58,6 +60,7 @@ public final class AssistedFactoryTest { static class SomeEntryPoint { private final SimpleFoo.Factory nestedSimpleFooFactory; private final SimpleFooFactory nonNestedSimpleFooFactory; + private final Provider<SimpleFoo.Factory> nestedSimpleFooFactoryProvider; private final ExtendedSimpleFooFactory extendedSimpleFooFactory; private final FooFactory fooFactory; private final AbstractFooFactory abstractFooFactory; @@ -66,12 +69,14 @@ public final class AssistedFactoryTest { @Inject SomeEntryPoint( SimpleFoo.Factory nestedSimpleFooFactory, + Provider<SimpleFoo.Factory> nestedSimpleFooFactoryProvider, SimpleFooFactory nonNestedSimpleFooFactory, ExtendedSimpleFooFactory extendedSimpleFooFactory, FooFactory fooFactory, AbstractFooFactory abstractFooFactory, NoAssistedParametersFooFactory noAssistedParametersFooFactory) { this.nestedSimpleFooFactory = nestedSimpleFooFactory; + this.nestedSimpleFooFactoryProvider = nestedSimpleFooFactoryProvider; this.nonNestedSimpleFooFactory = nonNestedSimpleFooFactory; this.extendedSimpleFooFactory = extendedSimpleFooFactory; this.fooFactory = fooFactory; @@ -265,6 +270,25 @@ public final class AssistedFactoryTest { } @Test + public void testNestedSimpleFooFactoryProvider() { + AssistedDep1 assistedDep1 = new AssistedDep1(); + SimpleFoo simpleFoo1 = + DaggerAssistedFactoryTest_ParentComponent.create() + .nestedSimpleFooFactoryProvider() + .get() + .createSimpleFoo(assistedDep1); + assertThat(simpleFoo1.assistedDep).isEqualTo(assistedDep1); + + AssistedDep2 assistedDep2 = new AssistedDep2(); + SimpleFoo simpleFoo2 = + DaggerAssistedFactoryTest_ParentComponent.create() + .nestedSimpleFooFactoryProvider() + .get() + .createSimpleFoo(assistedDep2); + assertThat(simpleFoo2.assistedDep).isEqualTo(assistedDep2); + } + + @Test public void testNonNestedSimpleFooFactory() { AssistedDep1 assistedDep1 = new AssistedDep1(); SimpleFoo simpleFoo = @@ -323,6 +347,7 @@ public final class AssistedFactoryTest { SomeEntryPoint someEntryPoint = DaggerAssistedFactoryTest_ParentComponent.create().someEntryPoint(); assertThat(someEntryPoint.nestedSimpleFooFactory).isNotNull(); + assertThat(someEntryPoint.nestedSimpleFooFactoryProvider).isNotNull(); assertThat(someEntryPoint.nonNestedSimpleFooFactory).isNotNull(); assertThat(someEntryPoint.extendedSimpleFooFactory).isNotNull(); assertThat(someEntryPoint.fooFactory).isNotNull(); diff --git a/javatests/dagger/functional/assisted/AssistedFactoryWithMultibindingsTest.java b/javatests/dagger/functional/assisted/AssistedFactoryWithMultibindingsTest.java new file mode 100644 index 000000000..1e4bd60db --- /dev/null +++ b/javatests/dagger/functional/assisted/AssistedFactoryWithMultibindingsTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2021 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.functional.assisted; + +import static com.google.common.truth.Truth.assertThat; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.multibindings.IntoSet; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class AssistedFactoryWithMultibindingsTest { + @Component(modules = ParentModule.class) + interface ParentComponent { + // Factory for assisted injection binding with multi binding contribution. + MultibindingFooFactory multibindingFooFactory(); + + ChildComponent.Builder childComponent(); + } + + static final class AssistedDep {} + + static final class MultibindingFoo { + private final AssistedDep assistedDep; + private final Set<String> stringSet; + + @AssistedInject + MultibindingFoo(@Assisted AssistedDep assistedDep, Set<String> stringSet) { + this.assistedDep = assistedDep; + this.stringSet = stringSet; + } + + AssistedDep assistedDep() { + return assistedDep; + } + + Set<String> stringSet() { + return stringSet; + } + } + + @Subcomponent(modules = ChildModule.class) + static interface ChildComponent { + MultibindingFooFactory multibindingFooFactory(); + + @Subcomponent.Builder + interface Builder { + ChildComponent build(); + } + } + + @Module(subcomponents = ChildComponent.class) + static class ParentModule { + @Provides + @IntoSet + String parentString() { + return "parent"; + } + } + + @Module + static class ChildModule { + @Provides + @IntoSet + String childString() { + return "child"; + } + } + + @AssistedFactory + interface MultibindingFooFactory { + MultibindingFoo createFoo(AssistedDep factoryAssistedDep1); + } + + @Test + public void testAssistedFactoryWithMultibinding() { + AssistedDep assistedDep1 = new AssistedDep(); + ParentComponent parent = DaggerAssistedFactoryWithMultibindingsTest_ParentComponent.create(); + ChildComponent child = parent.childComponent().build(); + MultibindingFoo foo1 = parent.multibindingFooFactory().createFoo(assistedDep1); + MultibindingFoo foo2 = child.multibindingFooFactory().createFoo(assistedDep1); + assertThat(foo1.assistedDep()).isEqualTo(foo2.assistedDep); + assertThat(foo1.stringSet()).containsExactly("parent"); + assertThat(foo2.stringSet()).containsExactly("child", "parent"); + } +} diff --git a/javatests/dagger/functional/assisted/BUILD b/javatests/dagger/functional/assisted/BUILD index 5e663e7e9..b94b3576f 100644 --- a/javatests/dagger/functional/assisted/BUILD +++ b/javatests/dagger/functional/assisted/BUILD @@ -26,20 +26,20 @@ GenJavaTests( javacopts = DOCLINT_HTML_AND_SYNTAX, lib_javacopts = SOURCE_7_TARGET_7, test_only_deps = [ - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", - "@google_bazel_common//third_party/java/guava:testlib", - "@google_bazel_common//third_party/java/truth", - "@google_bazel_common//third_party/java/junit", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/util/concurrent", + "//third_party/java/guava:testlib", + "//third_party/java/truth", + "//third_party/java/junit", "//javatests/dagger/functional/assisted/subpackage", ], # NOTE: This should not depend on Guava or jsr305 to ensure that Dagger can be # used without Guava and jsr305 deps. deps = [ "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/auto:factory", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/auto:factory", + "//third_party/java/auto:value", + "//third_party/java/jsr330_inject", ], ) diff --git a/javatests/dagger/functional/assisted/kotlin/BUILD b/javatests/dagger/functional/assisted/kotlin/BUILD index 3c3421d2a..efb785e59 100644 --- a/javatests/dagger/functional/assisted/kotlin/BUILD +++ b/javatests/dagger/functional/assisted/kotlin/BUILD @@ -25,6 +25,7 @@ kt_jvm_library( srcs = ["KotlinAssistedInjectionClasses.kt"], deps = [ "//:dagger_with_compiler", + "//third_party/java/auto:factory", ], ) @@ -38,8 +39,8 @@ GenJavaTests( lib_javacopts = SOURCE_7_TARGET_7, test_only_deps = [ "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/junit", + "//third_party/java/truth", ], # NOTE: This should not depend on Guava or jsr305 to ensure that Dagger can be # used without Guava and jsr305 deps. diff --git a/javatests/dagger/functional/assisted/kotlin/KotlinAssistedInjectionClasses.kt b/javatests/dagger/functional/assisted/kotlin/KotlinAssistedInjectionClasses.kt index e78873d69..18cc43d2e 100644 --- a/javatests/dagger/functional/assisted/kotlin/KotlinAssistedInjectionClasses.kt +++ b/javatests/dagger/functional/assisted/kotlin/KotlinAssistedInjectionClasses.kt @@ -21,6 +21,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import javax.inject.Inject + class Dep @Inject constructor() class AssistedDep @@ -29,7 +30,9 @@ class AssistedDep class Foo @AssistedInject constructor(val dep: Dep, @Assisted val assistedDep: AssistedDep) /** Assisted injection for a kotlin data class. */ -data class FooData @AssistedInject constructor(val dep: Dep, @Assisted val assistedDep: AssistedDep) +data class FooData +@AssistedInject +constructor(val dep: Dep, @Assisted val assistedDep: AssistedDep) /** Assisted factory for a kotlin class */ @AssistedFactory @@ -42,3 +45,15 @@ interface FooFactory { interface FooDataFactory { fun create(assistedDep: AssistedDep): FooData } + +/** Kotlin classes for regression test of https://github.com/google/dagger/issues/3065. */ +class BarManager +@AssistedInject +internal constructor(@Assisted val bar: Bar, @Assisted val name: String) { + @AssistedFactory + interface Factory { + operator fun Bar.invoke(name: String): BarManager + } +} + +class Bar diff --git a/javatests/dagger/functional/assisted/kotlin/KotlinAssistedInjectionTest.java b/javatests/dagger/functional/assisted/kotlin/KotlinAssistedInjectionTest.java index 7c597f858..b7a0a1b27 100644 --- a/javatests/dagger/functional/assisted/kotlin/KotlinAssistedInjectionTest.java +++ b/javatests/dagger/functional/assisted/kotlin/KotlinAssistedInjectionTest.java @@ -31,6 +31,9 @@ public final class KotlinAssistedInjectionTest { FooFactory fooFactory(); FooDataFactory fooDataFactory(); + + BarManager.Factory barManagerFactory(); + } @Test @@ -49,4 +52,15 @@ public final class KotlinAssistedInjectionTest { FooData fooData = fooDataFactory.create(assistedDep); assertThat(fooData.getAssistedDep()).isEqualTo(assistedDep); } + + @Test + public void testBarManager() { + BarManager.Factory barManagerFactory = + DaggerKotlinAssistedInjectionTest_TestComponent.create().barManagerFactory(); + Bar bar = new Bar(); + String name = "someName"; + BarManager barManager = barManagerFactory.invoke(bar, name); + assertThat(barManager.getBar()).isEqualTo(bar); + assertThat(barManager.getName()).isEqualTo(name); + } } diff --git a/javatests/dagger/functional/assisted/subpackage/BUILD b/javatests/dagger/functional/assisted/subpackage/BUILD index 644cf4410..8f0698417 100644 --- a/javatests/dagger/functional/assisted/subpackage/BUILD +++ b/javatests/dagger/functional/assisted/subpackage/BUILD @@ -22,6 +22,6 @@ java_library( srcs = glob(["*.java"]), deps = [ "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/jsr330_inject", ], ) diff --git a/javatests/dagger/functional/binds/AccessesExposedComponent.java b/javatests/dagger/functional/binds/AccessesExposedComponent.java index 61952b6b3..075ae9605 100644 --- a/javatests/dagger/functional/binds/AccessesExposedComponent.java +++ b/javatests/dagger/functional/binds/AccessesExposedComponent.java @@ -29,7 +29,7 @@ import javax.inject.Singleton; * accessible from the component, but the left-hand-side is. If the right-hand-side is represented * as a Provider (e.g. because it is scoped), then the raw {@code Provider.get()} will return {@link * Object}, which must be downcasted to the type accessible from the component. See {@code - * instanceRequiresCast()} in {@link dagger.internal.codegen.DelegateBindingExpression}. + * instanceRequiresCast()} in {@link dagger.internal.codegen.DelegateRequestRepresentation}. */ @Singleton @Component(modules = ExposedModule.class) diff --git a/javatests/dagger/functional/binds/BindsTest.java b/javatests/dagger/functional/binds/BindsTest.java index 999c30327..e89dee60a 100644 --- a/javatests/dagger/functional/binds/BindsTest.java +++ b/javatests/dagger/functional/binds/BindsTest.java @@ -52,6 +52,7 @@ public class BindsTest { assertThat(component.foosOfNumbers()).hasSize(2); assertThat(component.objects()).hasSize(3); assertThat(component.charSequences()).hasSize(5); + assertThat(component.notExposedString()).isEqualTo("not exposed"); assertThat(component.integerObjectMap()) .containsExactly(123, "123-string", 456, "456-string", 789, "789-string"); diff --git a/javatests/dagger/functional/binds/ScopedBindsTest.java b/javatests/dagger/functional/binds/ScopedBindsTest.java new file mode 100644 index 000000000..90feb8bbb --- /dev/null +++ b/javatests/dagger/functional/binds/ScopedBindsTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 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.functional.binds; + +import static com.google.common.truth.Truth.assertThat; + +import dagger.Binds; +import dagger.Component; +import dagger.Module; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ScopedBindsTest { + interface Foo {} + + static final class FooImpl implements Foo { + @Inject FooImpl() {} + } + + @Module + interface FooModule { + @Binds + @Singleton + Foo bindFoo(FooImpl impl); + } + + @Component(modules = FooModule.class) + @Singleton + interface TestComponent { + Foo foo(); + + Provider<Foo> fooProvider(); + } + + @Test + public void fooVsFooProvider_sameInstanceTest() { + TestComponent component = DaggerScopedBindsTest_TestComponent.create(); + + // These should be the same instance because Foo is scoped + assertThat(component.foo()).isSameInstanceAs(component.fooProvider().get()); + } +} diff --git a/javatests/dagger/functional/binds/TestComponent.java b/javatests/dagger/functional/binds/TestComponent.java index 3299dc832..5e459be2c 100644 --- a/javatests/dagger/functional/binds/TestComponent.java +++ b/javatests/dagger/functional/binds/TestComponent.java @@ -18,16 +18,19 @@ package dagger.functional.binds; import dagger.Component; import dagger.functional.SomeQualifier; +import dagger.functional.binds.subpackage.ExposedModule; import java.util.Map; import java.util.Set; import javax.inject.Provider; import javax.inject.Singleton; @Singleton -@Component(modules = SimpleBindingModule.class) +@Component(modules = {SimpleBindingModule.class, ExposedModule.class}) public interface TestComponent { Object object(); + String notExposedString(); + @SomeQualifier Object reusableObject(); diff --git a/javatests/dagger/functional/binds/subpackage/ExposedModule.java b/javatests/dagger/functional/binds/subpackage/ExposedModule.java index a2660d95c..5f428d9b6 100644 --- a/javatests/dagger/functional/binds/subpackage/ExposedModule.java +++ b/javatests/dagger/functional/binds/subpackage/ExposedModule.java @@ -19,8 +19,14 @@ package dagger.functional.binds.subpackage; import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.ElementsIntoSet; +import dagger.multibindings.IntoSet; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Set; +import javax.inject.Provider; import javax.inject.Singleton; @Module @@ -40,4 +46,24 @@ public abstract class ExposedModule { @Binds abstract ExposedInjectsMembers bindExposedInjectsMembers( NotExposedInjectsMembers notExposedInjectsMembers); + + @Provides + static Collection<NotExposed> provideNotExposedCollection(NotExposed notExposed) { + return Arrays.<NotExposed>asList(notExposed); + } + + @Provides + @IntoSet // This is needed to ensure a provider field gets created for providing the Collection. + static NotExposed provideNotExposed(Provider<Collection<NotExposed>> collectionProvider) { + return collectionProvider.get().iterator().next(); + } + + @Binds + @ElementsIntoSet + abstract Set<NotExposed> bindCollectionOfNotExposeds(Collection<NotExposed> collection); + + @Provides + static String provideString(Set<NotExposed> setOfFoo) { + return "not exposed"; + } } diff --git a/javatests/dagger/functional/guava/BUILD b/javatests/dagger/functional/guava/BUILD index ed8efd37e..157fd55a4 100644 --- a/javatests/dagger/functional/guava/BUILD +++ b/javatests/dagger/functional/guava/BUILD @@ -26,12 +26,12 @@ GenJavaTests( javacopts = DOCLINT_HTML_AND_SYNTAX, deps = [ "//:dagger_with_compiler", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/jsr305_annotations", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/auto:value", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/jsr305_annotations", + "//third_party/java/jsr330_inject", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/functional/jakarta/BUILD b/javatests/dagger/functional/jakarta/BUILD new file mode 100644 index 000000000..7fc1ec23e --- /dev/null +++ b/javatests/dagger/functional/jakarta/BUILD @@ -0,0 +1,34 @@ +# Copyright (C) 2022 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. + +# Description: +# Functional tests for Dagger that depend on jakarta.inject. + +load("//:build_defs.bzl", "DOCLINT_HTML_AND_SYNTAX") +load("//:test_defs.bzl", "GenJavaTests") + +package(default_visibility = ["//:src"]) + +# TODO(b/203233586): Replace with GenJavaTest +GenJavaTests( + name = "SimpleJakartaTest", + srcs = ["SimpleJakartaTest.java"], + javacopts = DOCLINT_HTML_AND_SYNTAX, + deps = [ + "//:dagger_with_compiler", + "@maven//:jakarta_inject_jakarta_inject_api", + "//third_party/java/junit", + "//third_party/java/truth", + ], +) diff --git a/javatests/dagger/functional/jakarta/SimpleJakartaTest.java b/javatests/dagger/functional/jakarta/SimpleJakartaTest.java new file mode 100644 index 000000000..e943eb0b8 --- /dev/null +++ b/javatests/dagger/functional/jakarta/SimpleJakartaTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 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.functional.jakarta; + +import static com.google.common.truth.Truth.assertThat; + +import dagger.Binds; +import dagger.Component; +import dagger.Module; +import jakarta.inject.Inject; +import jakarta.inject.Qualifier; +import jakarta.inject.Scope; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class SimpleJakartaTest { + + @Scope + public @interface TestScope {} + + @Qualifier + public @interface TestQualifier {} + + @TestScope + @Component(modules = TestModule.class) + interface TestComponent { + @TestQualifier Foo getQualifiedFoo(); + } + + public static final class Foo { + @Inject Foo() {} + } + + @Module + interface TestModule { + // By binding this to itself, if the qualifier annotation isn't picked up, it will created a + // cycle. + @Binds + @TestScope + @TestQualifier + Foo bind(Foo impl); + } + + @Test + public void testFooFactory() { + TestComponent testComponent = DaggerSimpleJakartaTest_TestComponent.create(); + Foo foo = testComponent.getQualifiedFoo(); + + assertThat(foo).isSameInstanceAs(testComponent.getQualifiedFoo()); + } +} diff --git a/javatests/dagger/functional/jdk8/BUILD b/javatests/dagger/functional/jdk8/BUILD index 5e35c753f..cc0c722c8 100644 --- a/javatests/dagger/functional/jdk8/BUILD +++ b/javatests/dagger/functional/jdk8/BUILD @@ -20,19 +20,53 @@ load("//:test_defs.bzl", "GenJavaTests") package(default_visibility = ["//:src"]) +java_library( + name = "optional_binding_components", + srcs = ["OptionalBindingComponents.java"], + javacopts = DOCLINT_HTML_AND_SYNTAX, + deps = [ + "//third_party/java/auto:value", + "//:dagger_with_compiler", + "//third_party/java/jsr305_annotations", + ], +) + +# TODO(b/203233586): Replace with GenJavaTest +GenJavaTests( + name = "OptionalBindingComponentsEmptyTest", + srcs = ["OptionalBindingComponentsEmptyTest.java"], + javacopts = DOCLINT_HTML_AND_SYNTAX, + test_only_deps = [ + ":optional_binding_components", + + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/junit", + "//third_party/java/truth", + ], + deps = [ + "//third_party/java/auto:value", + "//:dagger_with_compiler", + "//third_party/java/jsr305_annotations", + ], +) + +# TODO(b/203233586): Replace with GenJavaTest GenJavaTests( - name = "jdk8_tests", - srcs = glob(["**/*.java"]), + name = "OptionalBindingComponentsPresentTest", + srcs = ["OptionalBindingComponentsPresentTest.java"], javacopts = DOCLINT_HTML_AND_SYNTAX, test_only_deps = [ - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + ":optional_binding_components", + + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/junit", + "//third_party/java/truth", ], deps = [ + "//third_party/java/auto:value", "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/jsr305_annotations", + "//third_party/java/jsr305_annotations", ], ) diff --git a/javatests/dagger/functional/jdk8/a/BUILD b/javatests/dagger/functional/jdk8/a/BUILD new file mode 100644 index 000000000..f36417474 --- /dev/null +++ b/javatests/dagger/functional/jdk8/a/BUILD @@ -0,0 +1,41 @@ +# Copyright (C) 2021 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. + +load("//:build_defs.bzl", "DOCLINT_HTML_AND_SYNTAX") +load("//:test_defs.bzl", "GenJavaTests") + +package(default_visibility = ["//:src"]) + +# TODO(b/203233586): Replace with GenJavaTest +GenJavaTests( + name = "OptionalBindingComponentsEmptyTest", + srcs = [ + "OptionalBindingComponentsWithInaccessibleTypes.java", + "OptionalBindingComponentsWithInaccessibleTypesTest.java", + ], + javacopts = DOCLINT_HTML_AND_SYNTAX, + test_only_deps = [ + + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/junit", + "//third_party/java/truth", + ], + deps = [ + "//third_party/java/auto:value", + "//:dagger_with_compiler", + "//third_party/java/jsr305_annotations", + "//javatests/dagger/functional/jdk8:optional_binding_components", + ], +) diff --git a/javatests/dagger/functional/kotlin/BUILD b/javatests/dagger/functional/kotlin/BUILD index 529eb6992..2741c8e06 100644 --- a/javatests/dagger/functional/kotlin/BUILD +++ b/javatests/dagger/functional/kotlin/BUILD @@ -41,6 +41,7 @@ kt_jvm_library( ":java_qualifier", "//:dagger_with_compiler", "//javatests/dagger/functional/kotlin/processor:annotation", + "//third_party/java/auto:factory", ], ) @@ -66,10 +67,10 @@ GenJavaTests( srcs = glob(["*Test.java"]), functional = True, test_only_deps = [ - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/junit", + "//third_party/java/truth", ], deps = [ ":foo_with_injected_qualifier", diff --git a/javatests/dagger/functional/kotlin/DependsOnGeneratedCodeClasses.kt b/javatests/dagger/functional/kotlin/DependsOnGeneratedCodeClasses.kt new file mode 100644 index 000000000..6fc8e24ae --- /dev/null +++ b/javatests/dagger/functional/kotlin/DependsOnGeneratedCodeClasses.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 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.functional.kotlin + +import com.google.auto.factory.AutoFactory +import dagger.Component +import javax.inject.Inject + +// TODO(bcorso): Merge this into the test once we support kt_jvm_test +/** Defines kotlin classes for the associated test. */ +object DependsOnGeneratedCodeClasses { + @Component + abstract class TestComponent { + abstract fun bar(): Bar + } + + class Bar @Inject constructor( + ) + + @AutoFactory class SomeClass +} diff --git a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SimpleApplication.java b/javatests/dagger/functional/kotlin/DependsOnGeneratedCodeTest.java index a163d8c5e..d85aee826 100644 --- a/java/dagger/hilt/android/example/gradle/simple/app/src/main/java/dagger/hilt/android/example/gradle/simple/SimpleApplication.java +++ b/javatests/dagger/functional/kotlin/DependsOnGeneratedCodeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2021 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. @@ -14,19 +14,21 @@ * limitations under the License. */ -package dagger.hilt.android.example.gradle.simple; +package dagger.functional.kotlin; -import android.app.Application; -import dagger.hilt.android.HiltAndroidApp; -import javax.inject.Inject; +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; /** - * A simple, skeletal application that demonstrates a dependency-injected application using the - * utilities in {@code Hilt} in Android. + * @see <a href="https://github.com/google/dagger/issues/3075">Issue #3075</a> */ -@HiltAndroidApp -public class SimpleApplication extends Application { - - // Shows that we can inject SingletonComponent bindings into an application. - @Inject @Model String model; +@RunWith(JUnit4.class) +public class DependsOnGeneratedCodeTest { + @Test + public void testComponentDependsOnGeneratedCode() { + assertThat(DaggerDependsOnGeneratedCodeClasses_TestComponent.create().bar()).isNotNull(); + } } diff --git a/javatests/dagger/functional/kotlin/processor/BUILD b/javatests/dagger/functional/kotlin/processor/BUILD index 31a45c75a..25273e226 100644 --- a/javatests/dagger/functional/kotlin/processor/BUILD +++ b/javatests/dagger/functional/kotlin/processor/BUILD @@ -26,8 +26,8 @@ kt_jvm_library( name = "processor", srcs = ["TestGeneratedTypeProcessor.kt"], deps = [ - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/auto:service", + "//third_party/java/javapoet", ], ) diff --git a/javatests/dagger/functional/names/BUILD b/javatests/dagger/functional/names/BUILD new file mode 100644 index 000000000..5de751009 --- /dev/null +++ b/javatests/dagger/functional/names/BUILD @@ -0,0 +1,33 @@ +# Copyright (C) 2021 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. + +# Description: +# Functional tests for Dagger testing various name conflicts. + +load("//:build_defs.bzl", "DOCLINT_HTML_AND_SYNTAX") +load("//:test_defs.bzl", "GenJavaTests") + +package(default_visibility = ["//:src"]) + +# TODO(b/203233586): Replace with GenJavaTest +GenJavaTests( + name = "ComponentFactoryNameConflictsTest", + srcs = ["ComponentFactoryNameConflictsTest.java"], + javacopts = DOCLINT_HTML_AND_SYNTAX, + deps = [ + "//:dagger_with_compiler", + "//third_party/java/junit", + "//third_party/java/truth", + ], +) diff --git a/javatests/dagger/functional/names/ComponentFactoryNameConflictsTest.java b/javatests/dagger/functional/names/ComponentFactoryNameConflictsTest.java new file mode 100644 index 000000000..14acd0e5a --- /dev/null +++ b/javatests/dagger/functional/names/ComponentFactoryNameConflictsTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021 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.functional.names; + +import static com.google.common.truth.Truth.assertThat; + +import dagger.Component; +import javax.inject.Inject; +import javax.inject.Provider; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** This is a regression test for https://github.com/google/dagger/issues/3069. */ +@RunWith(JUnit4.class) +public final class ComponentFactoryNameConflictsTest { + + // A class name "Create" so the private method name in fastInit mode conflicts with the create() + // factory method. + public static final class Create { + // Take a dependency so we don't just inline this directly. + @Inject Create(Provider<CreateUsage> provider) {} + } + + // Use another class to make sure the create class is used but not a direct component entry point. + public static final class CreateUsage { + @Inject CreateUsage(Create create) {} + } + + @Component + interface CreateComponent { + CreateUsage getCreateUsage(); + } + + @Test + public void testCreate() { + CreateComponent testComponent = + DaggerComponentFactoryNameConflictsTest_CreateComponent.create(); + CreateUsage createUsage = testComponent.getCreateUsage(); + assertThat(createUsage).isNotNull(); + } + + // A class name "Builder" so the private method name in fastInit mode conflicts with the builder() + // factory method. + public static final class Builder { + // Take a dependency so we don't just inline this directly. + @Inject Builder(Provider<BuilderUsage> provider) {} + } + + public static final class BuilderUsage { + @Inject BuilderUsage(Builder create) {} + } + + @Component + interface BuilderComponent { + BuilderUsage getBuilderUsage(); + + @Component.Builder + interface OtherBuilder { + BuilderComponent build(); + } + } + + // Technically this test passes without claiming the name "builder" when we add the method (even + // though we do anyway for safety) because KeyVariableNamer actually hardcodes a list of common + // names to avoid which includes "builder". + @Test + public void testBuilder() { + BuilderComponent testComponent = + DaggerComponentFactoryNameConflictsTest_BuilderComponent.builder().build(); + BuilderUsage builderUsage = testComponent.getBuilderUsage(); + assertThat(builderUsage).isNotNull(); + } +} diff --git a/javatests/dagger/functional/producers/BUILD b/javatests/dagger/functional/producers/BUILD index f69b3cb85..6f2551e34 100644 --- a/javatests/dagger/functional/producers/BUILD +++ b/javatests/dagger/functional/producers/BUILD @@ -32,15 +32,15 @@ GenJavaTests( lib_javacopts = SOURCE_7_TARGET_7, deps = [ "//:producers_with_compiler", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/jsr305_annotations", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/mockito", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/auto:value", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/util/concurrent", + "//third_party/java/jsr305_annotations", + "//third_party/java/jsr330_inject", + "//third_party/java/junit", + "//third_party/java/mockito", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/functional/spi/BUILD b/javatests/dagger/functional/spi/BUILD index 8119bdd7f..dad3de668 100644 --- a/javatests/dagger/functional/spi/BUILD +++ b/javatests/dagger/functional/spi/BUILD @@ -24,11 +24,11 @@ java_plugin( name = "test_plugin", srcs = ["TestPlugin.java"], deps = [ - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", "//java/dagger/spi", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/javapoet", + "//third_party/java/auto:service", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", ], ) @@ -45,12 +45,12 @@ GenJavaTests( ), functional = 0, test_only_deps = [ - "@google_bazel_common//third_party/java/truth", - "@google_bazel_common//third_party/java/junit", + "//third_party/java/truth", + "//third_party/java/junit", ], deps = [ ":test_lib", "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/guava", + "//third_party/java/guava", ], ) diff --git a/javatests/dagger/functional/sub/NestedType.java b/javatests/dagger/functional/sub/NestedType.java new file mode 100644 index 000000000..999017a7d --- /dev/null +++ b/javatests/dagger/functional/sub/NestedType.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2021 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.functional.sub; + +// Move this back to dagger.functional package once the javapoet clash bug gets fixed. +// https://github.com/square/javapoet/issues/860 +public interface NestedType {} diff --git a/javatests/dagger/functional/tck/BUILD b/javatests/dagger/functional/tck/BUILD index 9cad2a017..ede975921 100644 --- a/javatests/dagger/functional/tck/BUILD +++ b/javatests/dagger/functional/tck/BUILD @@ -35,8 +35,8 @@ GenJavaTests( ], deps = [ "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/jsr330_inject:tck", - "@google_bazel_common//third_party/java/junit", + "//third_party/java/jsr330_inject", + "//third_party/java/jsr330_inject:tck", + "//third_party/java/junit", ], ) diff --git a/javatests/dagger/hilt/android/ActivityInjectedSavedStateViewModelTest.java b/javatests/dagger/hilt/android/ActivityInjectedSavedStateViewModelTest.java index 03aa3248d..e8ba0463f 100644 --- a/javatests/dagger/hilt/android/ActivityInjectedSavedStateViewModelTest.java +++ b/javatests/dagger/hilt/android/ActivityInjectedSavedStateViewModelTest.java @@ -19,13 +19,13 @@ package dagger.hilt.android; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.google.common.truth.Truth.assertThat; -import androidx.lifecycle.SavedStateHandle; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; import android.content.Intent; import android.os.Build; import android.os.Bundle; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.SavedStateHandle; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; import androidx.test.core.app.ActivityScenario; import androidx.test.ext.junit.runners.AndroidJUnit4; import dagger.Module; diff --git a/javatests/dagger/hilt/android/ActivityInjectedViewModelTest.java b/javatests/dagger/hilt/android/ActivityInjectedViewModelTest.java index 0e75741c4..5f88ca6af 100644 --- a/javatests/dagger/hilt/android/ActivityInjectedViewModelTest.java +++ b/javatests/dagger/hilt/android/ActivityInjectedViewModelTest.java @@ -19,11 +19,11 @@ package dagger.hilt.android; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.google.common.truth.Truth.assertThat; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; import android.content.Intent; import android.os.Build; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; import androidx.test.core.app.ActivityScenario; import androidx.test.ext.junit.runners.AndroidJUnit4; import dagger.Module; diff --git a/javatests/dagger/hilt/android/ActivityRetainedClearedListenerTest.java b/javatests/dagger/hilt/android/ActivityRetainedClearedListenerTest.java index 286998c19..4530a7249 100644 --- a/javatests/dagger/hilt/android/ActivityRetainedClearedListenerTest.java +++ b/javatests/dagger/hilt/android/ActivityRetainedClearedListenerTest.java @@ -19,9 +19,9 @@ package dagger.hilt.android; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; -import androidx.lifecycle.Lifecycle.State; import android.os.Build; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.Lifecycle.State; import androidx.test.core.app.ActivityScenario; import androidx.test.ext.junit.runners.AndroidJUnit4; import dagger.hilt.android.ActivityRetainedLifecycle.OnClearedListener; diff --git a/javatests/dagger/hilt/android/AliasOfMultipleScopesTest.java b/javatests/dagger/hilt/android/AliasOfMultipleScopesTest.java new file mode 100644 index 000000000..032d5464c --- /dev/null +++ b/javatests/dagger/hilt/android/AliasOfMultipleScopesTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import android.content.Context; +import android.os.Build; +import androidx.activity.ComponentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.DefineComponent; +import dagger.hilt.EntryPoint; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.android.components.ActivityComponent; +import dagger.hilt.android.qualifiers.ApplicationContext; +import dagger.hilt.android.scopes.ActivityScoped; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.components.SingletonComponent; +import dagger.hilt.migration.AliasOf; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Scope; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class AliasOfMultipleScopesTest { + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject @ApplicationContext Context context; + @Inject CustomComponent.Builder customComponentBuilder; + + @Scope + @Retention(CLASS) + @Target({ElementType.METHOD, ElementType.TYPE}) + public @interface CustomScoped {} + + @DefineComponent(parent = SingletonComponent.class) + @CustomScoped + public interface CustomComponent { + @DefineComponent.Builder + public interface Builder { + CustomComponent build(); + } + } + + @Scope + @AliasOf({ActivityScoped.class, CustomScoped.class}) + public @interface AliasScoped {} + + public interface UnscopedDep {} + + public interface ActivityScopedDep {} + + public interface CustomScopedDep {} + + public interface AliasScopedDep {} + + @Module + @InstallIn(SingletonComponent.class) + interface SingletonTestModule { + @Provides + static UnscopedDep unscopedDep() { + return new UnscopedDep() {}; + } + } + + @Module + @InstallIn(ActivityComponent.class) + interface ActivityTestModule { + @Provides + @ActivityScoped + static ActivityScopedDep activityScopedDep() { + return new ActivityScopedDep() {}; + } + + @Provides + @AliasScoped + static AliasScopedDep aliasScopedDep() { + return new AliasScopedDep() {}; + } + } + + @Module + @InstallIn(CustomComponent.class) + interface CustomTestModule { + @Provides + @CustomScoped + static CustomScopedDep customScopedDep() { + return new CustomScopedDep() {}; + } + + @Provides + @AliasScoped + static AliasScopedDep aliasScopedDep() { + return new AliasScopedDep() {}; + } + } + + /** An activity to test injection. */ + @AndroidEntryPoint(ComponentActivity.class) + public static final class TestActivity extends Hilt_AliasOfMultipleScopesTest_TestActivity { + @Inject Provider<UnscopedDep> unscopedDep; + @Inject Provider<ActivityScopedDep> activityScopedDep; + @Inject Provider<AliasScopedDep> aliasScopedDep; + } + + @EntryPoint + @InstallIn(SingletonComponent.class) + interface CustomComponentBuilderEntryPoint { + CustomComponent.Builder customComponentBuilder(); + } + + @EntryPoint + @InstallIn(CustomComponent.class) + interface CustomComponentEntryPoint { + Provider<UnscopedDep> unscopedDep(); + + Provider<CustomScopedDep> customScopedDep(); + + Provider<AliasScopedDep> aliasScopedDep(); + } + + @Before + public void setUp() { + rule.inject(); + } + + @Test + public void testActivityScoped() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.onActivity( + activity -> { + assertThat(activity.unscopedDep.get()).isNotSameInstanceAs(activity.unscopedDep.get()); + assertThat(activity.activityScopedDep.get()) + .isSameInstanceAs(activity.activityScopedDep.get()); + assertThat(activity.aliasScopedDep.get()) + .isSameInstanceAs(activity.aliasScopedDep.get()); + }); + } + } + + @Test + public void testCustomScoped() { + CustomComponent customComponent = + EntryPoints.get(context, CustomComponentBuilderEntryPoint.class) + .customComponentBuilder() + .build(); + CustomComponentEntryPoint entryPoint = + EntryPoints.get(customComponent, CustomComponentEntryPoint.class); + assertThat(entryPoint.unscopedDep().get()).isNotSameInstanceAs(entryPoint.unscopedDep().get()); + assertThat(entryPoint.customScopedDep().get()) + .isSameInstanceAs(entryPoint.customScopedDep().get()); + assertThat(entryPoint.aliasScopedDep().get()) + .isSameInstanceAs(entryPoint.aliasScopedDep().get()); + } +} diff --git a/javatests/dagger/hilt/android/AndroidManifest.xml b/javatests/dagger/hilt/android/AndroidManifest.xml index 7f6ede633..5195ca61c 100644 --- a/javatests/dagger/hilt/android/AndroidManifest.xml +++ b/javatests/dagger/hilt/android/AndroidManifest.xml @@ -1,4 +1,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" package="dagger.hilt.android"> <uses-sdk android:minSdkVersion="14" /> @@ -6,42 +7,71 @@ <application> <activity android:name=".ActivityInjectedSavedStateViewModelTest$TestActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> <activity android:name=".ActivityInjectedSavedStateViewModelTest$TestActivityWithSuperActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> <activity android:name=".ActivityInjectedViewModelTest$TestActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> <activity android:name=".ActivityRetainedClearedListenerTest$TestActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> <activity android:name=".ActivityScenarioRuleTest$TestActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> + <activity + android:name=".AliasOfMultipleScopesTest$TestActivity" + android:exported="false" + tools:ignore="MissingClass"/> <activity android:name=".DefaultViewModelFactoryTest$TestActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> + <activity + android:name=".OptionalInjectTestClasses$TestActivity" + android:exported="false" + tools:ignore="MissingClass"/> + <activity + android:name=".OptionalInjectTestClasses$NonOptionalSubclassActivity" + android:exported="false" + tools:ignore="MissingClass"/> + <activity + android:name=".OptionalInjectTestClasses$OptionalSubclassActivity" + android:exported="false" + tools:ignore="MissingClass"/> <activity android:name=".QualifierInKotlinFieldsTest$TestActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> <activity android:name=".UsesSharedComponent1Test$TestActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> <activity android:name=".UsesSharedComponent2Test$TestActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> <activity android:name=".UsesSharedComponentEnclosedTest$EnclosedTest$TestActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> <activity android:name=".ViewModelScopedTest$TestActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> <activity android:name=".ViewModelWithBaseTest$TestActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> <activity android:name="dagger.hilt.android.testsubpackage.UsesSharedComponent1Test$TestActivity" - android:exported="false"/> + android:exported="false" + tools:ignore="MissingClass"/> </application> </manifest> diff --git a/javatests/dagger/hilt/android/BUILD b/javatests/dagger/hilt/android/BUILD index 1f9bd0809..2aadb7d47 100644 --- a/javatests/dagger/hilt/android/BUILD +++ b/javatests/dagger/hilt/android/BUILD @@ -30,6 +30,7 @@ android_library( "MultiTestRootExternalModules.java", ], exports_manifest = 1, + javacopts = ["-Adagger.hilt.shareTestComponents=true"], manifest = "AndroidManifest.xml", deps = [ "//:android_local_test_exports", @@ -42,7 +43,7 @@ android_library( "//java/dagger/hilt/android/testing:custom_test_application", "//java/dagger/hilt/android/testing:hilt_android_test", "//java/dagger/hilt/android/testing:uninstall_modules", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/truth", "@maven//:androidx_test_core", "@maven//:androidx_test_ext_junit", "@maven//:junit_junit", @@ -91,6 +92,57 @@ android_local_test( ) android_local_test( + name = "AliasOfMultipleScopesTest", + srcs = ["AliasOfMultipleScopesTest.java"], + manifest = "AndroidManifest.xml", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//java/dagger/hilt:define_component", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/qualifiers", + "//java/dagger/hilt/android/scopes", + "//java/dagger/hilt/android/testing:hilt_android_rule", + "//java/dagger/hilt/android/testing:hilt_android_test", + "//java/dagger/hilt/migration:alias_of", + "//third_party/java/truth", + ], +) + +android_library( + name = "custom_inject_classes", + srcs = ["CustomInjectClasses.java"], + deps = [ + "//:dagger_with_compiler", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:hilt_android_app", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/migration:custom_inject", + "//third_party/java/jsr330_inject", + ], +) + +android_local_test( + name = "CustomInjectTest", + size = "small", + srcs = ["CustomInjectTest.java"], + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":custom_inject_classes", + "//:android_local_test_exports", + "//java/dagger/hilt/android:package_info", + "//third_party/java/truth", + ], +) + +android_local_test( name = "EarlyEntryPointHiltAndroidAppRuntimeTest", size = "small", srcs = ["EarlyEntryPointHiltAndroidAppRuntimeTest.java"], @@ -103,7 +155,7 @@ android_local_test( "//java/dagger/hilt:entry_point", "//java/dagger/hilt/android:early_entry_point", "//java/dagger/hilt/android:package_info", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/truth", "@maven//:junit_junit", ], ) @@ -138,7 +190,7 @@ android_local_test( "//java/dagger/hilt/android:early_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/testing:hilt_android_test", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/truth", ], ) @@ -161,7 +213,7 @@ android_local_test( "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/testing:custom_test_application", "//java/dagger/hilt/android/testing:hilt_android_test", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/truth", ], ) @@ -180,7 +232,27 @@ android_local_test( "//java/dagger/hilt/android:early_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/testing:hilt_android_test", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/truth", + ], +) + +android_local_test( + name = "FragmentContextOnAttachTest", + size = "small", + srcs = ["FragmentContextOnAttachTest.java"], + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/flags:fragment_get_context_fix", + "//java/dagger/hilt/android/testing:bind_value", + "//java/dagger/hilt/android/testing:hilt_android_test", + "//third_party/java/truth", ], ) @@ -203,11 +275,10 @@ android_local_test( }, deps = [ "//:android_local_test_exports", - "//:dagger_with_compiler", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:package_info", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/truth", ], ) @@ -223,8 +294,8 @@ android_local_test( "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/testing:hilt_android_test", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", ], ) @@ -242,8 +313,8 @@ android_local_test( "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/testing:hilt_android_test", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", ], ) @@ -261,8 +332,8 @@ android_local_test( "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/testing:hilt_android_test", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/junit", + "//third_party/java/truth", ], ) @@ -288,12 +359,11 @@ android_local_test( deps = [ "//:android_local_test_exports", "//:dagger_with_compiler", - "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/testing:hilt_android_test", "//javatests/dagger/hilt/testmodules", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/truth", ], ) @@ -307,15 +377,14 @@ android_local_test( deps = [ "//:android_local_test_exports", "//:dagger_with_compiler", - "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/lifecycle", "//java/dagger/hilt/android/testing:bind_value", "//java/dagger/hilt/android/testing:hilt_android_test", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", "@maven//:androidx_lifecycle_lifecycle_common", @@ -339,8 +408,8 @@ android_local_test( "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/testing:hilt_android_test", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", "@maven//:androidx_lifecycle_lifecycle_common", @@ -361,6 +430,75 @@ kt_android_library( ) android_local_test( + name = "OptionalInjectWithHiltTest", + size = "small", + srcs = [ + "OptionalInjectWithHiltTest.java", + ], + manifest = "AndroidManifest.xml", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":OptionalInjectTestClasses", + "//:android_local_test_exports", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/migration:optional_inject", + "//java/dagger/hilt/android/testing:hilt_android_test", + "//third_party/java/truth", + "@maven//:androidx_activity_activity", + "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", + ], +) + +android_local_test( + name = "OptionalInjectWithoutHiltTest", + size = "small", + srcs = [ + "OptionalInjectWithoutHiltTest.java", + ], + manifest = "AndroidManifest.xml", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":OptionalInjectTestClasses", + "//:android_local_test_exports", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/migration:optional_inject", + "//third_party/java/truth", + "@maven//:androidx_activity_activity", + "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", + ], +) + +android_library( + name = "OptionalInjectTestClasses", + srcs = ["OptionalInjectTestClasses.java"], + manifest = "AndroidManifest.xml", + deps = [ + "//:dagger_with_compiler", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/migration:optional_inject", + "//third_party/java/jsr330_inject", + "@maven//:androidx_activity_activity", + "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", + ], +) + +android_local_test( name = "ActivityRetainedClearedListenerTest", srcs = ["ActivityRetainedClearedListenerTest.java"], manifest = "AndroidManifest.xml", @@ -370,14 +508,13 @@ android_local_test( deps = [ "//:android_local_test_exports", "//:dagger_with_compiler", - "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:activity_retained_lifecycle", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/testing:hilt_android_test", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", "@maven//:androidx_lifecycle_lifecycle_common", @@ -397,13 +534,12 @@ android_local_test( deps = [ "//:android_local_test_exports", "//:dagger_with_compiler", - "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/testing:hilt_android_test", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", "@maven//:androidx_lifecycle_lifecycle_common", @@ -423,15 +559,14 @@ android_local_test( deps = [ "//:android_local_test_exports", "//:dagger_with_compiler", - "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/lifecycle", "//java/dagger/hilt/android/scopes", "//java/dagger/hilt/android/testing:hilt_android_test", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", "@maven//:androidx_lifecycle_lifecycle_common", @@ -451,14 +586,13 @@ android_local_test( deps = [ "//:android_local_test_exports", "//:dagger_with_compiler", - "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/lifecycle", "//java/dagger/hilt/android/testing:hilt_android_test", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", "@maven//:androidx_lifecycle_lifecycle_common", @@ -478,14 +612,12 @@ android_local_test( deps = [ "//:android_local_test_exports", "//:dagger_with_compiler", - "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/testing:hilt_android_test", - "//java/dagger/internal/guava:base-android", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "@maven//:junit_junit", ], ) @@ -502,7 +634,7 @@ android_library( "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android/components", - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/jsr330_inject", ], ) @@ -548,8 +680,8 @@ android_library( "//java/dagger/hilt/android/testing:custom_test_application", "//java/dagger/hilt/android/testing:hilt_android_test", "//java/dagger/hilt/android/testing:uninstall_modules", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "@maven//:androidx_test_core", "@maven//:androidx_test_ext_junit", "@maven//:junit_junit", @@ -578,8 +710,8 @@ android_library( "//java/dagger/hilt/android/testing:bind_value", "//java/dagger/hilt/android/testing:hilt_android_test", "//java/dagger/hilt/testing:test_install_in", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "@maven//:androidx_test_core", "@maven//:androidx_test_ext_junit", "@maven//:junit_junit", diff --git a/javatests/dagger/hilt/android/CustomInjectClasses.java b/javatests/dagger/hilt/android/CustomInjectClasses.java new file mode 100644 index 000000000..482b70bc9 --- /dev/null +++ b/javatests/dagger/hilt/android/CustomInjectClasses.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import android.app.Application; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.migration.CustomInject; +import dagger.hilt.android.migration.CustomInjection; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; + +/** + * Classes for CustomInjectTest. This is in a separate build target because otherwise + * robolectric does not recognize the application class as extending application due to order of + * class generation. + */ +final class CustomInjectClasses { + + @Module + @InstallIn(SingletonComponent.class) + static final class TestModule { + @Provides + static Integer provideInt() { + return 9; + } + } + + @CustomInject + @HiltAndroidApp(Application.class) + static final class TestApplication extends Hilt_CustomInjectClasses_TestApplication { + + @Inject Integer intValue; + + void inject() { + CustomInjection.inject(this); + } + } +} diff --git a/javatests/dagger/hilt/android/CustomInjectTest.java b/javatests/dagger/hilt/android/CustomInjectTest.java new file mode 100644 index 000000000..38512a037 --- /dev/null +++ b/javatests/dagger/hilt/android/CustomInjectTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.CustomInjectClasses.TestApplication; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** Tests for @CustomInject. */ +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config( + sdk = Build.VERSION_CODES.P, + application = TestApplication.class) +public class CustomInjectTest { + + @Test + public void testInjection() { + TestApplication app = (TestApplication) ApplicationProvider.getApplicationContext(); + + assertThat(app.intValue).isNull(); + app.inject(); + assertThat(app.intValue).isEqualTo(9); + } +} diff --git a/javatests/dagger/hilt/android/DefaultViewModelFactoryTest.java b/javatests/dagger/hilt/android/DefaultViewModelFactoryTest.java index 78cd5688a..0d59e24b7 100644 --- a/javatests/dagger/hilt/android/DefaultViewModelFactoryTest.java +++ b/javatests/dagger/hilt/android/DefaultViewModelFactoryTest.java @@ -18,11 +18,11 @@ package dagger.hilt.android; import static com.google.common.truth.Truth.assertThat; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; import android.os.Build; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; import androidx.test.core.app.ActivityScenario; import androidx.test.ext.junit.runners.AndroidJUnit4; import dagger.hilt.android.lifecycle.HiltViewModel; diff --git a/javatests/dagger/hilt/android/EntryPointAccessorsTest.kt b/javatests/dagger/hilt/android/EntryPointAccessorsTest.kt new file mode 100644 index 000000000..fb4afc077 --- /dev/null +++ b/javatests/dagger/hilt/android/EntryPointAccessorsTest.kt @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2021 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.hilt.android + +import android.content.Context +import android.os.Build +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import android.view.View +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth +import dagger.Module +import dagger.Provides +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.components.FragmentComponent +import dagger.hilt.android.components.ViewComponent +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.HiltTestApplication +import dagger.hilt.components.SingletonComponent +import javax.inject.Qualifier +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.Robolectric +import org.robolectric.annotation.Config + +@HiltAndroidTest +@RunWith(AndroidJUnit4::class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = [Build.VERSION_CODES.P], application = HiltTestApplication::class) +class EntryPointAccessorsTest { + + companion object { + const val APPLICATION_STRING = "APPLICATION_STRING" + const val ACTIVITY_STRING = "ACTIVITY_STRING" + const val FRAGMENT_STRING = "FRAGMENT_STRING" + const val VIEW_STRING = "VIEW_STRING" + } + + @get:Rule + var rule = HiltAndroidRule(this) + + @Qualifier + @Retention(AnnotationRetention.BINARY) + annotation class ApplicationLevel + + @Qualifier + @Retention(AnnotationRetention.BINARY) + annotation class ActivityLevel + + @Qualifier + @Retention(AnnotationRetention.BINARY) + annotation class FragmentLevel + + @Qualifier + @Retention(AnnotationRetention.BINARY) + annotation class ViewLevel + + @Module + @InstallIn(SingletonComponent::class) + internal object ApplicationModule { + @ApplicationLevel + @Provides + fun provideString(): String { + return APPLICATION_STRING + } + } + + @Module + @InstallIn(ActivityComponent::class) + internal object ActivityModule { + @ActivityLevel + @Provides + fun provideString(): String { + return ACTIVITY_STRING + } + } + + @Module + @InstallIn(FragmentComponent::class) + internal object FragmentModule { + @FragmentLevel + @Provides + fun provideString(): String { + return FRAGMENT_STRING + } + } + + @Module + @InstallIn(ViewComponent::class) + internal object ViewModule { + @ViewLevel + @Provides + fun provideString(): String { + return VIEW_STRING + } + } + + @EntryPoint + @InstallIn(SingletonComponent::class) + internal interface ApplicationEntryPoint { + @ApplicationLevel + fun getString(): String + } + + @EntryPoint + @InstallIn(ActivityComponent::class) + internal interface ActivityEntryPoint { + @ActivityLevel + fun getString(): String + } + + @EntryPoint + @InstallIn(FragmentComponent::class) + internal interface FragmentEntryPoint { + @FragmentLevel + fun getString(): String + } + + @EntryPoint + @InstallIn(ViewComponent::class) + internal interface ViewEntryPoint { + @ViewLevel + fun getString(): String + } + + @Test + fun testApplicationEntryPoint() { + val app = getApplicationContext<HiltTestApplication>() + val entryPoint = EntryPointAccessors.fromApplication<ApplicationEntryPoint>(app) + Truth.assertThat(entryPoint.getString()) + .isEqualTo(APPLICATION_STRING) + + val activity = Robolectric.buildActivity(TestActivity::class.java).setup().get() + val applicationEntryPoint = EntryPointAccessors.fromApplication<ApplicationEntryPoint>(activity) + Truth.assertThat(applicationEntryPoint.getString()) + .isEqualTo(APPLICATION_STRING) + } + + @Test + fun testActivityEntryPoint() { + val activity = Robolectric.buildActivity(TestActivity::class.java).setup().get() + val entryPoint = EntryPointAccessors.fromActivity<ActivityEntryPoint>(activity) + Truth.assertThat(entryPoint.getString()) + .isEqualTo(ACTIVITY_STRING) + } + + @Test + fun testFragmentEntryPoint() { + val activity = Robolectric.buildActivity(TestActivity::class.java).setup().get() + val fragment = TestFragment() + activity.supportFragmentManager.beginTransaction().add(fragment, "").commitNow() + val entryPoint = EntryPointAccessors.fromFragment<FragmentEntryPoint>(fragment) + Truth.assertThat(entryPoint.getString()) + .isEqualTo(FRAGMENT_STRING) + } + + @Test + fun testViewEntryPoint() { + val activity = Robolectric.buildActivity(TestActivity::class.java).setup().get() + val view = TestView(activity) + val entryPoint = EntryPointAccessors.fromView<ViewEntryPoint>(view) + Truth.assertThat(entryPoint.getString()) + .isEqualTo(VIEW_STRING) + } + + @AndroidEntryPoint(FragmentActivity::class) + class TestActivity : Hilt_EntryPointAccessorsTest_TestActivity() + + @AndroidEntryPoint(Fragment::class) + class TestFragment : Hilt_EntryPointAccessorsTest_TestFragment() + + @AndroidEntryPoint(View::class) + class TestView(context: Context) : Hilt_EntryPointAccessorsTest_TestView(context) +} diff --git a/javatests/dagger/hilt/android/FragmentContextOnAttachTest.java b/javatests/dagger/hilt/android/FragmentContextOnAttachTest.java new file mode 100644 index 000000000..19b3d05aa --- /dev/null +++ b/javatests/dagger/hilt/android/FragmentContextOnAttachTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.flags.FragmentGetContextFix; +import dagger.hilt.android.testing.BindValueIntoSet; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class FragmentContextOnAttachTest { + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @BindValueIntoSet + @FragmentGetContextFix.DisableFragmentGetContextFix + boolean disableGetContextFix = false; + + /** Hilt Activity */ + @AndroidEntryPoint(FragmentActivity.class) + public static final class TestActivity extends Hilt_FragmentContextOnAttachTest_TestActivity {} + + /** Hilt Fragment */ + @AndroidEntryPoint(Fragment.class) + public static final class TestFragment extends Hilt_FragmentContextOnAttachTest_TestFragment { + Context onAttachContextContext = null; + Context onAttachActivityContext = null; + + @Override + public void onAttach(Context context) { + // Test that getContext() can be called at this point + onAttachContextContext = getContext(); + super.onAttach(context); + } + + @Override + public void onAttach(Activity activity) { + // Test that getContext() can be called at this point + onAttachActivityContext = getContext(); + super.onAttach(activity); + } + } + + @Test + public void testGetContextAvailableBeforeSuperOnAttach() throws Exception { + FragmentActivity activity = Robolectric.setupActivity(TestActivity.class); + TestFragment fragment = new TestFragment(); + activity.getSupportFragmentManager().beginTransaction().add(fragment, "").commitNow(); + assertThat(fragment.onAttachContextContext).isNotNull(); + assertThat(fragment.onAttachActivityContext).isNotNull(); + } + + // Tests the behavior when using the useFragmentGetContextFix flag. + @Test + public void testGetContextReturnsNullAfterRemoval() throws Exception { + FragmentActivity activity = Robolectric.setupActivity(TestActivity.class); + TestFragment fragment = new TestFragment(); + activity.getSupportFragmentManager().beginTransaction().add(fragment, "").commitNow(); + assertThat(fragment.getContext()).isNotNull(); + activity.getSupportFragmentManager().beginTransaction().remove(fragment).commitNow(); + // This should be null since the fix was enabled by the compiler flag and runtime flag + assertThat(fragment.getContext()).isNull(); + + // Flip the flag so that we now disable the fix + disableGetContextFix = true; + TestFragment fragment2 = new TestFragment(); + activity.getSupportFragmentManager().beginTransaction().add(fragment2, "").commitNow(); + assertThat(fragment2.getContext()).isNotNull(); + activity.getSupportFragmentManager().beginTransaction().remove(fragment2).commitNow(); + // This should not be null since the fix was disabled by the runtime flag + assertThat(fragment2.getContext()).isNotNull(); + } +} diff --git a/javatests/dagger/hilt/android/ModuleTest.java b/javatests/dagger/hilt/android/ModuleTest.java index 0dadfe0ec..2a2462384 100644 --- a/javatests/dagger/hilt/android/ModuleTest.java +++ b/javatests/dagger/hilt/android/ModuleTest.java @@ -79,7 +79,7 @@ public final class ModuleTest { // constructor exists. @Module @InstallIn(SingletonComponent.class) - static final class TestModule3 { + public static final class TestModule3 { TestModule3() { this(""); } diff --git a/javatests/dagger/hilt/android/MultiTestRoot1Test.java b/javatests/dagger/hilt/android/MultiTestRoot1Test.java index 49c03bc75..36719b45e 100644 --- a/javatests/dagger/hilt/android/MultiTestRoot1Test.java +++ b/javatests/dagger/hilt/android/MultiTestRoot1Test.java @@ -150,7 +150,8 @@ public final class MultiTestRoot1Test { } } - static class Bar { + // Must be public due to b/183636779 + public static class Bar { final String value; Bar(String value) { @@ -166,7 +167,8 @@ public final class MultiTestRoot1Test { } } - static class Qux {} + // Must be public due to b/183636779 + public static class Qux {} @Module @InstallIn(SingletonComponent.class) @@ -277,10 +279,11 @@ public final class MultiTestRoot1Test { ClassCastException.class, () -> EntryPoints.get(getApplicationContext(), MultiTestRoot2Test.BarEntryPoint.class)); assertThat(exception) - .hasMessageThat() - .isEqualTo( - "Cannot cast dagger.hilt.android.DaggerMultiTestRoot1Test_HiltComponents_SingletonC" - + " to dagger.hilt.android.MultiTestRoot2Test$BarEntryPoint"); + .hasMessageThat() + .isEqualTo( + "Cannot cast dagger.hilt.android.internal.testing.root." + + "DaggerMultiTestRoot1Test_HiltComponents_SingletonC" + + " to dagger.hilt.android.MultiTestRoot2Test$BarEntryPoint"); } @Test diff --git a/javatests/dagger/hilt/android/MultiTestRoot2Test.java b/javatests/dagger/hilt/android/MultiTestRoot2Test.java index 39aecfa2e..ec660c429 100644 --- a/javatests/dagger/hilt/android/MultiTestRoot2Test.java +++ b/javatests/dagger/hilt/android/MultiTestRoot2Test.java @@ -121,7 +121,8 @@ public final class MultiTestRoot2Test { } } - static class Bar { + // Must be public due to b/183636779 + public static class Bar { final String value; Bar(String value) { @@ -137,7 +138,8 @@ public final class MultiTestRoot2Test { } } - static class Qux {} + // Must be public due to b/183636779 + public static class Qux {} @Module @InstallIn(SingletonComponent.class) @@ -255,10 +257,11 @@ public final class MultiTestRoot2Test { ClassCastException.class, () -> EntryPoints.get(getApplicationContext(), MultiTestRoot1Test.BarEntryPoint.class)); assertThat(exception) - .hasMessageThat() - .isEqualTo( - "Cannot cast dagger.hilt.android.DaggerMultiTestRoot2Test_HiltComponents_SingletonC" - + " to dagger.hilt.android.MultiTestRoot1Test$BarEntryPoint"); + .hasMessageThat() + .isEqualTo( + "Cannot cast dagger.hilt.android.internal.testing.root." + + "DaggerMultiTestRoot2Test_HiltComponents_SingletonC" + + " to dagger.hilt.android.MultiTestRoot1Test$BarEntryPoint"); } @Test diff --git a/javatests/dagger/hilt/android/OptionalInjectTestClasses.java b/javatests/dagger/hilt/android/OptionalInjectTestClasses.java new file mode 100644 index 000000000..78b52daa8 --- /dev/null +++ b/javatests/dagger/hilt/android/OptionalInjectTestClasses.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import android.app.IntentService; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import android.widget.LinearLayout; +import androidx.lifecycle.ViewModel; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.lifecycle.HiltViewModel; +import dagger.hilt.android.migration.OptionalInject; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import javax.inject.Qualifier; + +/** Test classes for optional injection. */ +public final class OptionalInjectTestClasses { + public static final String APP_BINDING = "app binding"; + public static final String ACTIVITY_BINDING = "activity binding"; + public static final String FRAGMENT_BINDING = "fragment binding"; + public static final String VIEW_BINDING = "view binding"; + + @Qualifier + @interface ActivityLevel {} + + @Qualifier + @interface FragmentLevel {} + + @Qualifier + @interface ViewLevel {} + + @AndroidEntryPoint(FragmentActivity.class) + @OptionalInject + public static class TestActivity extends Hilt_OptionalInjectTestClasses_TestActivity { + @Inject @ActivityLevel String testActivityBinding; + } + + @AndroidEntryPoint(TestActivity.class) + public static final class NonOptionalSubclassActivity + extends Hilt_OptionalInjectTestClasses_NonOptionalSubclassActivity { + @Inject @ActivityLevel String testActivitySubclassBinding; + } + + @AndroidEntryPoint(TestActivity.class) + @OptionalInject + public static final class OptionalSubclassActivity + extends Hilt_OptionalInjectTestClasses_OptionalSubclassActivity { + @Inject @ActivityLevel String testActivitySubclassBinding; + } + + @AndroidEntryPoint(Fragment.class) + @OptionalInject + public static final class TestFragment extends Hilt_OptionalInjectTestClasses_TestFragment { + @Inject @FragmentLevel String testFragmentBinding; + } + + @AndroidEntryPoint(LinearLayout.class) + @OptionalInject + public static final class TestView extends Hilt_OptionalInjectTestClasses_TestView { + @Inject @ViewLevel String testViewBinding; + + TestView(Context context) { + super(context); + } + } + + @WithFragmentBindings + @AndroidEntryPoint(LinearLayout.class) + @OptionalInject + public static final class TestWithFragmentBindingsView + extends Hilt_OptionalInjectTestClasses_TestWithFragmentBindingsView { + @Inject @ViewLevel String testViewBinding; + + TestWithFragmentBindingsView(Context context) { + super(context); + } + } + + @HiltViewModel + public static final class TestViewModel extends ViewModel { + final String appBinding; + + @Inject TestViewModel(String appBinding) { + this.appBinding = appBinding; + } + } + + public static final class NonHiltViewModel extends ViewModel {} + + @AndroidEntryPoint(Service.class) + @OptionalInject + public static final class TestService extends Hilt_OptionalInjectTestClasses_TestService { + @Inject String testAppBinding; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + } + + @AndroidEntryPoint(IntentService.class) + @OptionalInject + public static final class TestIntentService + extends Hilt_OptionalInjectTestClasses_TestIntentService { + TestIntentService() { + super("TestIntentService"); + } + + @Inject String testAppBinding; + + @Override + public void onHandleIntent(Intent intent) {} + } + + @AndroidEntryPoint(BroadcastReceiver.class) + @OptionalInject + public static final class TestBroadcastReceiver + extends Hilt_OptionalInjectTestClasses_TestBroadcastReceiver { + @Inject String testAppBinding; + } + + @Module + @InstallIn(SingletonComponent.class) + static final class AppModule { + @Provides + static String provideAppString() { + return APP_BINDING; + } + + @Provides + @ActivityLevel + static String provideActivityString() { + return ACTIVITY_BINDING; + } + + @Provides + @FragmentLevel + static String provideFragmentString() { + return FRAGMENT_BINDING; + } + + @Provides + @ViewLevel + static String provideViewString() { + return VIEW_BINDING; + } + } +} diff --git a/javatests/dagger/hilt/android/OptionalInjectWithHiltTest.java b/javatests/dagger/hilt/android/OptionalInjectWithHiltTest.java new file mode 100644 index 000000000..a8fedf6d3 --- /dev/null +++ b/javatests/dagger/hilt/android/OptionalInjectWithHiltTest.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; +import static dagger.hilt.android.OptionalInjectTestClasses.ACTIVITY_BINDING; +import static dagger.hilt.android.OptionalInjectTestClasses.APP_BINDING; +import static dagger.hilt.android.OptionalInjectTestClasses.FRAGMENT_BINDING; +import static dagger.hilt.android.OptionalInjectTestClasses.VIEW_BINDING; +import static dagger.hilt.android.migration.OptionalInjectCheck.wasInjectedByHilt; +import static org.junit.Assert.assertThrows; + +import android.content.Intent; +import android.os.Build; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModelProvider; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.OptionalInjectTestClasses.NonHiltViewModel; +import dagger.hilt.android.OptionalInjectTestClasses.NonOptionalSubclassActivity; +import dagger.hilt.android.OptionalInjectTestClasses.OptionalSubclassActivity; +import dagger.hilt.android.OptionalInjectTestClasses.TestActivity; +import dagger.hilt.android.OptionalInjectTestClasses.TestBroadcastReceiver; +import dagger.hilt.android.OptionalInjectTestClasses.TestFragment; +import dagger.hilt.android.OptionalInjectTestClasses.TestIntentService; +import dagger.hilt.android.OptionalInjectTestClasses.TestService; +import dagger.hilt.android.OptionalInjectTestClasses.TestView; +import dagger.hilt.android.OptionalInjectTestClasses.TestViewModel; +import dagger.hilt.android.OptionalInjectTestClasses.TestWithFragmentBindingsView; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.annotation.Config; + +/** Tests that optional inject works with a Hilt root. */ +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class OptionalInjectWithHiltTest { + @Rule public final HiltAndroidRule rules = new HiltAndroidRule(this); + + @Test + public void testActivityInjection() throws Exception { + TestActivity testActivity = Robolectric.setupActivity(TestActivity.class); + assertThat(testActivity.testActivityBinding).isEqualTo(ACTIVITY_BINDING); + assertThat(testActivity.wasInjectedByHilt()).isTrue(); + assertThat(wasInjectedByHilt(testActivity)).isTrue(); + } + + @Test + public void testNonOptionalSubclassActivityInjection() throws Exception { + NonOptionalSubclassActivity testActivity = Robolectric.setupActivity( + NonOptionalSubclassActivity.class); + assertThat(testActivity.testActivityBinding).isEqualTo(ACTIVITY_BINDING); + assertThat(testActivity.testActivitySubclassBinding).isEqualTo(ACTIVITY_BINDING); + assertThat(testActivity.wasInjectedByHilt()).isTrue(); + assertThat(wasInjectedByHilt(testActivity)).isTrue(); + } + + @Test + public void testOptionalSubclassActivityInjection() throws Exception { + OptionalSubclassActivity testActivity = Robolectric.setupActivity( + OptionalSubclassActivity.class); + assertThat(testActivity.testActivityBinding).isEqualTo(ACTIVITY_BINDING); + assertThat(testActivity.testActivitySubclassBinding).isEqualTo(ACTIVITY_BINDING); + assertThat(testActivity.wasInjectedByHilt()).isTrue(); + assertThat(wasInjectedByHilt(testActivity)).isTrue(); + } + + @Test + public void testFragmentInjection() throws Exception { + TestActivity testActivity = Robolectric.setupActivity(TestActivity.class); + TestFragment testFragment = new TestFragment(); + testActivity.getSupportFragmentManager() + .beginTransaction() + .add(testFragment, null) + .commitNow(); + assertThat(testFragment.testFragmentBinding).isEqualTo(FRAGMENT_BINDING); + assertThat(testFragment.wasInjectedByHilt()).isTrue(); + assertThat(wasInjectedByHilt(testFragment)).isTrue(); + } + + @Test + public void testFragmentInjectionWithNonHiltActivityWithHiltRoot() throws Exception { + FragmentActivity testActivity = Robolectric.setupActivity(FragmentActivity.class); + TestFragment testFragment = new TestFragment(); + testActivity.getSupportFragmentManager() + .beginTransaction() + .add(testFragment, null) + .commitNow(); + assertThat(testFragment.testFragmentBinding).isNull(); + assertThat(testFragment.wasInjectedByHilt()).isFalse(); + assertThat(wasInjectedByHilt(testFragment)).isFalse(); + } + + @Test + public void testViewInjection() throws Exception { + TestActivity testActivity = Robolectric.setupActivity(TestActivity.class); + TestView testView = new TestView(testActivity); + assertThat(testView.testViewBinding).isEqualTo(VIEW_BINDING); + assertThat(testView.wasInjectedByHilt()).isTrue(); + assertThat(wasInjectedByHilt(testView)).isTrue(); + } + + @Test + public void testViewInjectionWithNonHiltActivityWithHiltRoot() throws Exception { + FragmentActivity testActivity = Robolectric.setupActivity(FragmentActivity.class); + TestView testView = new TestView(testActivity); + assertThat(testView.testViewBinding).isNull(); + assertThat(testView.wasInjectedByHilt()).isFalse(); + assertThat(wasInjectedByHilt(testView)).isFalse(); + } + + @Test + public void testViewWithFragmentBindingsInjection() throws Exception { + TestActivity testActivity = Robolectric.setupActivity(TestActivity.class); + TestFragment testFragment = new TestFragment(); + testActivity.getSupportFragmentManager() + .beginTransaction() + .add(testFragment, null) + .commitNow(); + + TestWithFragmentBindingsView testView = new TestWithFragmentBindingsView( + testFragment.getLayoutInflater().getContext()); + assertThat(testView.testViewBinding).isEqualTo(VIEW_BINDING); + assertThat(testView.wasInjectedByHilt()).isTrue(); + assertThat(wasInjectedByHilt(testView)).isTrue(); + } + + @Test + public void testViewWithFragmentBindingsInjectionWithNonHiltFragmentWithHiltRoot() + throws Exception { + TestActivity testActivity = Robolectric.setupActivity(TestActivity.class); + Fragment testFragment = new Fragment(); + testActivity.getSupportFragmentManager() + .beginTransaction() + .add(testFragment, null) + .commitNow(); + + TestWithFragmentBindingsView testView = new TestWithFragmentBindingsView( + testFragment.getLayoutInflater().getContext()); + assertThat(testView.testViewBinding).isNull(); + assertThat(testView.wasInjectedByHilt()).isFalse(); + assertThat(wasInjectedByHilt(testView)).isFalse(); + } + + @Test + public void testHiltViewModels() { + TestActivity testActivity = Robolectric.setupActivity(TestActivity.class); + TestFragment testFragment = new TestFragment(); + testActivity.getSupportFragmentManager() + .beginTransaction() + .add(testFragment, null) + .commitNow(); + assertThat(new ViewModelProvider(testActivity).get(TestViewModel.class).appBinding) + .isEqualTo(APP_BINDING); + assertThat(new ViewModelProvider(testActivity).get(NonHiltViewModel.class)).isNotNull(); + assertThat(new ViewModelProvider(testFragment).get(TestViewModel.class).appBinding) + .isEqualTo(APP_BINDING); + assertThat(new ViewModelProvider(testFragment).get(NonHiltViewModel.class)).isNotNull(); + } + + @Test + public void testHiltViewModelsWithNonHiltActivityWithHiltRoot() throws Exception { + FragmentActivity testActivity = Robolectric.setupActivity(FragmentActivity.class); + TestFragment testFragment = new TestFragment(); + testActivity.getSupportFragmentManager() + .beginTransaction() + .add(testFragment, null) + .commitNow(); + assertThat(new ViewModelProvider(testActivity).get(NonHiltViewModel.class)).isNotNull(); + assertThat(new ViewModelProvider(testFragment).get(NonHiltViewModel.class)).isNotNull(); + + // Hilt View Models aren't usable in this case, so check that it throws. We only test with the + // owner as the fragment since the activity is just a plain FragmentActivity. + RuntimeException exception = + assertThrows( + RuntimeException.class, + () -> new ViewModelProvider(testFragment).get(TestViewModel.class)); + assertThat(exception) + .hasMessageThat() + .contains("TestViewModel"); + } + + @Test + public void testServiceInjection() throws Exception { + TestService testService = Robolectric.setupService(TestService.class); + assertThat(testService.testAppBinding).isEqualTo(APP_BINDING); + assertThat(testService.wasInjectedByHilt()).isTrue(); + assertThat(wasInjectedByHilt(testService)).isTrue(); + } + + @Test + public void testIntentServiceInjection() throws Exception { + TestIntentService testIntentService = Robolectric.setupService(TestIntentService.class); + assertThat(testIntentService.testAppBinding).isEqualTo(APP_BINDING); + assertThat(testIntentService.wasInjectedByHilt()).isTrue(); + assertThat(wasInjectedByHilt(testIntentService)).isTrue(); + } + + @Test + public void testBroadcastReceiverInjection() throws Exception { + TestBroadcastReceiver testBroadcastReceiver = new TestBroadcastReceiver(); + Intent intent = new Intent(); + testBroadcastReceiver.onReceive(ApplicationProvider.getApplicationContext(), intent); + assertThat(testBroadcastReceiver.testAppBinding).isEqualTo(APP_BINDING); + assertThat(testBroadcastReceiver.wasInjectedByHilt()).isTrue(); + assertThat(wasInjectedByHilt(testBroadcastReceiver)).isTrue(); + } +} diff --git a/javatests/dagger/hilt/android/OptionalInjectWithoutHiltTest.java b/javatests/dagger/hilt/android/OptionalInjectWithoutHiltTest.java new file mode 100644 index 000000000..fb516486d --- /dev/null +++ b/javatests/dagger/hilt/android/OptionalInjectWithoutHiltTest.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; +import static dagger.hilt.android.migration.OptionalInjectCheck.wasInjectedByHilt; +import static org.junit.Assert.assertThrows; + +import android.content.Intent; +import android.os.Build; +import androidx.lifecycle.ViewModelProvider; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.OptionalInjectTestClasses.NonHiltViewModel; +import dagger.hilt.android.OptionalInjectTestClasses.OptionalSubclassActivity; +import dagger.hilt.android.OptionalInjectTestClasses.TestActivity; +import dagger.hilt.android.OptionalInjectTestClasses.TestBroadcastReceiver; +import dagger.hilt.android.OptionalInjectTestClasses.TestFragment; +import dagger.hilt.android.OptionalInjectTestClasses.TestIntentService; +import dagger.hilt.android.OptionalInjectTestClasses.TestService; +import dagger.hilt.android.OptionalInjectTestClasses.TestView; +import dagger.hilt.android.OptionalInjectTestClasses.TestViewModel; +import dagger.hilt.android.OptionalInjectTestClasses.TestWithFragmentBindingsView; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.annotation.Config; + +/** Tests that optional inject work without a Hilt root. */ +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P) +public final class OptionalInjectWithoutHiltTest { + @Test + public void testActivityInjection() throws Exception { + TestActivity testActivity = Robolectric.setupActivity(TestActivity.class); + assertThat(testActivity.testActivityBinding).isNull(); + assertThat(testActivity.wasInjectedByHilt()).isFalse(); + assertThat(wasInjectedByHilt(testActivity)).isFalse(); + } + + @Test + public void testOptionalSubclassActivityInjection() throws Exception { + OptionalSubclassActivity testActivity = Robolectric.setupActivity( + OptionalSubclassActivity.class); + assertThat(testActivity.testActivityBinding).isNull(); + assertThat(testActivity.testActivitySubclassBinding).isNull(); + assertThat(testActivity.wasInjectedByHilt()).isFalse(); + assertThat(wasInjectedByHilt(testActivity)).isFalse(); + } + + @Test + public void testFragmentInjection() throws Exception { + TestActivity testActivity = Robolectric.setupActivity(TestActivity.class); + TestFragment testFragment = new TestFragment(); + testActivity.getSupportFragmentManager() + .beginTransaction() + .add(testFragment, null) + .commitNow(); + assertThat(testFragment.testFragmentBinding).isNull(); + assertThat(testFragment.wasInjectedByHilt()).isFalse(); + assertThat(wasInjectedByHilt(testFragment)).isFalse(); + } + + @Test + public void testViewInjection() throws Exception { + TestActivity testActivity = Robolectric.setupActivity(TestActivity.class); + TestView testView = new TestView(testActivity); + assertThat(testView.testViewBinding).isNull(); + assertThat(testView.wasInjectedByHilt()).isFalse(); + assertThat(wasInjectedByHilt(testView)).isFalse(); + } + + @Test + public void testViewWithFragmentBindingsInjection() throws Exception { + TestActivity testActivity = Robolectric.setupActivity(TestActivity.class); + TestFragment testFragment = new TestFragment(); + testActivity.getSupportFragmentManager() + .beginTransaction() + .add(testFragment, null) + .commitNow(); + + TestWithFragmentBindingsView testView = new TestWithFragmentBindingsView( + testFragment.getLayoutInflater().getContext()); + assertThat(testView.testViewBinding).isNull(); + assertThat(testView.wasInjectedByHilt()).isFalse(); + assertThat(wasInjectedByHilt(testView)).isFalse(); + } + + @Test + public void testViewModels() { + TestActivity testActivity = Robolectric.setupActivity(TestActivity.class); + TestFragment testFragment = new TestFragment(); + testActivity.getSupportFragmentManager() + .beginTransaction() + .add(testFragment, null) + .commitNow(); + assertThat(new ViewModelProvider(testActivity).get(NonHiltViewModel.class)).isNotNull(); + assertThat(new ViewModelProvider(testFragment).get(NonHiltViewModel.class)).isNotNull(); + + // Hilt View Models aren't usable in this case, so check that it throws. + RuntimeException activityException = + assertThrows( + RuntimeException.class, + () -> new ViewModelProvider(testFragment).get(TestViewModel.class)); + assertThat(activityException) + .hasMessageThat() + .contains("TestViewModel"); + RuntimeException fragmentException = + assertThrows( + RuntimeException.class, + () -> new ViewModelProvider(testFragment).get(TestViewModel.class)); + assertThat(fragmentException) + .hasMessageThat() + .contains("TestViewModel"); + } + + @Test + public void testServiceInjection() throws Exception { + TestService testService = Robolectric.setupService(TestService.class); + assertThat(testService.testAppBinding).isNull(); + assertThat(testService.wasInjectedByHilt()).isFalse(); + assertThat(wasInjectedByHilt(testService)).isFalse(); + } + + @Test + public void testIntentServiceInjection() throws Exception { + TestIntentService testIntentService = Robolectric.setupService(TestIntentService.class); + assertThat(testIntentService.testAppBinding).isNull(); + assertThat(testIntentService.wasInjectedByHilt()).isFalse(); + assertThat(wasInjectedByHilt(testIntentService)).isFalse(); + } + + @Test + public void testBroadcastReceiverInjection() throws Exception { + TestBroadcastReceiver testBroadcastReceiver = new TestBroadcastReceiver(); + Intent intent = new Intent(); + testBroadcastReceiver.onReceive(ApplicationProvider.getApplicationContext(), intent); + assertThat(testBroadcastReceiver.testAppBinding).isNull(); + assertThat(testBroadcastReceiver.wasInjectedByHilt()).isFalse(); + assertThat(wasInjectedByHilt(testBroadcastReceiver)).isFalse(); + } +} diff --git a/javatests/dagger/hilt/android/ViewModelScopedTest.java b/javatests/dagger/hilt/android/ViewModelScopedTest.java index e37e42ac7..bc8212650 100644 --- a/javatests/dagger/hilt/android/ViewModelScopedTest.java +++ b/javatests/dagger/hilt/android/ViewModelScopedTest.java @@ -18,13 +18,13 @@ package dagger.hilt.android; import static com.google.common.truth.Truth.assertThat; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; import android.os.Build; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; import androidx.test.core.app.ActivityScenario; import androidx.test.ext.junit.runners.AndroidJUnit4; import dagger.hilt.android.lifecycle.HiltViewModel; diff --git a/javatests/dagger/hilt/android/ViewModelWithBaseTest.java b/javatests/dagger/hilt/android/ViewModelWithBaseTest.java index 4e4a47c0b..292fd1605 100644 --- a/javatests/dagger/hilt/android/ViewModelWithBaseTest.java +++ b/javatests/dagger/hilt/android/ViewModelWithBaseTest.java @@ -18,13 +18,13 @@ package dagger.hilt.android; import static com.google.common.truth.Truth.assertThat; -import androidx.lifecycle.SavedStateHandle; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; import android.os.Build; import android.os.Bundle; import androidx.fragment.app.FragmentActivity; import androidx.annotation.Nullable; +import androidx.lifecycle.SavedStateHandle; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; import androidx.test.core.app.ActivityScenario; import androidx.test.ext.junit.runners.AndroidJUnit4; import dagger.hilt.android.lifecycle.HiltViewModel; diff --git a/javatests/dagger/hilt/android/internal/managers/BUILD b/javatests/dagger/hilt/android/internal/managers/BUILD index ff4f432f7..b7fe47c83 100644 --- a/javatests/dagger/hilt/android/internal/managers/BUILD +++ b/javatests/dagger/hilt/android/internal/managers/BUILD @@ -27,10 +27,8 @@ android_local_test( deps = [ "//:android_local_test_exports", "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/jsr330_inject", "@maven//:junit_junit", - "@google_bazel_common//third_party/java/truth", - "//java/dagger/hilt:entry_point", + "//third_party/java/truth", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android/lifecycle", diff --git a/javatests/dagger/hilt/android/internal/managers/FragmentContextWrapperLeakTest.java b/javatests/dagger/hilt/android/internal/managers/FragmentContextWrapperLeakTest.java index 38086c6aa..116d2d943 100644 --- a/javatests/dagger/hilt/android/internal/managers/FragmentContextWrapperLeakTest.java +++ b/javatests/dagger/hilt/android/internal/managers/FragmentContextWrapperLeakTest.java @@ -19,10 +19,10 @@ package dagger.hilt.android.internal.managers; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; -import androidx.lifecycle.Lifecycle; import android.os.Build; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.Lifecycle; import androidx.test.core.app.ActivityScenario; import androidx.test.ext.junit.runners.AndroidJUnit4; import dagger.hilt.android.AndroidEntryPoint; diff --git a/javatests/dagger/hilt/android/processor/internal/BUILD b/javatests/dagger/hilt/android/processor/internal/BUILD index 9a0a8f2a0..513173f15 100644 --- a/javatests/dagger/hilt/android/processor/internal/BUILD +++ b/javatests/dagger/hilt/android/processor/internal/BUILD @@ -28,9 +28,9 @@ compiler_test( "@maven//:androidx_annotation_annotation", ], deps = [ - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", - "//javatests/dagger/hilt/android/processor:android_compilers", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", ], ) diff --git a/javatests/dagger/hilt/android/processor/internal/GeneratorsTest.java b/javatests/dagger/hilt/android/processor/internal/GeneratorsTest.java index ecfd1f6aa..4dfa87c5d 100644 --- a/javatests/dagger/hilt/android/processor/internal/GeneratorsTest.java +++ b/javatests/dagger/hilt/android/processor/internal/GeneratorsTest.java @@ -17,7 +17,7 @@ package dagger.hilt.android.processor.internal; import static com.google.testing.compile.CompilationSubject.assertThat; -import static dagger.hilt.android.processor.AndroidCompilers.compiler; +import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; @@ -174,7 +174,6 @@ public final class GeneratorsTest { " public MyView(Context context, AttributeSet attributeSet){", " super(context, attributeSet);", " }", - "", "}"); Compilation compilation = compiler().compile(myView); assertThat(compilation).succeeded(); @@ -247,7 +246,6 @@ public final class GeneratorsTest { "", "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.FragmentGenerator\")", "@TargetApi(24)", - "@SuppressWarnings(\"deprecation\")", "abstract class Hilt_MyFragment extends Fragment implements" + " GeneratedComponentManagerHolder {}")); } @@ -315,4 +313,193 @@ public final class GeneratorsTest { "abstract class Hilt_MyService extends Service implements" + " GeneratedComponentManagerHolder{}")); } + + @Test + public void copySuppressWarningsAnnotationActivity_annotationCopied() { + JavaFileObject myActivity = + JavaFileObjects.forSourceLines( + "test.MyActivity", + "package test;", + "", + "import android.annotation.TargetApi;", + "import androidx.fragment.app.FragmentActivity;", + "import dagger.hilt.android.AndroidEntryPoint;", + "", + "@SuppressWarnings(\"deprecation\")", + "@AndroidEntryPoint(FragmentActivity.class)", + "public class MyActivity extends Hilt_MyActivity {}"); + Compilation compilation = compiler().compile(myActivity); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyActivity") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyActivity", + " package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator\")", + "@SuppressWarnings(\"deprecation\")", + "abstract class Hilt_MyActivity extends FragmentActivity ", + "implements GeneratedComponentManagerHolder {", + "}")); + } + + @Test + public void copySuppressWarningsAnnotation_onView_annotationCopied() { + JavaFileObject myView = + JavaFileObjects.forSourceLines( + "test.MyView", + "package test;", + "", + "import android.annotation.TargetApi;", + "import android.widget.LinearLayout;", + "import android.content.Context;", + "import android.util.AttributeSet;", + "import dagger.hilt.android.AndroidEntryPoint;", + "", + "@SuppressWarnings(\"deprecation\")", + "@AndroidEntryPoint(LinearLayout.class)", + "public class MyView extends Hilt_MyView {", + " public MyView(Context context, AttributeSet attributeSet){", + " super(context, attributeSet);", + " }", + "}"); + Compilation compilation = compiler().compile(myView); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyView") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyView", + "", + "package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ViewGenerator\")", + "@SuppressWarnings(\"deprecation\")", + "abstract class Hilt_MyView extends LinearLayout implements" + + " GeneratedComponentManagerHolder {", + "}")); + } + + @Test + public void copySuppressWarningsAnnotation_onApplication_annotationCopied() { + JavaFileObject myApplication = + JavaFileObjects.forSourceLines( + "test.MyApplication", + "package test;", + "", + "import android.annotation.TargetApi;", + "import android.app.Application;", + "import dagger.hilt.android.HiltAndroidApp;", + "", + "@SuppressWarnings(\"deprecation\")", + "@HiltAndroidApp(Application.class)", + "public class MyApplication extends Hilt_MyApplication {}"); + Compilation compilation = compiler().compile(myApplication); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyApplication") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyApplication", + " package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator\")", + "@SuppressWarnings(\"deprecation\")", + "abstract class Hilt_MyApplication extends Application implements" + + " GeneratedComponentManagerHolder {}")); + } + + @Test + public void copySuppressWarningsAnnotation_onFragment_annotationCopied() { + JavaFileObject myApplication = + JavaFileObjects.forSourceLines( + "test.MyFragment", + "package test;", + "", + "import android.annotation.TargetApi;", + "import androidx.fragment.app.Fragment;", + "import dagger.hilt.android.AndroidEntryPoint;", + "", + "@SuppressWarnings(\"rawtypes\")", + "@AndroidEntryPoint(Fragment.class)", + "public class MyFragment extends Hilt_MyFragment {}"); + Compilation compilation = compiler().compile(myApplication); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyFragment") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyFragment", + "package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.FragmentGenerator\")", + "@SuppressWarnings(\"rawtypes\")", + "abstract class Hilt_MyFragment extends Fragment implements" + + " GeneratedComponentManagerHolder {}")); + } + + @Test + public void copySuppressWarnings_onBroadcastRecieverGenerator_annotationCopied() { + JavaFileObject myBroadcastReceiver = + JavaFileObjects.forSourceLines( + "test.MyBroadcastReceiver", + "package test;", + "", + "import android.content.BroadcastReceiver;", + "import android.annotation.TargetApi;", + "import dagger.hilt.android.AndroidEntryPoint;", + "", + "@SuppressWarnings(\"deprecation\")", + "@AndroidEntryPoint(BroadcastReceiver.class)", + "public class MyBroadcastReceiver extends Hilt_MyBroadcastReceiver {}"); + Compilation compilation = compiler().compile(myBroadcastReceiver); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyBroadcastReceiver") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyBroadcastReceiver", + "package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.BroadcastReceiverGenerator\")", + "@SuppressWarnings(\"deprecation\")", + "abstract class Hilt_MyBroadcastReceiver extends BroadcastReceiver {}")); + } + + @Test + public void copySuppressWarnings_onServiceGenerator_annotationCopied() { + JavaFileObject myService = + JavaFileObjects.forSourceLines( + "test.MyService", + "package test;", + "", + "import android.annotation.TargetApi;", + "import android.content.Intent;", + "import android.app.Service;", + "import android.os.IBinder;", + "import dagger.hilt.android.AndroidEntryPoint;", + "", + "@SuppressWarnings(\"deprecation\")", + "@AndroidEntryPoint(Service.class)", + "public class MyService extends Hilt_MyService {", + " @Override", + " public IBinder onBind(Intent intent){", + " return null;", + " }", + "}"); + Compilation compilation = compiler().compile(myService); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyService") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyService", + "package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ServiceGenerator\")", + "@SuppressWarnings(\"deprecation\")", + "abstract class Hilt_MyService extends Service implements" + + " GeneratedComponentManagerHolder{}")); + } } diff --git a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD index 654b573a0..79a45c4e5 100644 --- a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD +++ b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD @@ -33,10 +33,10 @@ compiler_test( "@androidsdk//:platforms/android-30/android.jar", ], deps = [ - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", - "//javatests/dagger/hilt/android/processor:android_compilers", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", ], ) @@ -52,10 +52,10 @@ compiler_test( "@androidsdk//:platforms/android-30/android.jar", ], deps = [ - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", - "//javatests/dagger/hilt/android/processor:android_compilers", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", ], ) diff --git a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java index 3bf3a31de..b39cd6b52 100644 --- a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java +++ b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java @@ -17,7 +17,7 @@ package dagger.hilt.android.processor.internal.aggregateddeps; import static com.google.testing.compile.CompilationSubject.assertThat; -import static dagger.hilt.android.processor.AndroidCompilers.compiler; +import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; diff --git a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/TestInstallInTest.java b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/TestInstallInTest.java index 3af5be6fd..e4e9f674f 100644 --- a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/TestInstallInTest.java +++ b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/TestInstallInTest.java @@ -17,7 +17,7 @@ package dagger.hilt.android.processor.internal.aggregateddeps; import static com.google.testing.compile.CompilationSubject.assertThat; -import static dagger.hilt.android.processor.AndroidCompilers.compiler; +import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; diff --git a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGeneratorTest.java b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGeneratorTest.java index 25a1abdeb..b6674d4a7 100644 --- a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGeneratorTest.java +++ b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGeneratorTest.java @@ -17,7 +17,7 @@ package dagger.hilt.android.processor.internal.androidentrypoint; import static com.google.testing.compile.CompilationSubject.assertThat; -import static dagger.hilt.android.processor.AndroidCompilers.compiler; +import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; diff --git a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessorTest.java b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessorTest.java index c024a9c18..a5f85a252 100644 --- a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessorTest.java +++ b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessorTest.java @@ -17,7 +17,7 @@ package dagger.hilt.android.processor.internal.androidentrypoint; import static com.google.testing.compile.CompilationSubject.assertThat; -import static dagger.hilt.android.processor.AndroidCompilers.compiler; +import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; diff --git a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/BUILD b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/BUILD index e53d9e26f..c1f9fd2f2 100644 --- a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/BUILD +++ b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/BUILD @@ -26,10 +26,10 @@ compiler_test( "@androidsdk//:platforms/android-30/android.jar", ], deps = [ - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", - "//javatests/dagger/hilt/android/processor:android_compilers", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", ], ) @@ -42,10 +42,10 @@ compiler_test( "@androidsdk//:platforms/android-30/android.jar", ], deps = [ - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", - "//javatests/dagger/hilt/android/processor:android_compilers", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", ], ) @@ -58,10 +58,10 @@ compiler_test( "@androidsdk//:platforms/android-30/android.jar", ], deps = [ - "//java/dagger/internal/guava:collect", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", - "//javatests/dagger/hilt/android/processor:android_compilers", + "//third_party/java/guava/collect", + "//third_party/java/junit", + "//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", "@maven//:com_github_tschuchortdev_kotlin_compile_testing", ], ) diff --git a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/KotlinAndroidEntryPointProcessorTest.java b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/KotlinAndroidEntryPointProcessorTest.java index 739c87c73..42875b197 100644 --- a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/KotlinAndroidEntryPointProcessorTest.java +++ b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/KotlinAndroidEntryPointProcessorTest.java @@ -16,7 +16,7 @@ package dagger.hilt.android.processor.internal.androidentrypoint; -import static dagger.hilt.android.processor.AndroidCompilers.kotlinCompiler; +import static dagger.hilt.android.testing.compile.HiltCompilerTests.kotlinCompiler; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; diff --git a/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD b/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD index 6fc39e435..2e564ec78 100644 --- a/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD +++ b/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD @@ -28,9 +28,9 @@ compiler_test( "@androidsdk//:platforms/android-30/android.jar", ], deps = [ - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", - "//javatests/dagger/hilt/android/processor:android_compilers", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", ], ) diff --git a/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java b/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java index f0372ab5b..beadecf9a 100644 --- a/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java +++ b/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java @@ -17,7 +17,7 @@ package dagger.hilt.android.processor.internal.customtestapplication; import static com.google.testing.compile.CompilationSubject.assertThat; -import static dagger.hilt.android.processor.AndroidCompilers.compiler; +import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; diff --git a/javatests/dagger/hilt/android/processor/internal/viewmodel/BUILD b/javatests/dagger/hilt/android/processor/internal/viewmodel/BUILD index 030efd918..86342bd19 100644 --- a/javatests/dagger/hilt/android/processor/internal/viewmodel/BUILD +++ b/javatests/dagger/hilt/android/processor/internal/viewmodel/BUILD @@ -25,9 +25,9 @@ java_test( runtime_deps = [ ":ViewModelProcessorTestLib", "//java/dagger/hilt/android/lifecycle", + "//third_party/java/compile_testing", + "//third_party/java/truth", "@androidsdk//:platforms/android-30/android.jar", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/truth", "@maven//:androidx_lifecycle_lifecycle_viewmodel", "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", ], @@ -41,9 +41,9 @@ kt_jvm_library( deps = [ ":test_utils", "//java/dagger/hilt/android/processor/internal/viewmodel:processor_lib", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", ], ) @@ -52,9 +52,9 @@ java_test( runtime_deps = [ ":ViewModelGeneratorTestLib", "//java/dagger/hilt/android/lifecycle", + "//third_party/java/compile_testing", + "//third_party/java/truth", "@androidsdk//:platforms/android-30/android.jar", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/truth", "@maven//:androidx_lifecycle_lifecycle_viewmodel", "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", ], @@ -68,9 +68,9 @@ kt_jvm_library( deps = [ ":test_utils", "//java/dagger/hilt/android/processor/internal/viewmodel:processor_lib", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", ], ) @@ -83,8 +83,8 @@ kt_compiler_test( "@androidsdk//:platforms/android-30/android.jar", "@maven//:androidx_lifecycle_lifecycle_viewmodel", "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/compile_testing", + "//third_party/java/truth", "//java/dagger/hilt/android/lifecycle", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:hilt_android_app", @@ -94,10 +94,10 @@ kt_compiler_test( "//:compiler_internals", "//java/dagger/hilt/android/processor/internal/viewmodel:processor_lib", "//java/dagger/hilt/android/processor/internal/viewmodel:validation_plugin_lib", - "//javatests/dagger/hilt/android/processor:android_compilers", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", ], ) @@ -107,6 +107,6 @@ kt_jvm_library( "TestUtils.kt", ], deps = [ - "@google_bazel_common//third_party/java/compile_testing", + "//third_party/java/compile_testing", ], ) diff --git a/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelGeneratorTest.kt b/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelGeneratorTest.kt index df020ffa9..c2378e030 100644 --- a/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelGeneratorTest.kt +++ b/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelGeneratorTest.kt @@ -46,21 +46,6 @@ class ViewModelGeneratorTest { val expected = """ package dagger.hilt.android.test; - import androidx.lifecycle.ViewModel; - import dagger.Binds; - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.components.ActivityRetainedComponent; - import dagger.hilt.android.components.ViewModelComponent; - import dagger.hilt.android.internal.lifecycle.HiltViewModelMap; - import dagger.hilt.codegen.OriginatingElement; - import dagger.multibindings.IntoMap; - import dagger.multibindings.IntoSet; - import dagger.multibindings.StringKey; - import java.lang.String; - import $GENERATED_TYPE - $GENERATED_ANNOTATION @OriginatingElement( topLevelClass = MyViewModel.class @@ -102,7 +87,7 @@ class ViewModelGeneratorTest { assertThat(compilation).apply { succeeded() generatedSourceFile("dagger.hilt.android.test.MyViewModel_HiltModules") - .hasSourceEquivalentTo(expected) + .containsElementsIn(expected) } } @@ -126,21 +111,6 @@ class ViewModelGeneratorTest { val expected = """ package dagger.hilt.android.test; - import androidx.lifecycle.ViewModel; - import dagger.Binds; - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.components.ActivityRetainedComponent; - import dagger.hilt.android.components.ViewModelComponent; - import dagger.hilt.android.internal.lifecycle.HiltViewModelMap; - import dagger.hilt.codegen.OriginatingElement; - import dagger.multibindings.IntoMap; - import dagger.multibindings.IntoSet; - import dagger.multibindings.StringKey; - import java.lang.String; - import $GENERATED_TYPE - $GENERATED_ANNOTATION @OriginatingElement( topLevelClass = MyViewModel.class @@ -182,7 +152,7 @@ class ViewModelGeneratorTest { assertThat(compilation).apply { succeeded() generatedSourceFile("dagger.hilt.android.test.MyViewModel_HiltModules") - .hasSourceEquivalentTo(expected) + .containsElementsIn(expected) } } @@ -213,21 +183,6 @@ class ViewModelGeneratorTest { val expected = """ package dagger.hilt.android.test; - import androidx.lifecycle.ViewModel; - import dagger.Binds; - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.components.ActivityRetainedComponent; - import dagger.hilt.android.components.ViewModelComponent; - import dagger.hilt.android.internal.lifecycle.HiltViewModelMap; - import dagger.hilt.codegen.OriginatingElement; - import dagger.multibindings.IntoMap; - import dagger.multibindings.IntoSet; - import dagger.multibindings.StringKey; - import java.lang.String; - import $GENERATED_TYPE - $GENERATED_ANNOTATION @OriginatingElement( topLevelClass = MyViewModel.class @@ -269,7 +224,7 @@ class ViewModelGeneratorTest { assertThat(compilation).apply { succeeded() generatedSourceFile("dagger.hilt.android.test.MyViewModel_HiltModules") - .hasSourceEquivalentTo(expected) + .containsElementsIn(expected) } } @@ -301,21 +256,6 @@ class ViewModelGeneratorTest { val expected = """ package dagger.hilt.android.test; - import androidx.lifecycle.ViewModel; - import dagger.Binds; - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.components.ActivityRetainedComponent; - import dagger.hilt.android.components.ViewModelComponent; - import dagger.hilt.android.internal.lifecycle.HiltViewModelMap; - import dagger.hilt.codegen.OriginatingElement; - import dagger.multibindings.IntoMap; - import dagger.multibindings.IntoSet; - import dagger.multibindings.StringKey; - import java.lang.String; - import $GENERATED_TYPE; - $GENERATED_ANNOTATION @OriginatingElement( topLevelClass = MyViewModel.class @@ -357,7 +297,7 @@ class ViewModelGeneratorTest { assertThat(compilation).apply { succeeded() generatedSourceFile("dagger.hilt.android.test.MyViewModel_HiltModules") - .hasSourceEquivalentTo(expected) + .containsElementsIn(expected) } } @@ -396,21 +336,6 @@ class ViewModelGeneratorTest { val expected = """ package dagger.hilt.android.test; - import androidx.lifecycle.ViewModel; - import dagger.Binds; - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.components.ActivityRetainedComponent; - import dagger.hilt.android.components.ViewModelComponent; - import dagger.hilt.android.internal.lifecycle.HiltViewModelMap; - import dagger.hilt.codegen.OriginatingElement; - import dagger.multibindings.IntoMap; - import dagger.multibindings.IntoSet; - import dagger.multibindings.StringKey; - import java.lang.String; - import $GENERATED_TYPE; - $GENERATED_ANNOTATION @OriginatingElement( topLevelClass = MyViewModel.class @@ -452,7 +377,7 @@ class ViewModelGeneratorTest { assertThat(compilation).apply { succeeded() generatedSourceFile("dagger.hilt.android.test.MyViewModel_HiltModules") - .hasSourceEquivalentTo(expected) + .containsElementsIn(expected) } } @@ -477,21 +402,6 @@ class ViewModelGeneratorTest { val expectedModule = """ package dagger.hilt.android.test; - import androidx.lifecycle.ViewModel; - import dagger.Binds; - import dagger.Module; - import dagger.Provides; - import dagger.hilt.InstallIn; - import dagger.hilt.android.components.ActivityRetainedComponent; - import dagger.hilt.android.components.ViewModelComponent; - import dagger.hilt.android.internal.lifecycle.HiltViewModelMap; - import dagger.hilt.codegen.OriginatingElement; - import dagger.multibindings.IntoMap; - import dagger.multibindings.IntoSet; - import dagger.multibindings.StringKey; - import java.lang.String; - import $GENERATED_TYPE - $GENERATED_ANNOTATION @OriginatingElement( topLevelClass = Outer.class @@ -533,7 +443,7 @@ class ViewModelGeneratorTest { assertThat(compilation).apply { succeeded() generatedSourceFile("dagger.hilt.android.test.Outer_InnerViewModel_HiltModules") - .hasSourceEquivalentTo(expectedModule) + .containsElementsIn(expectedModule) } } } diff --git a/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPluginTest.kt b/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPluginTest.kt index b5c22c1a3..5fe40ddb9 100644 --- a/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPluginTest.kt +++ b/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPluginTest.kt @@ -18,7 +18,7 @@ package dagger.hilt.android.processor.internal.viewmodel import com.google.testing.compile.CompilationSubject.assertThat import com.google.testing.compile.Compiler -import dagger.hilt.android.processor.AndroidCompilers.compiler +import dagger.hilt.android.testing.compile.HiltCompilerTests.compiler import dagger.internal.codegen.ComponentProcessor import org.junit.Test import org.junit.runner.RunWith diff --git a/javatests/dagger/hilt/android/testing/BUILD b/javatests/dagger/hilt/android/testing/BUILD index e3367778d..4b3cac97b 100644 --- a/javatests/dagger/hilt/android/testing/BUILD +++ b/javatests/dagger/hilt/android/testing/BUILD @@ -25,8 +25,8 @@ android_local_test( }, deps = [ "//:android_local_test_exports", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "//java/dagger/hilt:entry_point", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android/testing:bind_value", @@ -44,10 +44,10 @@ android_local_test( }, deps = [ "//:android_local_test_exports", - "@google_bazel_common//third_party/java/auto:value", + "//third_party/java/auto:value", "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "//java/dagger/hilt:entry_point", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android/testing:bind_value", @@ -66,8 +66,8 @@ android_local_test( deps = [ "//:android_local_test_exports", "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "//java/dagger/hilt:entry_point", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android/testing:bind_value", @@ -85,10 +85,10 @@ android_local_test( }, deps = [ "//:android_local_test_exports", - "//java/dagger/internal/guava:collect", + "//third_party/java/guava/collect", "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/jsr330_inject", + "//third_party/java/truth", "//java/dagger/hilt:entry_point", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android/testing:bind_value", @@ -107,7 +107,7 @@ android_local_test( deps = [ "//:android_local_test_exports", "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/jsr330_inject", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android/testing:hilt_android_test", @@ -125,11 +125,9 @@ android_local_test( deps = [ ":HiltAndroidRuleTestApp", "//:android_local_test_exports", - "//java/dagger/internal/guava:collect", + "//third_party/java/guava/collect", "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", - "//java/dagger/hilt/android/qualifiers", + "//third_party/java/truth", "//java/dagger/hilt/android/testing:hilt_android_rule", "//java/dagger/hilt/android/testing:hilt_android_test", ], @@ -152,8 +150,7 @@ android_local_test( }, deps = [ "//:android_local_test_exports", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/truth", "//java/dagger/hilt:entry_point", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android/testing:bind_value", diff --git a/javatests/dagger/hilt/android/testing/HiltAndroidRuleTestApp.java b/javatests/dagger/hilt/android/testing/HiltAndroidRuleTestApp.java index 9ec7d0869..9ec5d751a 100644 --- a/javatests/dagger/hilt/android/testing/HiltAndroidRuleTestApp.java +++ b/javatests/dagger/hilt/android/testing/HiltAndroidRuleTestApp.java @@ -21,4 +21,4 @@ import dagger.hilt.android.HiltAndroidApp; /** A Hilt application used to test errors in {@link HiltAndroidRuleTest}. */ @HiltAndroidApp(Application.class) -final class HiltAndroidRuleTestApp extends Hilt_HiltAndroidRuleTestApp {} +public final class HiltAndroidRuleTestApp extends Hilt_HiltAndroidRuleTestApp {} diff --git a/javatests/dagger/hilt/android/testing/testinstallin/BUILD b/javatests/dagger/hilt/android/testing/testinstallin/BUILD index 63d0bca95..67f3c3512 100644 --- a/javatests/dagger/hilt/android/testing/testinstallin/BUILD +++ b/javatests/dagger/hilt/android/testing/testinstallin/BUILD @@ -33,7 +33,7 @@ android_local_test( "@maven//:androidx_test_ext_junit", "@maven//:androidx_test_core", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/truth", "//java/dagger/hilt/android/testing:hilt_android_test", ], ) @@ -55,7 +55,7 @@ android_local_test( "@maven//:androidx_test_ext_junit", "@maven//:androidx_test_core", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/truth", "//java/dagger/hilt/android/testing:hilt_android_test", "//java/dagger/hilt/android/testing:uninstall_modules", "//java/dagger/hilt/testing:test_install_in", @@ -79,7 +79,7 @@ android_local_test( "@maven//:androidx_test_ext_junit", "@maven//:androidx_test_core", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/hilt/processor/internal/BUILD b/javatests/dagger/hilt/processor/internal/BUILD index e6e1cfb4e..3dfe1ea73 100644 --- a/javatests/dagger/hilt/processor/internal/BUILD +++ b/javatests/dagger/hilt/processor/internal/BUILD @@ -23,9 +23,9 @@ java_test( srcs = ["ProcessorsTest.java"], deps = [ "//java/dagger/hilt/processor/internal:processors", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/javapoet", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/hilt/processor/internal/aggregateddeps/BUILD b/javatests/dagger/hilt/processor/internal/aggregateddeps/BUILD index f52b051d7..180bba1b8 100644 --- a/javatests/dagger/hilt/processor/internal/aggregateddeps/BUILD +++ b/javatests/dagger/hilt/processor/internal/aggregateddeps/BUILD @@ -27,7 +27,7 @@ compiler_test( "//java/dagger/hilt/internal:component_entry_point", "//java/dagger/hilt/internal:generated_entry_point", "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/jsr250_annotations", + "//third_party/java/jsr250_annotations", "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android/testing:hilt_android_test", @@ -35,11 +35,11 @@ compiler_test( ], deps = [ "//java/dagger/hilt/processor/internal/aggregateddeps:processor_lib", - "//java/dagger/internal/guava:base", "//javatests/dagger/hilt/processor/internal:generated_import", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/compile_testing", + "//third_party/java/guava/base", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/hilt/processor/internal/aliasof/AliasOfProcessorTest.java b/javatests/dagger/hilt/processor/internal/aliasof/AliasOfProcessorTest.java new file mode 100644 index 000000000..f04dc7649 --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/aliasof/AliasOfProcessorTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.aliasof; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for failure on alias scope used on DefineComponent. */ +@RunWith(JUnit4.class) +public final class AliasOfProcessorTest { + @Test + public void fails_componentScopedWithAliasScope() { + JavaFileObject scope = + JavaFileObjects.forSourceLines( + "test.AliasScope", + "package test;", + "", + "import javax.inject.Scope;", + "import javax.inject.Singleton;", + "import dagger.hilt.migration.AliasOf;", + "", + "@Scope", + "@AliasOf(Singleton.class)", + "public @interface AliasScope{}"); + + JavaFileObject root = + JavaFileObjects.forSourceLines( + "test.MyApp", + "package test;", + "", + "import android.app.Application;", + "import dagger.hilt.android.HiltAndroidApp;", + "", + "@HiltAndroidApp(Application.class)", + "public final class MyApp extends Hilt_MyApp {}"); + + JavaFileObject defineComponent = + JavaFileObjects.forSourceLines( + "test.ChildComponent", + "package test;", + "", + "import dagger.hilt.DefineComponent;", + "import dagger.hilt.components.SingletonComponent;", + "", + "@DefineComponent(parent = SingletonComponent.class)", + "@AliasScope", + "public interface ChildComponent {}"); + + Compilation compilation = + compiler() + .withOptions("-Xlint:-processing") // Suppresses unclaimed annotation warning + .compile(root, defineComponent, scope); + + assertThat(compilation).failed(); + // One extra error for the missing Hilt_MyApp reference + assertThat(compilation).hadErrorCount(2); + assertThat(compilation) + .hadErrorContaining( + "@DefineComponent test.ChildComponent, references invalid scope(s) annotated with" + + " @AliasOf. @DefineComponent scopes cannot be aliases of other scopes:" + + " [@test.AliasScope]"); + } + + @Test + public void fails_conflictingAliasScope() { + JavaFileObject scope = + JavaFileObjects.forSourceLines( + "test.AliasScope", + "package test;", + "", + "import javax.inject.Scope;", + "import javax.inject.Singleton;", + "import dagger.hilt.android.scopes.ActivityScoped;", + "import dagger.hilt.migration.AliasOf;", + "", + "@Scope", + "@AliasOf({Singleton.class, ActivityScoped.class})", + "public @interface AliasScope{}"); + + JavaFileObject root = + JavaFileObjects.forSourceLines( + "test.MyApp", + "package test;", + "", + "import android.app.Application;", + "import dagger.hilt.android.HiltAndroidApp;", + "", + "@HiltAndroidApp(Application.class)", + "public final class MyApp extends Hilt_MyApp {}"); + + Compilation compilation = + compiler() + .withOptions("-Xlint:-processing") // Suppresses unclaimed annotation warning + .compile(root, scope); + + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation).hadErrorContaining("has conflicting scopes"); + } +} diff --git a/javatests/dagger/hilt/processor/internal/aliasof/BUILD b/javatests/dagger/hilt/processor/internal/aliasof/BUILD new file mode 100644 index 000000000..3a7b4af68 --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/aliasof/BUILD @@ -0,0 +1,40 @@ +# Copyright (C) 2021 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. +# +# Description: +# Builds and run tests related to AliasOfProcessor. + +load("//java/dagger/testing/compile:macros.bzl", "compiler_test") + +package(default_visibility = ["//:src"]) + +compiler_test( + name = "AliasOfProcessorTest", + size = "small", + srcs = ["AliasOfProcessorTest.java"], + compiler_deps = [ + "@androidsdk//:platforms/android-30/android.jar", + "//third_party/java/jsr330_inject", + "//java/dagger/hilt/android:hilt_android_app", + "//java/dagger/hilt/android/components", + "//java/dagger/hilt:define_component", + "//java/dagger/hilt/migration:alias_of", + ], + deps = [ + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", + ], +) diff --git a/javatests/dagger/hilt/processor/internal/definecomponent/BUILD b/javatests/dagger/hilt/processor/internal/definecomponent/BUILD index ce5cc0aef..9de94bad3 100644 --- a/javatests/dagger/hilt/processor/internal/definecomponent/BUILD +++ b/javatests/dagger/hilt/processor/internal/definecomponent/BUILD @@ -34,9 +34,9 @@ GenJavaTests( "//java/dagger/hilt/processor/internal/definecomponent:define_components", "//java/dagger/hilt/processor/internal/definecomponent:processor_lib", "//javatests/dagger/hilt/processor/internal:generated_import", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java b/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java index a8c5fbe9c..373061c5f 100644 --- a/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java +++ b/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java @@ -22,7 +22,6 @@ import static com.google.testing.compile.Compiler.javac; import com.google.testing.compile.Compilation; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; -import dagger.hilt.processor.internal.GeneratedImport; import javax.tools.JavaFileObject; import org.junit.Test; import org.junit.runner.RunWith; @@ -67,9 +66,6 @@ public final class DefineComponentProcessorTest { "dagger.hilt.processor.internal.definecomponent.codegen._test_FooComponent", "package dagger.hilt.processor.internal.definecomponent.codegen;", "", - "import dagger.hilt.internal.definecomponent.DefineComponentClasses;", - GeneratedImport.IMPORT_GENERATED_ANNOTATION, - "", "@DefineComponentClasses(component = \"test.FooComponent\")", "@Generated(\"" + DefineComponentProcessor.class.getName() + "\")", "public class _test_FooComponent {}"); @@ -79,9 +75,6 @@ public final class DefineComponentProcessorTest { "dagger.hilt.processor.internal.definecomponent.codegen._test_FooComponentBuilder", "package dagger.hilt.processor.internal.definecomponent.codegen;", "", - "import dagger.hilt.internal.definecomponent.DefineComponentClasses;", - GeneratedImport.IMPORT_GENERATED_ANNOTATION, - "", "@DefineComponentClasses(builder = \"test.FooComponentBuilder\")", "@Generated(\"" + DefineComponentProcessor.class.getName() + "\")", "public class _test_FooComponentBuilder {}"); @@ -90,10 +83,10 @@ public final class DefineComponentProcessorTest { assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile(sourceName(componentOutput)) - .hasSourceEquivalentTo(componentOutput); + .containsElementsIn(componentOutput); assertThat(compilation) .generatedSourceFile(sourceName(builderOutput)) - .hasSourceEquivalentTo(builderOutput); + .containsElementsIn(builderOutput); } private static String sourceName(JavaFileObject fileObject) { diff --git a/javatests/dagger/hilt/processor/internal/disableinstallincheck/BUILD b/javatests/dagger/hilt/processor/internal/disableinstallincheck/BUILD index 90b0596b6..11252a6f9 100644 --- a/javatests/dagger/hilt/processor/internal/disableinstallincheck/BUILD +++ b/javatests/dagger/hilt/processor/internal/disableinstallincheck/BUILD @@ -26,14 +26,14 @@ compiler_test( compiler_deps = [ "//java/dagger/hilt/migration:disable_install_in_check", "//:dagger_with_compiler", - "@google_bazel_common//third_party/java/jsr250_annotations", + "//third_party/java/jsr250_annotations", "//java/dagger/hilt:entry_point", ], deps = [ "//java/dagger/hilt/processor/internal/disableinstallincheck:processor_lib", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/hilt/processor/internal/generatesrootinput/BUILD b/javatests/dagger/hilt/processor/internal/generatesrootinput/BUILD index c5dacd9f8..a815ecd54 100644 --- a/javatests/dagger/hilt/processor/internal/generatesrootinput/BUILD +++ b/javatests/dagger/hilt/processor/internal/generatesrootinput/BUILD @@ -31,11 +31,11 @@ GenJavaTests( "//java/dagger/hilt/processor/internal:base_processor", "//java/dagger/hilt/processor/internal/generatesrootinput:generates_root_inputs", "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/compile_testing", + "//third_party/java/javapoet", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessorTest.java b/javatests/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessorTest.java index fb62c3d12..48818d4d6 100644 --- a/javatests/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessorTest.java +++ b/javatests/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessorTest.java @@ -74,7 +74,7 @@ public final class GeneratesRootInputProcessorTest { } @Test - public void succeeds_ComponentProcessorWaitsForAnnotationsWithgeneratesstinginput() { + public void succeeds_ComponentProcessorWaitsForAnnotationsWithGeneratesRootInput() { JavaFileObject testAnnotation = JavaFileObjects.forSourceLines( "test.TestAnnotation", diff --git a/javatests/dagger/hilt/processor/internal/originatingelement/BUILD b/javatests/dagger/hilt/processor/internal/originatingelement/BUILD index 3135159ce..c45cfb6e9 100644 --- a/javatests/dagger/hilt/processor/internal/originatingelement/BUILD +++ b/javatests/dagger/hilt/processor/internal/originatingelement/BUILD @@ -28,10 +28,10 @@ compiler_test( "@maven//:androidx_annotation_annotation", ], deps = [ - "//javatests/dagger/hilt/android/processor:android_compilers", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java b/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java index 444fb1d67..5dc7a11f4 100644 --- a/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java +++ b/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java @@ -17,7 +17,7 @@ package dagger.hilt.processor.internal.originatingelement; import static com.google.testing.compile.CompilationSubject.assertThat; -import static dagger.hilt.android.processor.AndroidCompilers.compiler; +import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; diff --git a/javatests/dagger/hilt/processor/internal/root/BUILD b/javatests/dagger/hilt/processor/internal/root/BUILD index 0dba875c7..ef2344c1d 100644 --- a/javatests/dagger/hilt/processor/internal/root/BUILD +++ b/javatests/dagger/hilt/processor/internal/root/BUILD @@ -40,11 +40,11 @@ compiler_test( "@maven//:androidx_test_core", ], deps = [ - "//java/dagger/internal/guava:collect", - "//javatests/dagger/hilt/android/processor:android_compilers", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", + "//third_party/java/compile_testing", + "//third_party/java/guava/collect", + "//third_party/java/junit", + "//third_party/java/truth", ], ) @@ -71,11 +71,11 @@ compiler_test( "@maven//:androidx_test_core", ], deps = [ - "//java/dagger/internal/guava:collect", - "//javatests/dagger/hilt/android/processor:android_compilers", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", + "//third_party/java/compile_testing", + "//third_party/java/guava/collect", + "//third_party/java/junit", + "//third_party/java/truth", ], ) @@ -92,11 +92,11 @@ compiler_test( "@maven//:androidx_test_core", ], deps = [ - "//java/dagger/internal/guava:collect", - "//javatests/dagger/hilt/android/processor:android_compilers", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", + "//third_party/java/compile_testing", + "//third_party/java/guava/collect", + "//third_party/java/junit", + "//third_party/java/truth", ], ) @@ -113,11 +113,11 @@ compiler_test( "@maven//:androidx_test_core", ], deps = [ - "//java/dagger/internal/guava:base", - "//javatests/dagger/hilt/android/processor:android_compilers", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", + "//third_party/java/compile_testing", + "//third_party/java/guava/base", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java b/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java index adf6d9b2d..e4aa5abe7 100644 --- a/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java +++ b/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java @@ -23,7 +23,7 @@ import com.google.common.collect.ImmutableList; import com.google.testing.compile.Compilation; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; -import dagger.hilt.android.processor.AndroidCompilers; +import dagger.hilt.android.testing.compile.HiltCompilerTests; import javax.tools.JavaFileObject; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,7 +45,7 @@ public final class MyAppPreviousCompilationTest { } private Compiler compiler() { - return AndroidCompilers.compiler() + return HiltCompilerTests.compiler() .withOptions( String.format( "-Adagger.hilt.disableCrossCompilationRootValidation=%s", @@ -90,11 +90,11 @@ public final class MyAppPreviousCompilationTest { assertThat(compilation).hadErrorCount(1); assertThat(compilation) .hadErrorContaining( - "Cannot process app roots in this compilation unit since there are app roots in a " + "Cannot process new app roots when there are app roots from a " + "previous compilation unit:" - + "\n \tApp roots in previous compilation unit: [" - + "dagger.hilt.processor.internal.root.MyAppPreviousCompilation.MyApp]" - + "\n \tApp roots in this compilation unit: [test.AppRoot]"); + + "\n App roots in previous compilation unit: " + + "dagger.hilt.processor.internal.root.MyAppPreviousCompilation.MyApp" + + "\n App roots in this compilation unit: test.AppRoot"); } } } diff --git a/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java b/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java index 9e9fae57e..82251bd70 100644 --- a/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java +++ b/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java @@ -23,7 +23,7 @@ import com.google.common.collect.ImmutableList; import com.google.testing.compile.Compilation; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; -import dagger.hilt.android.processor.AndroidCompilers; +import dagger.hilt.android.testing.compile.HiltCompilerTests; import javax.tools.JavaFileObject; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,7 +45,7 @@ public final class MyTestPreviousCompilationTest { } private Compiler compiler() { - return AndroidCompilers.compiler() + return HiltCompilerTests.compiler() .withOptions( String.format( "-Adagger.hilt.disableCrossCompilationRootValidation=%s", @@ -73,9 +73,9 @@ public final class MyTestPreviousCompilationTest { assertThat(compilation) .hadErrorContaining( "Cannot process new roots when there are test roots from a previous compilation unit:" - + "\n \tTest roots from previous compilation unit: " - + "[dagger.hilt.processor.internal.root.MyTestPreviousCompilation.MyTest]" - + "\n \tAll roots from this compilation unit: [test.TestRoot]"); + + "\n Test roots from previous compilation unit: " + + "dagger.hilt.processor.internal.root.MyTestPreviousCompilation.MyTest" + + "\n All roots from this compilation unit: test.TestRoot"); } } @@ -101,9 +101,9 @@ public final class MyTestPreviousCompilationTest { assertThat(compilation) .hadErrorContaining( "Cannot process new roots when there are test roots from a previous compilation unit:" - + "\n \tTest roots from previous compilation unit: " - + "[dagger.hilt.processor.internal.root.MyTestPreviousCompilation.MyTest]" - + "\n \tAll roots from this compilation unit: [test.AppRoot]"); + + "\n Test roots from previous compilation unit: " + + "dagger.hilt.processor.internal.root.MyTestPreviousCompilation.MyTest" + + "\n All roots from this compilation unit: test.AppRoot"); } } } diff --git a/javatests/dagger/hilt/processor/internal/root/RootFileFormatterTest.java b/javatests/dagger/hilt/processor/internal/root/RootFileFormatterTest.java index 6d598c83f..53ef42c9c 100644 --- a/javatests/dagger/hilt/processor/internal/root/RootFileFormatterTest.java +++ b/javatests/dagger/hilt/processor/internal/root/RootFileFormatterTest.java @@ -17,7 +17,7 @@ package dagger.hilt.processor.internal.root; import static com.google.testing.compile.CompilationSubject.assertThat; -import static dagger.hilt.android.processor.AndroidCompilers.compiler; +import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler; import com.google.common.base.Joiner; import com.google.testing.compile.Compilation; @@ -83,6 +83,7 @@ public final class RootFileFormatterTest { public void testTestComponents() { Compilation compilation = compiler() + .withOptions("-Adagger.hilt.shareTestComponents=false") .compile( JavaFileObjects.forSourceLines( "test.MyTest", diff --git a/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java b/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java index e9ed8c418..c07ee5347 100644 --- a/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java +++ b/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java @@ -23,7 +23,7 @@ import com.google.common.collect.ImmutableList; import com.google.testing.compile.Compilation; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; -import dagger.hilt.android.processor.AndroidCompilers; +import dagger.hilt.android.testing.compile.HiltCompilerTests; import javax.tools.JavaFileObject; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,7 +45,7 @@ public final class RootProcessorErrorsTest { } private Compiler compiler() { - return AndroidCompilers.compiler() + return HiltCompilerTests.compiler() .withOptions( String.format( "-Adagger.hilt.disableCrossCompilationRootValidation=%s", @@ -83,7 +83,7 @@ public final class RootProcessorErrorsTest { assertThat(compilation) .hadErrorContaining( "Cannot process multiple app roots in the same compilation unit: " - + "[test.AppRoot1, test.AppRoot2]"); + + "test.AppRoot1, test.AppRoot2"); } @Test @@ -116,7 +116,7 @@ public final class RootProcessorErrorsTest { assertThat(compilation) .hadErrorContaining( "Cannot process test roots and app roots in the same compilation unit:" - + "\n \tApp root in this compilation unit: [test.AppRoot]" - + "\n \tTest roots in this compilation unit: [test.TestRoot]"); + + "\n App root in this compilation unit: test.AppRoot" + + "\n Test roots in this compilation unit: test.TestRoot"); } } diff --git a/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD b/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD index caceb7ce5..e407193b4 100644 --- a/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD +++ b/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD @@ -31,10 +31,10 @@ compiler_test( "@maven//:androidx_annotation_annotation", ], deps = [ - "//javatests/dagger/hilt/android/processor:android_compilers", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt/android/testing/compile", + "//third_party/java/compile_testing", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java b/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java index 2be9ab670..04b5a2461 100644 --- a/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java +++ b/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java @@ -17,7 +17,7 @@ package dagger.hilt.processor.internal.uninstallmodules; import static com.google.testing.compile.CompilationSubject.assertThat; -import static dagger.hilt.android.processor.AndroidCompilers.compiler; +import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; diff --git a/javatests/dagger/hilt/testmodules/BUILD b/javatests/dagger/hilt/testmodules/BUILD index 7acd3d1ae..f7aef8024 100644 --- a/javatests/dagger/hilt/testmodules/BUILD +++ b/javatests/dagger/hilt/testmodules/BUILD @@ -27,7 +27,7 @@ kt_android_library( "//:dagger_with_compiler", "//java/dagger/hilt:install_in", "//java/dagger/hilt/components", - "@google_bazel_common//third_party/java/jsr330_inject", + "//third_party/java/jsr330_inject", ], ) diff --git a/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java b/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java index 42b8f8a99..09f9fab75 100644 --- a/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java +++ b/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java @@ -620,7 +620,7 @@ public class AssistedFactoryErrorsTest { } @Test - public void testInjectsProviderOfAssistedFactory() { + public void testInjectsLazyOfAssistedFactory() { JavaFileObject foo = JavaFileObjects.forSourceLines( "test.Foo", @@ -644,12 +644,12 @@ public class AssistedFactoryErrorsTest { "test.Bar", "package test;", "", + "import dagger.Lazy;", "import javax.inject.Inject;", - "import javax.inject.Provider;", "", "class Bar {", " @Inject", - " Bar(Foo.Factory fooFactory, Provider<Foo.Factory> fooFactoryProvider) {}", + " Bar(Foo.Factory fooFactory, Lazy<Foo.Factory> fooFactoryLazy) {}", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(foo, bar); @@ -657,7 +657,7 @@ public class AssistedFactoryErrorsTest { assertThat(compilation).hadErrorCount(1); assertThat(compilation) .hadErrorContaining( - "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, or Produced<T> " + "Dagger does not support injecting Lazy<T>, Producer<T>, or Produced<T> " + "when T is an @AssistedFactory-annotated type such as test.Foo.Factory") .inFile(bar) .onLine(8); diff --git a/javatests/dagger/internal/codegen/AssistedFactoryTest.java b/javatests/dagger/internal/codegen/AssistedFactoryTest.java index cffd42eba..7691e1e26 100644 --- a/javatests/dagger/internal/codegen/AssistedFactoryTest.java +++ b/javatests/dagger/internal/codegen/AssistedFactoryTest.java @@ -99,19 +99,39 @@ public class AssistedFactoryTest { .addLinesIn( FAST_INIT_MODE, "final class DaggerTestComponent implements TestComponent {", + " private final DaggerTestComponent testComponent = this;", + " private Provider<FooFactory> fooFactoryProvider;", "", - " private Foo foo(String str) {", - " return new Foo(str, new Bar());", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.fooFactoryProvider = SingleCheck.provider(new" + + " SwitchingProvider<FooFactory>(testComponent, 0));", " }", "", " @Override", " public FooFactory fooFactory() {", - " return new FooFactory() {", - " @Override", - " public Foo create(String str) {", - " return DaggerTestComponent.this.foo(str);", + " return fooFactoryProvider.get();", + " }", + "", + " private static final class SwitchingProvider<T> implements Provider<T> {", + " private final DaggerTestComponent testComponent;", + " private final int id;", + "", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0:", + " return (T) new FooFactory() {", + " @Override", + " public Foo create(String str) {", + " return new Foo(str, new Bar());", + " }", + " };", + "", + " default: throw new AssertionError(id);", " }", - " };", + " }", " }", "}") .addLinesIn( @@ -195,23 +215,43 @@ public class AssistedFactoryTest { .addLinesIn( FAST_INIT_MODE, "final class DaggerTestComponent implements TestComponent {", + " private final DaggerTestComponent testComponent = this;", + " private Provider<FooFactory> fooFactoryProvider;", "", " private Bar bar() {", - " return new Bar(fooFactory());", + " return new Bar(fooFactoryProvider.get());", " }", "", - " private Foo foo(String str) {", - " return new Foo(str, bar());", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.fooFactoryProvider = SingleCheck.provider(new" + + " SwitchingProvider<FooFactory>(testComponent, 0));", " }", "", " @Override", " public FooFactory fooFactory() {", - " return new FooFactory() {", - " @Override", - " public Foo create(String str) {", - " return DaggerTestComponent.this.foo(str);", + " return fooFactoryProvider.get();", + " }", + "", + " private static final class SwitchingProvider<T> implements Provider<T> {", + " private final DaggerTestComponent testComponent;", + " private final int id;", + "", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0:", + " return (T) new FooFactory() {", + " @Override", + " public Foo create(String str) {", + " return new Foo(str, testComponent.bar())", + " }", + " };", + "", + " default: throw new AssertionError(id);", " }", - " };", + " }", " }", "}") .addLinesIn( @@ -243,4 +283,344 @@ public class AssistedFactoryTest { .generatedSourceFile("test.DaggerTestComponent") .containsElementsIn(generatedComponent); } + + @Test + public void assistedParamConflictsWithComponentFieldName_successfulyDeduped() { + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import dagger.assisted.Assisted;", + "import dagger.assisted.AssistedInject;", + "import javax.inject.Provider;", + "", + "class Foo {", + " @AssistedInject", + " Foo(@Assisted String testComponent, Provider<Bar> bar) {}", + "}"); + JavaFileObject fooFactory = + JavaFileObjects.forSourceLines( + "test.FooFactory", + "package test;", + "", + "import dagger.assisted.AssistedFactory;", + "", + "@AssistedFactory", + "interface FooFactory {", + " Foo create(String factoryStr);", + "}"); + JavaFileObject bar = + JavaFileObjects.forSourceLines( + "test.Bar", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Bar {", + " @Inject Bar() {}", + "}"); + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component", + "interface TestComponent {", + " FooFactory fooFactory();", + "}"); + JavaFileObject generatedComponent = + compilerMode + .javaFileBuilder("test.DaggerTestComponent") + .addLines("package test;", "", GeneratedLines.generatedAnnotations()) + .addLinesIn( + FAST_INIT_MODE, + "final class DaggerTestComponent implements TestComponent {", + " private final DaggerTestComponent testComponent = this;", + " private Provider<FooFactory> fooFactoryProvider;", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.barProvider = new SwitchingProvider<>(testComponent, 1);", + " this.fooFactoryProvider = SingleCheck.provider(", + " new SwitchingProvider<FooFactory>(testComponent, 0));", + " }", + "", + " @Override", + " public FooFactory fooFactory() {", + " return fooFactoryProvider.get();", + " }", + "", + " private static final class SwitchingProvider<T> implements Provider<T> {", + " private final DaggerTestComponent testComponent;", + " private final int id;", + "", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0:", + " return (T) new FooFactory() {", + " @Override", + " public Foo create(String testComponent2) {", + " return new Foo(testComponent2, testComponent.barProvider);", + " }", + " };", + " case 1: return (T) new Bar();", + " default: throw new AssertionError(id);", + " }", + " }", + " }", + "}") + .addLinesIn( + DEFAULT_MODE, + "final class DaggerTestComponent implements TestComponent {", + "", + " private Foo_Factory fooProvider;", + "", + " private Provider<FooFactory> fooFactoryProvider;", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.fooProvider = Foo_Factory.create(Bar_Factory.create());", + " this.fooFactoryProvider = FooFactory_Impl.create(fooProvider);", + " }", + "", + " @Override", + " public FooFactory fooFactory() {", + " return fooFactoryProvider.get();", + " }", + "}") + .build(); + + Compilation compilation = + compilerWithOptions(compilerMode.javacopts()).compile(foo, bar, fooFactory, component); + + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.DaggerTestComponent") + .containsElementsIn(generatedComponent); + } + + @Test + public void testFactoryGeneratorDuplicatedParamNames() { + JavaFileObject componentSrc = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.BindsInstance;", + "import dagger.Component;", + "", + "@Component", + "interface TestComponent {", + " @Component.Factory", + " interface Factory {", + " TestComponent create(@BindsInstance Bar arg);", + "}", + " FooFactory getFooFactory();", + "}"); + JavaFileObject factorySrc = + JavaFileObjects.forSourceLines( + "test.FooFactory", + "package test;", + "", + "import dagger.assisted.AssistedFactory;", + "", + "@AssistedFactory", + "public interface FooFactory {", + " Foo create(Integer arg);", + "}"); + JavaFileObject barSrc = + JavaFileObjects.forSourceLines("test.Bar", "package test;", "", "interface Bar {}"); + JavaFileObject injectSrc = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import dagger.assisted.Assisted;", + "import dagger.assisted.AssistedInject;", + "", + "class Foo {", + " @AssistedInject", + " Foo(Bar arg, @Assisted Integer argProvider) {}", + "}"); + JavaFileObject generatedSrc = + compilerMode + .javaFileBuilder("test.DaggerTestComponent") + .addLines( + "package test;", + "", + "@ScopeMetadata", + "@QualifierMetadata", + GeneratedLines.generatedAnnotations()) + .addLinesIn( + FAST_INIT_MODE, + "public final class Foo_Factory {", + " private final Provider<Bar> argProvider;", + "", + " public Foo_Factory(Provider<Bar> argProvider) {", + " this.argProvider = argProvider;", + " }", + "", + " public Foo get(Integer argProvider2) {", + " return newInstance(argProvider.get(), argProvider2);", + " }", + "", + " public static Foo_Factory create(Provider<Bar> argProvider) {", + " return new Foo_Factory(argProvider);", + " }", + "", + " public static Foo newInstance(Object arg, Integer argProvider) {", + " return new Foo((Bar) arg, argProvider);", + " }", + "}") + .addLinesIn( + DEFAULT_MODE, + "public final class Foo_Factory {", + " private final Provider<Bar> argProvider;", + "", + " public Foo_Factory(Provider<Bar> argProvider) {", + " this.argProvider = argProvider;", + " }", + "", + " public Foo get(Integer argProvider2) {", + " return newInstance(argProvider.get(), argProvider2);", + " }", + "", + " public static Foo_Factory create(Provider<Bar> argProvider) {", + " return new Foo_Factory(argProvider);", + " }", + "", + " public static Foo newInstance(Object arg, Integer argProvider) {", + " return new Foo((Bar) arg, argProvider);", + " }", + "}") + .build(); + Compilation compilation = + compilerWithOptions(compilerMode.javacopts()) + .compile(componentSrc, factorySrc, barSrc, injectSrc); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.Foo_Factory") + .containsElementsIn(generatedSrc); + } + + @Test + public void testParameterizedAssistParam() { + JavaFileObject componentSrc = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component", + "interface TestComponent {", + " FooFactory<String> getFooFactory();", + "}"); + JavaFileObject factorySrc = + JavaFileObjects.forSourceLines( + "test.FooFactory", + "package test;", + "", + "import dagger.assisted.AssistedFactory;", + "", + "@AssistedFactory", + "public interface FooFactory<T> {", + " Foo<T> create(T arg);", + "}"); + JavaFileObject injectSrc = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import dagger.assisted.Assisted;", + "import dagger.assisted.AssistedInject;", + "", + "class Foo<T> {", + " @AssistedInject", + " Foo(@Assisted T arg) {}", + "}"); + JavaFileObject generatedSrc = + compilerMode + .javaFileBuilder("test.DaggerTestComponent") + .addLines("package test;", "", GeneratedLines.generatedAnnotations()) + .addLinesIn( + FAST_INIT_MODE, + "final class DaggerTestComponent implements TestComponent {", + " private final DaggerTestComponent testComponent = this;", + " private Provider<FooFactory<String>> fooFactoryProvider;", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.fooFactoryProvider = SingleCheck.provider(new" + + " SwitchingProvider<FooFactory<String>>(testComponent, 0));", + " }", + "", + " @Override", + " public FooFactory<String> getFooFactory() {", + " return fooFactoryProvider.get();", + " }", + " ", + " private static final class SwitchingProvider<T> implements Provider<T> {", + " private final DaggerTestComponent testComponent;", + " private final int id;", + "", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0: return (T) new FooFactory<String>() {", + " @Override", + " public Foo<String> create(String arg) {", + " return new Foo<String>(arg)", + " }", + " };", + "", + " default: throw new AssertionError(id);", + " }", + " }", + " }", + "}") + .addLinesIn( + DEFAULT_MODE, + "final class DaggerTestComponent implements TestComponent {", + " private final DaggerTestComponent testComponent = this;", + " private Foo_Factory<String> fooProvider;", + " private Provider<FooFactory<String>> fooFactoryProvider;", + "", + " private DaggerTestComponent() {", + " initialize();", + " }", + "", + " public static Builder builder() {", + " return new Builder();", + " }", + "", + " public static TestComponent create() {", + " return new Builder().build();", + " }", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.fooProvider = Foo_Factory.create();", + " this.fooFactoryProvider = FooFactory_Impl.create(fooProvider);", + " }", + "", + " @Override", + " public FooFactory<String> getFooFactory() {", + " return fooFactoryProvider.get();", + " }", + "}") + .build(); + Compilation compilation = + compilerWithOptions(compilerMode.javacopts()).compile(componentSrc, factorySrc, injectSrc); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.DaggerTestComponent") + .containsElementsIn(generatedSrc); + } } diff --git a/javatests/dagger/internal/codegen/BUILD b/javatests/dagger/internal/codegen/BUILD index 979ae71ef..f0a65bf0d 100644 --- a/javatests/dagger/internal/codegen/BUILD +++ b/javatests/dagger/internal/codegen/BUILD @@ -18,6 +18,7 @@ load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library") load("//:build_defs.bzl", "DOCLINT_HTML_AND_SYNTAX") load("//:test_defs.bzl", "GenJavaTests") +load("//java/dagger/testing/compile:macros.bzl", "compiler_test") package(default_visibility = ["//:src"]) @@ -43,10 +44,19 @@ java_library( deps = [ "//java/dagger/internal/codegen:package_info", "//java/dagger/internal/codegen:processor", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", + "//third_party/java/compile_testing", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", "@com_google_auto_value_auto_value//jar", - "@google_bazel_common//third_party/java/compile_testing", + ], +) + +java_library( + name = "InvalidInjectConstructor", + srcs = ["InvalidInjectConstructor.java"], + # Note: We purposely leave off the dagger processor here. + deps = [ + "//third_party/java/jsr330_inject", ], ) @@ -58,12 +68,15 @@ GenJavaTests( "CompilerMode.java", "Compilers.java", "JavaFileBuilder.java", + "ComponentValidationKtTest.java", + "InvalidInjectConstructor.java", ], ), functional = False, javacopts = DOCLINT_HTML_AND_SYNTAX, plugins = ["//java/dagger/internal/codegen/bootstrap"], deps = [ + ":InvalidInjectConstructor", ":compilers", ":kotlin_sources", "//java/dagger:core", @@ -73,26 +86,54 @@ GenJavaTests( "//java/dagger/internal/codegen/binding", "//java/dagger/internal/codegen/bindinggraphvalidation", "//java/dagger/internal/codegen/compileroption", + "//java/dagger/internal/codegen/javac", "//java/dagger/internal/codegen/javapoet", "//java/dagger/internal/codegen/kotlin", "//java/dagger/internal/codegen/langmodel", "//java/dagger/internal/codegen/validation", "//java/dagger/internal/codegen/writing", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", + "//java/dagger/internal/codegen/xprocessing", "//java/dagger/model/testing", "//java/dagger/producers", "//java/dagger/spi", + "//third_party/java/auto:common", + "//third_party/java/auto:value", + "//third_party/java/compile_testing", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/guava/util/concurrent", + "//third_party/java/javapoet", + "//third_party/java/jsr330_inject", + "//third_party/java/junit", + "//third_party/java/mockito", + "//third_party/java/truth", "@com_google_auto_value_auto_value//jar", - "@google_bazel_common//third_party/java/auto:value", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/jsr250_annotations", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/mockito", - "@google_bazel_common//third_party/java/truth", - "@maven//:com_google_auto_auto_common", + ], +) + +compiler_test( + name = "ComponentValidationKtTest", + srcs = ["ComponentValidationKtTest.java"], + compiler_deps = [ + "//java/dagger:core", + "//java/dagger/internal/codegen:package_info", + "//java/dagger/internal/codegen:processor", + "//java/dagger/internal/codegen/base", + "//java/dagger/internal/codegen/binding", + "//java/dagger/internal/codegen/bindinggraphvalidation", + "//java/dagger/internal/codegen/compileroption", + "//java/dagger/internal/codegen/javapoet", + "//java/dagger/internal/codegen/langmodel", + "//java/dagger/internal/codegen/validation", + "//java/dagger/internal/codegen/writing", + "//java/dagger/model/testing", + "//java/dagger/producers", + "//java/dagger/spi", + ], + deps = [ + "//third_party/java/guava/collect", + "//third_party/java/junit", + "//third_party/java/truth", + "@maven//:com_github_tschuchortdev_kotlin_compile_testing", ], ) diff --git a/javatests/dagger/internal/codegen/BindsMethodValidationTest.java b/javatests/dagger/internal/codegen/BindsMethodValidationTest.java index ab2601091..8974e0617 100644 --- a/javatests/dagger/internal/codegen/BindsMethodValidationTest.java +++ b/javatests/dagger/internal/codegen/BindsMethodValidationTest.java @@ -16,11 +16,15 @@ package dagger.internal.codegen; +import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.internal.codegen.Compilers.daggerCompiler; import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass; import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatModuleMethod; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.google.common.collect.ImmutableList; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; import dagger.Module; import dagger.multibindings.IntKey; import dagger.multibindings.LongKey; @@ -29,6 +33,7 @@ import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.util.Collection; import javax.inject.Qualifier; +import javax.tools.JavaFileObject; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -41,10 +46,12 @@ public class BindsMethodValidationTest { return ImmutableList.copyOf(new Object[][] {{Module.class}, {ProducerModule.class}}); } + private final String moduleAnnotation; private final String moduleDeclaration; public BindsMethodValidationTest(Class<? extends Annotation> moduleAnnotation) { - moduleDeclaration = "@" + moduleAnnotation.getCanonicalName() + " abstract class %s { %s }"; + this.moduleAnnotation = "@" + moduleAnnotation.getCanonicalName(); + moduleDeclaration = this.moduleAnnotation + " abstract class %s { %s }"; } @Test @@ -141,6 +148,113 @@ public class BindsMethodValidationTest { .hasError("may not have more than one map key"); } + @Test + public void bindsMissingTypeInParameterHierarchy() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Binds;", + "", + moduleAnnotation, + "interface TestModule {", + " @Binds String bindObject(Child<String> child);", + "}"); + + JavaFileObject child = + JavaFileObjects.forSourceLines( + "test.Child", + "package test;", + "", + "class Child<T> extends Parent<T> {}"); + + JavaFileObject parent = + JavaFileObjects.forSourceLines( + "test.Parent", + "package test;", + "", + "class Parent<T> extends MissingType {}"); + + Compilation compilation = daggerCompiler().compile(module, child, parent); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(3); + assertThat(compilation) + .hadErrorContaining( + "cannot find symbol" + + "\n symbol: class MissingType"); + assertThat(compilation) + .hadErrorContaining( + "ModuleProcessingStep was unable to process 'test.TestModule' because 'MissingType' " + + "could not be resolved."); + assertThat(compilation) + .hadErrorContaining( + "BindingMethodProcessingStep was unable to process" + + " 'bindObject(test.Child<java.lang.String>)' because 'MissingType' could not be" + + " resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (INTERFACE): test.TestModule" + + "\n => element (METHOD): bindObject(test.Child<java.lang.String>)" + + "\n => element (PARAMETER): child" + + "\n => type (DECLARED parameter): test.Child<java.lang.String>" + + "\n => type (DECLARED supertype): test.Parent<java.lang.String>" + + "\n => type (ERROR supertype): MissingType"); + } + + + @Test + public void bindsMissingTypeInReturnTypeHierarchy() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Binds;", + "", + moduleAnnotation, + "interface TestModule {", + " @Binds Child<String> bindChild(String str);", + "}"); + + JavaFileObject child = + JavaFileObjects.forSourceLines( + "test.Child", + "package test;", + "", + "class Child<T> extends Parent<T> {}"); + + JavaFileObject parent = + JavaFileObjects.forSourceLines( + "test.Parent", + "package test;", + "", + "class Parent<T> extends MissingType {}"); + + Compilation compilation = daggerCompiler().compile(module, child, parent); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(3); + assertThat(compilation) + .hadErrorContaining( + "cannot find symbol" + + "\n symbol: class MissingType"); + assertThat(compilation) + .hadErrorContaining( + "ModuleProcessingStep was unable to process 'test.TestModule' because 'MissingType' " + + "could not be resolved."); + assertThat(compilation) + .hadErrorContaining( + "BindingMethodProcessingStep was unable to process 'bindChild(java.lang.String)'" + + " because 'MissingType' could not be resolved." + + "\n " + + "\n Dependency trace:" + + "\n => element (INTERFACE): test.TestModule" + + "\n => element (METHOD): bindChild(java.lang.String)" + + "\n => type (DECLARED return type): test.Child<java.lang.String>" + + "\n => type (DECLARED supertype): test.Parent<java.lang.String>" + + "\n => type (ERROR supertype): MissingType"); + } + private DaggerModuleMethodSubject assertThatMethod(String method) { return assertThatModuleMethod(method).withDeclaration(moduleDeclaration); } diff --git a/javatests/dagger/internal/codegen/Compilers.java b/javatests/dagger/internal/codegen/Compilers.java index 26796bfd2..af45f2c25 100644 --- a/javatests/dagger/internal/codegen/Compilers.java +++ b/javatests/dagger/internal/codegen/Compilers.java @@ -44,7 +44,8 @@ public final class Compilers { .collect(collectingAndThen(toList(), ImmutableList::copyOf)); static final ImmutableList<String> DEFAULT_JAVACOPTS = - ImmutableList.of("-Adagger.experimentalDaggerErrorMessages=enabled"); + ImmutableList.of( + "-Adagger.experimentalDaggerErrorMessages=enabled"); /** * Returns a compiler that runs the Dagger and {@code @AutoAnnotation} processors, along with diff --git a/javatests/dagger/internal/codegen/ComponentBuilderTest.java b/javatests/dagger/internal/codegen/ComponentBuilderTest.java index c4e0e07f1..17c49002d 100644 --- a/javatests/dagger/internal/codegen/ComponentBuilderTest.java +++ b/javatests/dagger/internal/codegen/ComponentBuilderTest.java @@ -18,7 +18,7 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.COMPONENT_BUILDER; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.COMPONENT_BUILDER; import static dagger.internal.codegen.binding.ErrorMessages.creatorMessagesFor; import com.google.testing.compile.Compilation; diff --git a/javatests/dagger/internal/codegen/ComponentCreatorTest.java b/javatests/dagger/internal/codegen/ComponentCreatorTest.java index 3cf05ac9c..be8066f35 100644 --- a/javatests/dagger/internal/codegen/ComponentCreatorTest.java +++ b/javatests/dagger/internal/codegen/ComponentCreatorTest.java @@ -23,17 +23,17 @@ import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; import static dagger.internal.codegen.Compilers.daggerCompiler; import static dagger.internal.codegen.ComponentCreatorTest.CompilerType.JAVAC; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.COMPONENT_BUILDER; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.COMPONENT_FACTORY; -import static dagger.internal.codegen.binding.ComponentCreatorKind.BUILDER; -import static dagger.internal.codegen.binding.ComponentCreatorKind.FACTORY; -import static dagger.internal.codegen.binding.ComponentKind.COMPONENT; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.COMPONENT_BUILDER; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.COMPONENT_FACTORY; +import static dagger.internal.codegen.base.ComponentCreatorKind.BUILDER; +import static dagger.internal.codegen.base.ComponentCreatorKind.FACTORY; +import static dagger.internal.codegen.base.ComponentKind.COMPONENT; import static dagger.internal.codegen.binding.ErrorMessages.componentMessagesFor; import com.google.common.collect.ImmutableList; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; -import dagger.internal.codegen.binding.ComponentCreatorAnnotation; +import dagger.internal.codegen.base.ComponentCreatorAnnotation; import java.util.Collection; import javax.tools.JavaFileObject; import org.junit.Test; @@ -163,6 +163,8 @@ public class ComponentCreatorTest extends ComponentCreatorTestHelper { "final class DaggerTestComponent implements TestComponent {", " private final TestModule testModule;", "", + " private final DaggerTestComponent testComponent = this;", + "", " private DaggerTestComponent(TestModule testModuleParam) {", " this.testModule = testModuleParam;", " }", @@ -361,6 +363,8 @@ public class ComponentCreatorTest extends ComponentCreatorTestHelper { "final class DaggerSimpleComponent implements SimpleComponent {", " private final Object object;", "", + " private final DaggerSimpleComponent simpleComponent = this;", + "", " private DaggerSimpleComponent(Object objectParam) {", " this.object = objectParam;", " }", diff --git a/javatests/dagger/internal/codegen/ComponentCreatorTestHelper.java b/javatests/dagger/internal/codegen/ComponentCreatorTestHelper.java index 8ad43227f..aaab5c3b4 100644 --- a/javatests/dagger/internal/codegen/ComponentCreatorTestHelper.java +++ b/javatests/dagger/internal/codegen/ComponentCreatorTestHelper.java @@ -17,14 +17,14 @@ package dagger.internal.codegen; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.binding.ComponentCreatorKind.FACTORY; +import static dagger.internal.codegen.base.ComponentCreatorKind.FACTORY; import static dagger.internal.codegen.binding.ErrorMessages.creatorMessagesFor; import static java.util.stream.Collectors.joining; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; -import dagger.internal.codegen.binding.ComponentCreatorAnnotation; -import dagger.internal.codegen.binding.ComponentCreatorKind; +import dagger.internal.codegen.base.ComponentCreatorAnnotation; +import dagger.internal.codegen.base.ComponentCreatorKind; import dagger.internal.codegen.binding.ErrorMessages; import java.util.Arrays; import java.util.stream.Stream; @@ -67,7 +67,7 @@ abstract class ComponentCreatorTestHelper { line -> line.replace("Builder", "Factory") .replace("builder", "factory") - .replace("build", "create")); + .replace("build", "createComponent")); } return stream.collect(joining("\n")); } diff --git a/javatests/dagger/internal/codegen/ComponentFactoryTest.java b/javatests/dagger/internal/codegen/ComponentFactoryTest.java index 35cbb6a67..a910f7222 100644 --- a/javatests/dagger/internal/codegen/ComponentFactoryTest.java +++ b/javatests/dagger/internal/codegen/ComponentFactoryTest.java @@ -18,7 +18,7 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.COMPONENT_FACTORY; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.COMPONENT_FACTORY; import static dagger.internal.codegen.binding.ErrorMessages.creatorMessagesFor; import com.google.testing.compile.Compilation; diff --git a/javatests/dagger/internal/codegen/ComponentProcessorTest.java b/javatests/dagger/internal/codegen/ComponentProcessorTest.java index 3e514c829..480a449ad 100644 --- a/javatests/dagger/internal/codegen/ComponentProcessorTest.java +++ b/javatests/dagger/internal/codegen/ComponentProcessorTest.java @@ -175,74 +175,34 @@ public class ComponentProcessorTest { .addLines( "package test;", "", - GeneratedLines.generatedImports( - "import dagger.Lazy;", - "import dagger.internal.DoubleCheck;", - "import javax.inject.Provider;"), - "", GeneratedLines.generatedAnnotations(), - "final class DaggerSimpleComponent implements SimpleComponent {") + "final class DaggerSimpleComponent implements SimpleComponent {", + " private final DaggerSimpleComponent simpleComponent = this;") .addLinesIn( FAST_INIT_MODE, - " private volatile Provider<SomeInjectableType> someInjectableTypeProvider;") - .addLines( - " private DaggerSimpleComponent() {}", - "", - " public static Builder builder() {", - " return new Builder();", - " }", - "", - " public static SimpleComponent create() {", - " return new Builder().build();", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.someInjectableTypeProvider =", + " new SwitchingProvider<>(simpleComponent, 0);", " }", "", " @Override", " public SomeInjectableType someInjectableType() {", - " return new SomeInjectableType();", + " return someInjectableTypeProvider.get();", " }", "", " @Override", - " public Lazy<SomeInjectableType> lazySomeInjectableType() {") - .addLinesIn( - DEFAULT_MODE, // - " return DoubleCheck.lazy(SomeInjectableType_Factory.create());") - .addLinesIn( - FAST_INIT_MODE, - " return DoubleCheck.lazy(someInjectableTypeProvider());") - .addLines( + " public Lazy<SomeInjectableType> lazySomeInjectableType() {", + " return DoubleCheck.lazy(someInjectableTypeProvider);", " }", "", " @Override", - " public Provider<SomeInjectableType> someInjectableTypeProvider() {") - .addLinesIn( - DEFAULT_MODE, // - " return SomeInjectableType_Factory.create();") - .addLinesIn( - FAST_INIT_MODE, // - " Object local = someInjectableTypeProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " someInjectableTypeProvider = (Provider<SomeInjectableType>) local;", - " }", - " return (Provider<SomeInjectableType>) local;") - .addLines( + " public Provider<SomeInjectableType> someInjectableTypeProvider() {", + " return someInjectableTypeProvider", " }", "", - " static final class Builder {", - " private Builder() {}", - "", - " public SimpleComponent build() {", - " return new DaggerSimpleComponent();", - " }", - " }") - .addLinesIn( - FAST_INIT_MODE, - " private final class SwitchingProvider<T> implements Provider<T> {", - " private final int id;", - "", - " SwitchingProvider(int id) {", - " this.id = id;", - " }", + " private static final class SwitchingProvider<T> implements Provider<T> {", + " private final DaggerSimpleComponent simpleComponent;", "", " @SuppressWarnings(\"unchecked\")", " @Override", @@ -251,8 +211,24 @@ public class ComponentProcessorTest { " case 0: return (T) new SomeInjectableType();", " default: throw new AssertionError(id);", " }", - " }", - " }") + " }") + .addLinesIn( + DEFAULT_MODE, + " @Override", + " public SomeInjectableType someInjectableType() {", + " return new SomeInjectableType();", + " }", + "", + " @Override", + " public Lazy<SomeInjectableType> lazySomeInjectableType() {", + " return DoubleCheck.lazy(SomeInjectableType_Factory.create());", + " }", + "", + " @Override", + " public Provider<SomeInjectableType> someInjectableTypeProvider() {", + " return SomeInjectableType_Factory.create();", + " }", + "}") .build(); Compilation compilation = @@ -261,7 +237,7 @@ public class ComponentProcessorTest { assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerSimpleComponent") - .hasSourceEquivalentTo(generatedComponent); + .containsElementsIn(generatedComponent); } @Test public void componentWithScope() { @@ -297,84 +273,46 @@ public class ComponentProcessorTest { "package test;", "", GeneratedLines.generatedAnnotations(), - "final class DaggerSimpleComponent implements SimpleComponent {") + "final class DaggerSimpleComponent implements SimpleComponent {", + " private Provider<SomeInjectableType> someInjectableTypeProvider;") .addLinesIn( FAST_INIT_MODE, - " private volatile Object someInjectableType = new MemoizedSentinel();", - " private volatile Provider<SomeInjectableType> someInjectableTypeProvider;") + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.someInjectableTypeProvider =", + " DoubleCheck.provider(", + " new SwitchingProvider<SomeInjectableType>(simpleComponent, 0));", + " }") .addLinesIn( DEFAULT_MODE, - " private Provider<SomeInjectableType> someInjectableTypeProvider;", - "", " @SuppressWarnings(\"unchecked\")", " private void initialize() {", " this.someInjectableTypeProvider =", " DoubleCheck.provider(SomeInjectableType_Factory.create());", - " }", - "") - .addLines( - " @Override", // - " public SomeInjectableType someInjectableType() {") - .addLinesIn( - FAST_INIT_MODE, - " Object local = someInjectableType;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = someInjectableType;", - " if (local instanceof MemoizedSentinel) {", - " local = new SomeInjectableType();", - " someInjectableType =", - " DoubleCheck.reentrantCheck(someInjectableType, local);", - " }", - " }", - " }", - " return (SomeInjectableType) local;") - .addLinesIn( - DEFAULT_MODE, // - " return someInjectableTypeProvider.get();") + " }") .addLines( + " @Override", + " public SomeInjectableType someInjectableType() {", + " return someInjectableTypeProvider.get();", " }", "", " @Override", - " public Lazy<SomeInjectableType> lazySomeInjectableType() {") - .addLinesIn( - DEFAULT_MODE, // - " return DoubleCheck.lazy(someInjectableTypeProvider);") - .addLinesIn( - FAST_INIT_MODE, - " return DoubleCheck.lazy(someInjectableTypeProvider());") - .addLines( + " public Lazy<SomeInjectableType> lazySomeInjectableType() {", + " return DoubleCheck.lazy(someInjectableTypeProvider);", " }", "", " @Override", - " public Provider<SomeInjectableType> someInjectableTypeProvider() {") - .addLinesIn( - FAST_INIT_MODE, // - " Object local = someInjectableTypeProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " someInjectableTypeProvider = (Provider<SomeInjectableType>) local;", - " }", - " return (Provider<SomeInjectableType>) local;") - .addLinesIn( - DEFAULT_MODE, // - " return someInjectableTypeProvider;") - .addLines( // + " public Provider<SomeInjectableType> someInjectableTypeProvider() {", + " return someInjectableTypeProvider;", " }") .addLinesIn( FAST_INIT_MODE, - " private final class SwitchingProvider<T> implements Provider<T> {", - " private final int id;", - "", - " SwitchingProvider(int id) {", - " this.id = id;", - " }", - "", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", - " case 0: return (T) DaggerSimpleComponent.this.someInjectableType();", + " case 0: return (T) new SomeInjectableType();", " default: throw new AssertionError(id);", " }", " }", @@ -882,6 +820,7 @@ public class ComponentProcessorTest { "", GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", + " private final DaggerParent parent = this;", "", " private DaggerParent() {}", "", @@ -1044,29 +983,42 @@ public class ComponentProcessorTest { " Provider<SimpleComponent> selfProvider();", "}"); JavaFileObject generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerSimpleComponent", - "package test;", - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerSimpleComponent implements SimpleComponent {", - " private Provider<SimpleComponent> simpleComponentProvider;", - "", - " @SuppressWarnings(\"unchecked\")", - " private void initialize() {", - " this.simpleComponentProvider = InstanceFactory.create((SimpleComponent) this);", - " }", - "", - " @Override", - " public SomeInjectableType someInjectableType() {", - " return new SomeInjectableType(this)", - " }", - "", - " @Override", - " public Provider<SimpleComponent> selfProvider() {", - " return simpleComponentProvider;", - " }", - "}"); + compilerMode + .javaFileBuilder("test.DaggerSimpleComponent") + .addLines( + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "final class DaggerSimpleComponent implements SimpleComponent {", + " private final DaggerSimpleComponent simpleComponent = this;", + "", + " private Provider<SimpleComponent> simpleComponentProvider;", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.simpleComponentProvider =", + " InstanceFactory.create((SimpleComponent) simpleComponent);", + " }", + "") + .addLinesIn( + DEFAULT_MODE, + " @Override", + " public SomeInjectableType someInjectableType() {", + " return new SomeInjectableType(this)", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @Override", + " public SomeInjectableType someInjectableType() {", + " return new SomeInjectableType(simpleComponentProvider.get());", + " }") + .addLines( + " @Override", + " public Provider<SimpleComponent> selfProvider() {", + " return simpleComponentProvider;", + " }", + "}") + .build(); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(injectableTypeFile, componentFile); @@ -1182,34 +1134,37 @@ public class ComponentProcessorTest { "", GeneratedLines.generatedAnnotations(), "final class DaggerBComponent implements BComponent {") - .addLinesIn(DEFAULT_MODE, " private Provider<A> aProvider;") .addLinesIn( FAST_INIT_MODE, " private final AComponent aComponent;", - " private volatile Provider<A> aProvider;", + " private final DaggerBComponent bComponent = this;", + " private Provider<A> aProvider;", "", " private DaggerBComponent(AComponent aComponentParam) {", " this.aComponent = aComponentParam;", + " initialize(aComponentParam);", " }", "", - " private Provider<A> aProvider() {", - " Object local = aProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " aProvider = (Provider<A>) local;", - " }", - " return (Provider<A>) local;", + " @SuppressWarnings(\"unchecked\")", + " private void initialize(final AComponent aComponentParam) {", + " this.aProvider = new SwitchingProvider<>(bComponent, 0);", " }") .addLinesIn( DEFAULT_MODE, + " private Provider<A> aProvider;", + "", + " private DaggerBComponent(AComponent aComponentParam) {", + " initialize(aComponentParam);", + " }", + "", " @SuppressWarnings(\"unchecked\")", " private void initialize(final AComponent aComponentParam) {", " this.aProvider = new test_AComponent_a(aComponentParam);", " }") - .addLines("", " @Override", " public B b() {") - .addLinesIn(DEFAULT_MODE, " return new B(aProvider);") - .addLinesIn(FAST_INIT_MODE, " return new B(aProvider());") .addLines( + " @Override", + " public B b() {", + " return new B(aProvider);", " }", "", " static final class Builder {", @@ -1242,15 +1197,14 @@ public class ComponentProcessorTest { "}") .addLinesIn( FAST_INIT_MODE, - " private final class SwitchingProvider<T> implements Provider<T> {", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", " case 0:", " return (T)", - " Preconditions.checkNotNullFromComponent(", - " DaggerBComponent.this.aComponent.a());", + " Preconditions.checkNotNullFromComponent(bComponent.aComponent.a());", " default:", " throw new AssertionError(id);", " }", @@ -1320,9 +1274,9 @@ public class ComponentProcessorTest { "", " private DaggerTestComponent(", " TestModule testModuleParam,", - " other.test.TestModule testModule2Param) {", + " other.test.TestModule testModuleParam2) {", " this.testModule = testModuleParam;", - " this.testModule2 = testModule2Param;", + " this.testModule2 = testModuleParam2;", " }", "", " @Override", @@ -1601,6 +1555,8 @@ public class ComponentProcessorTest { "", GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", + " private final DaggerSimpleComponent simpleComponent = this;", + "", " private DaggerSimpleComponent() {}", "", " public static Builder builder() {", @@ -2042,8 +1998,9 @@ public class ComponentProcessorTest { "", GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", - " private DaggerParent() {", - " }", + " private final DaggerParent parent = this;", + "", + " private DaggerParent() {}", "", " public static Builder builder() {", " return new Builder();", @@ -2181,6 +2138,8 @@ public class ComponentProcessorTest { "test.TestModule_NonNullableStringFactory", "package test;", "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class TestModule_NonNullableStringFactory", " implements Factory<String> {", @@ -2271,6 +2230,8 @@ public class ComponentProcessorTest { "test.TestModule_PrimitiveIntegerFactory", "package test;", "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class TestModule_PrimitiveIntegerFactory", " implements Factory<Integer> {", @@ -2439,10 +2400,10 @@ public class ComponentProcessorTest { "package test;", GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", - " private final class ChildImpl implements Child {", + " private static final class ChildImpl implements Child {", " @Override", " public String string() {", - " return DaggerParent.this.string();", + " return parent.string();", " }", " }", "}"); @@ -2633,6 +2594,8 @@ public class ComponentProcessorTest { "", GeneratedLines.generatedAnnotations(), "public final class DaggerPublicComponent implements PublicComponent {", + " private final DaggerPublicComponent publicComponent = this;", + "", " private DaggerPublicComponent() {}", "", " public static Builder builder() {", @@ -2653,6 +2616,219 @@ public class ComponentProcessorTest { "}")); } + @Test + public void componentFactoryInterfaceTest() { + JavaFileObject parentInterface = + JavaFileObjects.forSourceLines( + "test.ParentInterface", + "package test;", + "", + "interface ParentInterface extends ChildInterface.Factory {}"); + + JavaFileObject childInterface = + JavaFileObjects.forSourceLines( + "test.ChildInterface", + "package test;", + "", + "interface ChildInterface {", + " interface Factory {", + " ChildInterface child(ChildModule childModule);", + " }", + "}"); + + JavaFileObject parent = + JavaFileObjects.forSourceLines( + "test.Parent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component", + "interface Parent extends ParentInterface, Child.Factory {}"); + + JavaFileObject child = + JavaFileObjects.forSourceLines( + "test.Child", + "package test;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent(modules = ChildModule.class)", + "interface Child extends ChildInterface {", + " interface Factory extends ChildInterface.Factory {", + " @Override Child child(ChildModule childModule);", + " }", + "}"); + + JavaFileObject childModule = + JavaFileObjects.forSourceLines( + "test.ChildModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "", + "@Module", + "class ChildModule {", + " @Provides", + " int provideInt() {", + " return 0;", + " }", + "}"); + + Compilation compilation = + daggerCompiler().compile(parentInterface, childInterface, parent, child, childModule); + assertThat(compilation).succeeded(); + } + + @Test + public void providerComponentType() { + JavaFileObject entryPoint = + JavaFileObjects.forSourceLines( + "test.SomeEntryPoint", + "package test;", + "", + "import javax.inject.Inject;", + "import javax.inject.Provider;", + "", + "public class SomeEntryPoint {", + " @Inject SomeEntryPoint(Foo foo, Provider<Foo> fooProvider) {}", + "}"); + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import javax.inject.Inject;", + "", + "public class Foo {", + " @Inject Foo(Bar bar) {}", + "}"); + JavaFileObject bar = + JavaFileObjects.forSourceLines( + "test.Bar", + "package test;", + "", + "import javax.inject.Inject;", + "", + "public class Bar {", + " @Inject Bar() {}", + "}"); + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "import javax.inject.Provider;", + "", + "@Component", + "public interface TestComponent {", + " SomeEntryPoint someEntryPoint();", + "}"); + Compilation compilation = + compilerWithOptions(compilerMode.javacopts()).compile(component, foo, bar, entryPoint); + + assertThat(compilation) + .generatedSourceFile("test.DaggerTestComponent") + .containsElementsIn( + compilerMode + .javaFileBuilder("test.DaggerSimpleComponent") + .addLines( + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "public final class DaggerTestComponent implements TestComponent {", + " private Provider<Foo> fooProvider;") + .addLinesIn( + DEFAULT_MODE, + " private Foo foo() {", + " return new Foo(new Bar());", + " }", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.fooProvider = Foo_Factory.create(Bar_Factory.create());", + " }", + "", + " @Override", + " public SomeEntryPoint someEntryPoint() {", + " return new SomeEntryPoint(foo(), fooProvider);", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.fooProvider = new SwitchingProvider<>(testComponent, 0);", + " }", + "", + " @Override", + " public SomeEntryPoint someEntryPoint() {", + " return new SomeEntryPoint(fooProvider.get(), fooProvider);", + " }", + "", + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0: return (T) new Foo(new Bar());", + "", + " default: throw new AssertionError(id);", + " }", + " }", + " }", + "}") + .build()); + } + + @Test + public void injectedTypeHasGeneratedParam() { + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import javax.inject.Inject;", + "", + "public final class Foo {", + "", + " @Inject", + " public Foo(GeneratedParam param) {}", + "", + " @Inject", + " public void init() {}", + "}"); + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "import java.util.Set;", + "", + "@Component", + "interface TestComponent {", + " Foo foo();", + "}"); + + Compilation compilation = + daggerCompiler( + new GeneratingProcessor( + "test.GeneratedParam", + "package test;", + "", + "import javax.inject.Inject;", + "", + "public final class GeneratedParam {", + "", + " @Inject", + " public GeneratedParam() {}", + "}")) + .compile(foo, component); + assertThat(compilation).succeededWithoutWarnings(); + } + /** * A {@link ComponentProcessor} that excludes elements using a {@link Predicate}. */ diff --git a/javatests/dagger/internal/codegen/ComponentProtectedTypeTest.java b/javatests/dagger/internal/codegen/ComponentProtectedTypeTest.java new file mode 100644 index 000000000..c67cbec09 --- /dev/null +++ b/javatests/dagger/internal/codegen/ComponentProtectedTypeTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 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.testing.compile.CompilationSubject.assertThat; +import static dagger.internal.codegen.Compilers.compilerWithOptions; + +import com.google.common.collect.ImmutableList; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public final class ComponentProtectedTypeTest { + @Parameters(name = "{0}") + public static ImmutableList<Object[]> parameters() { + return CompilerMode.TEST_PARAMETERS; + } + + private final CompilerMode compilerMode; + + public ComponentProtectedTypeTest(CompilerMode compilerMode) { + this.compilerMode = compilerMode; + } + + @Test + public void componentAccessesProtectedType_succeeds() { + JavaFileObject baseSrc = + JavaFileObjects.forSourceLines( + "test.sub.TestComponentBase", + "package test.sub;", + "", + "import javax.inject.Inject;", + "import javax.inject.Singleton;", + "", + "public abstract class TestComponentBase {", + " static class Dep {", + " @Inject", + " Dep() {}", + " }", + "", + " @Singleton", + " protected static final class ProtectedType {", + " @Inject", + " ProtectedType(Dep dep) {}", + " }", + "}"); + JavaFileObject componentSrc = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "import javax.inject.Provider;", + "import javax.inject.Singleton;", + "import test.sub.TestComponentBase;", + "", + "@Singleton", + "@Component", + "public abstract class TestComponent extends TestComponentBase {", + // This component method will be implemented as: + // TestComponentBase.ProtectedType provideProtectedType() { + // return protectedTypeProvider.get(); + // } + // The protectedTypeProvider can't be a raw provider, otherwise it will have a type cast + // error. So protected accessibility should be evaluated when checking accessibility of + // a type. + " abstract TestComponentBase.ProtectedType provideProtectedType();", + "}"); + JavaFileObject generatedComponent = + JavaFileObjects.forSourceLines( + "test.DaggerTestComponent", + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "public final class DaggerTestComponent extends TestComponent {", + " private Provider<test.sub.TestComponentBase.ProtectedType> protectedTypeProvider;", + "", + " @Override", + " test.sub.TestComponentBase.ProtectedType provideProtectedType() {", + " return protectedTypeProvider.get();", + " }", + "}"); + + Compilation compilation = + compilerWithOptions(compilerMode.javacopts()).compile(baseSrc, componentSrc); + + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.DaggerTestComponent") + .containsElementsIn(generatedComponent); + } +} diff --git a/javatests/dagger/internal/codegen/ComponentRequirementFieldTest.java b/javatests/dagger/internal/codegen/ComponentRequirementFieldTest.java index 05164a6a3..b0e30e80b 100644 --- a/javatests/dagger/internal/codegen/ComponentRequirementFieldTest.java +++ b/javatests/dagger/internal/codegen/ComponentRequirementFieldTest.java @@ -17,6 +17,8 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; +import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; import com.google.testing.compile.Compilation; @@ -272,15 +274,15 @@ public class ComponentRequirementFieldTest { " return Preconditions.checkNotNullFromComponent(dep.object());", " }", "", - " private final class TestSubcomponentImpl implements TestSubcomponent {", + " private static final class TestSubcomponentImpl implements TestSubcomponent {", " @Override", " public TestComponent parent() {", - " return DaggerTestComponent.this;", + " return testComponent;", " }", "", " @Override", " public Dep depFromSubcomponent() {", - " return DaggerTestComponent.this.dep;", + " return testComponent.dep;", " }", " }", "}")); @@ -348,83 +350,128 @@ public class ComponentRequirementFieldTest { "interface TestSubcomponent {", " Provider<Object> dependsOnMultibinding();", "}"); - JavaFileObject generatedComponent; - switch (compilerMode) { - case FAST_INIT_MODE: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerTestComponent", - "package test;", - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {", - " private final ParentModule parentModule;", - "", - " private DaggerTestComponent(ParentModule parentModuleParam) {", - " this.parentModule = parentModuleParam;", - " }", - "", - " private final class TestSubcomponentImpl implements TestSubcomponent {", - " private Set<Object> setOfObject() {", - " return ImmutableSet.<Object>of(", - " ParentModule_ContributionFactory.contribution(),", - " ChildModule_ContributionFactory.contribution());", - " }", - "", - " private Object object() {", - " return ParentModule_ReliesOnMultibindingFactory.reliesOnMultibinding(", - " DaggerTestComponent.this.parentModule, setOfObject());", - " }", - " }", - "}"); - break; - default: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerTestComponent", - "package test;", - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {", - " private final ParentModule parentModule;", - "", - " private DaggerTestComponent(ParentModule parentModuleParam) {", - " this.parentModule = parentModuleParam;", - " initialize(parentModuleParam);", - " }", - "", - " @SuppressWarnings(\"unchecked\")", - " private void initialize(final ParentModule parentModuleParam) {", - " this.setOfObjectProvider =", - " SetFactory.<Object>builder(1, 0)", - " .addProvider(ParentModule_ContributionFactory.create())", - " .build();", - " this.reliesOnMultibindingProvider =", - " ParentModule_ReliesOnMultibindingFactory.create(", - " parentModuleParam, setOfObjectProvider);", - " }", - "", - " private final class TestSubcomponentImpl implements TestSubcomponent {", - " @SuppressWarnings(\"unchecked\")", - " private void initialize() {", - " this.setOfObjectProvider =", - " SetFactory.<Object>builder(2, 0)", - " .addProvider(ParentModule_ContributionFactory.create())", - " .addProvider(ChildModule_ContributionFactory.create())", - " .build();", - " this.reliesOnMultibindingProvider =", - " ParentModule_ReliesOnMultibindingFactory.create(", - " DaggerTestComponent.this.parentModule, setOfObjectProvider);", - " }", - " }", - "}"); - } + Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(parentModule, childModule, component, subcomponent); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") - .containsElementsIn(generatedComponent); + .containsElementsIn( + compilerMode + .javaFileBuilder("test.DaggerSimpleComponent") + .addLines( + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "final class DaggerTestComponent implements TestComponent {", + " private final ParentModule parentModule;", + "", + " private DaggerTestComponent(ParentModule parentModuleParam) {", + " this.parentModule = parentModuleParam;", + " initialize(parentModuleParam);", + " }") + .addLinesIn( + DEFAULT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize(final ParentModule parentModuleParam) {", + " this.setOfObjectProvider =", + " SetFactory.<Object>builder(1, 0)", + " .addProvider(ParentModule_ContributionFactory.create())", + " .build();", + " this.reliesOnMultibindingProvider =", + " ParentModule_ReliesOnMultibindingFactory", + " .create(parentModuleParam, setOfObjectProvider);", + " }") + .addLinesIn( + FAST_INIT_MODE, + "", + " private Set<Object> setOfObject() {", + " return ImmutableSet.<Object>of(", + " ParentModule_ContributionFactory.contribution());", + " }", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize(final ParentModule parentModuleParam) {", + " this.reliesOnMultibindingProvider =", + " new SwitchingProvider<>(testComponent, 0);", + " }") + .addLines( + " @Override", + " public Provider<Object> dependsOnMultibinding() {", + " return reliesOnMultibindingProvider;", + " }", + "", + " @Override", + " public TestSubcomponent subcomponent() {", + " return new TestSubcomponentImpl(testComponent);", + " }", + "", + " private static final class TestSubcomponentImpl", + " implements TestSubcomponent {") + .addLinesIn( + DEFAULT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.setOfObjectProvider =", + " SetFactory.<Object>builder(2, 0)", + " .addProvider(ParentModule_ContributionFactory.create())", + " .addProvider(ChildModule_ContributionFactory.create())", + " .build();", + " this.reliesOnMultibindingProvider =", + " ParentModule_ReliesOnMultibindingFactory", + " .create(testComponent.parentModule, setOfObjectProvider);", + " }") + .addLinesIn( + FAST_INIT_MODE, + " private Set<Object> setOfObject() {", + " return ImmutableSet.<Object>of(", + " ParentModule_ContributionFactory.contribution(),", + " ChildModule_ContributionFactory.contribution());", + " }", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.reliesOnMultibindingProvider =", + " new SwitchingProvider<>(testComponent, testSubcomponentImpl, 0);", + " }") + .addLines( + " @Override", + " public Provider<Object> dependsOnMultibinding() {", + " return reliesOnMultibindingProvider;", + " }") + .addLinesIn( + FAST_INIT_MODE, + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0:", + " return (T) ParentModule_ReliesOnMultibindingFactory", + " .reliesOnMultibinding(", + " testComponent.parentModule,", + " testSubcomponentImpl.setOfObject());", + " default: throw new AssertionError(id);", + " }", + " }", + " }", + " }", + "", + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0:", + " return (T) ParentModule_ReliesOnMultibindingFactory", + " .reliesOnMultibinding(", + " testComponent.parentModule, testComponent.setOfObject());", + " default: throw new AssertionError(id);", + " }", + " }", + " }", + "}") + .build()); } } diff --git a/javatests/dagger/internal/codegen/ComponentShardTest.java b/javatests/dagger/internal/codegen/ComponentShardTest.java index ec7f4990c..015a8cf70 100644 --- a/javatests/dagger/internal/codegen/ComponentShardTest.java +++ b/javatests/dagger/internal/codegen/ComponentShardTest.java @@ -17,9 +17,12 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; -import static com.google.testing.compile.Compiler.javac; +import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; +import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; +import static dagger.internal.codegen.Compilers.compilerWithOptions; import static java.util.stream.Collectors.joining; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.testing.compile.Compilation; @@ -29,211 +32,268 @@ import java.util.Arrays; import javax.tools.JavaFileObject; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; -@RunWith(JUnit4.class) +@RunWith(Parameterized.class) public class ComponentShardTest { - private static final int BINDINGS_PER_SHARD = 10; + private static final int BINDINGS_PER_SHARD = 2; + + @Parameters(name = "{0}") + public static ImmutableCollection<Object[]> parameters() { + return CompilerMode.TEST_PARAMETERS; + } + + private final CompilerMode compilerMode; + + public ComponentShardTest(CompilerMode compilerMode) { + this.compilerMode = compilerMode; + } @Test public void testNewShardCreated() { - // Create 2N + 1 bindings: N in DaggerTestComponent, N in Shard1, and 1 in Shard2 - int numBindings = 2 * BINDINGS_PER_SHARD + 1; + // Add all bindings. + // + // 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 + // ^--------/ + // ImmutableList.Builder<JavaFileObject> javaFileObjects = ImmutableList.builder(); - ImmutableList.Builder<String> entryPoints = ImmutableList.builder(); - for (int i = 0; i < numBindings; i++) { - String bindingName = "Binding" + i; - entryPoints.add(String.format("%1$s get%1$s();", bindingName)); - entryPoints.add(String.format("Provider<%1$s> get%1$sProvider();", bindingName)); + javaFileObjects + // Shard 2: Bindings (1) + .add(createBinding("Binding1", "Binding2 binding2")) + // Shard 1: Bindings (2, 3, 4, 5). Contains more than 2 bindings due to cycle. + .add(createBinding("Binding2", "Binding3 binding3")) + .add(createBinding("Binding3", "Binding4 binding4")) + .add(createBinding("Binding4", "Binding5 binding5, Provider<Binding2> binding2Provider")) + .add(createBinding("Binding5", "Binding6 binding6")) + // Component shard: Bindings (6, 7) + .add(createBinding("Binding6", "Binding7 binding7")) + .add(createBinding("Binding7")); - // Add dependencies between main component and shard1: 9 -> 10 -> Provider<9> - // Add dependencies between shard1 and shard2: 19 -> 20 -> Provider<19> - switch (i) { - case 9: - javaFileObjects.add(createBinding(bindingName, "Binding10 dep")); - break; - case 10: - javaFileObjects.add(createBinding(bindingName, "Provider<Binding9> dep")); - break; - case 19: - javaFileObjects.add(createBinding(bindingName, "Binding20 dep")); - break; - case 20: - javaFileObjects.add(createBinding(bindingName, "Provider<Binding19> dep")); - break; - default: - javaFileObjects.add(createBinding(bindingName)); - break; - } - } - - javaFileObjects.add(createComponent(entryPoints.build())); - - // This generated component shows a couple things: - // 1. Binding locations: - // * Binding #9 belongs to DaggerTestComponent - // * Binding #10 belongs to Shard1 - // * Binding #20 belongs to Shard2 - // 2. DaggerTestComponent entry point methods: - // * Binding #9 implementation is inlined DaggerTestComponent. - // * Binding #10 implementation is delegated to Shard1. - // * Binding #20 implementation is delegated to Shard2. - // 3. Dependencies between component and shard: - // * Binding #9 in DaggerTestComponent depends on #10 in Shard1. - // * Binding #10 in Shard1 depends on Provider<#9> in DaggerTestComponent. - // 4. Dependencies between shard and shard: - // * Binding #19 in Shard1 depends on #20 in Shard2. - // * Binding #20 in Shard2 depends on Provider<#19> in Shard1. - JavaFileObject generatedComponent = + // Add the component with entry points for each binding and its provider. + javaFileObjects.add( JavaFileObjects.forSourceLines( - "dagger.internal.codegen.DaggerTestComponent", - "package dagger.internal.codegen;", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {", - " private final Shard1 shard1 = new Shard1();", - "", - " private volatile Provider<Binding9> binding9Provider;", - "", - " private volatile Object binding9 = new MemoizedSentinel();", - "", - " @Override", - " public Binding9 getBinding9() {", - " Object local = binding9;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = binding9;", - " if (local instanceof MemoizedSentinel) {", - " local = new Binding9(DaggerTestComponent.this.shard1.binding10());", - " binding9 = DoubleCheck.reentrantCheck(binding9, local);", - " }", - " }", - " }", - " return (Binding9) local;", - " }", - "", - " @Override", - " public Provider<Binding9> getBinding9Provider() {", - " Object local = binding9Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(9);", - " binding9Provider = (Provider<Binding9>) local;", - " }", - " return (Provider<Binding9>) local;", - " }", - "", - " @Override", - " public Binding10 getBinding10() {", - " return DaggerTestComponent.this.shard1.binding10();", - " }", - "", - " @Override", - " public Provider<Binding10> getBinding10Provider() {", - " return DaggerTestComponent.this.shard1.binding10Provider();", - " }", - "", - " @Override", - " public Binding20 getBinding20() {", - " return DaggerTestComponent.this.shard2.binding20();", - " }", - "", - " @Override", - " public Provider<Binding20> getBinding20Provider() {", - " return DaggerTestComponent.this.shard2.binding20Provider();", - " }", - "", - " private final class Shard1 {", - " private volatile Object binding10 = new MemoizedSentinel();", - "", - " private volatile Provider<Binding10> binding10Provider;", - "", - " private volatile Provider<Binding19> binding19Provider;", - "", - " private volatile Object binding19 = new MemoizedSentinel();", - "", - " private Binding10 binding10() {", - " Object local = binding10;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = binding10;", - " if (local instanceof MemoizedSentinel) {", - " local = new Binding10(", - " DaggerTestComponent.this.getBinding9Provider());", - " binding10 = DoubleCheck.reentrantCheck(binding10, local);", - " }", - " }", - " }", - " return (Binding10) local;", - " }", - "", - " private Provider<Binding10> binding10Provider() {", - " Object local = binding10Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(10);", - " binding10Provider = (Provider<Binding10>) local;", - " }", - " return (Provider<Binding10>) local;", - " }", - "", - " private Provider<Binding19> binding19Provider() {", - " Object local = binding19Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(19);", - " binding19Provider = (Provider<Binding19>) local;", - " }", - " return (Provider<Binding19>) local;", - " }", - "", - " private Binding19 binding19() {", - " Object local = binding19;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = binding19;", - " if (local instanceof MemoizedSentinel) {", - " local = new Binding19(DaggerTestComponent.this.shard2.binding20());", - " binding19 = DoubleCheck.reentrantCheck(binding19, local);", - " }", - " }", - " }", - " return (Binding19) local;", - " }", - " }", - "", - " private final class Shard2 {", - " private volatile Object binding20 = new MemoizedSentinel();", - "", - " private volatile Provider<Binding20> binding20Provider;", - "", - " private Binding20 binding20() {", - " Object local = binding20;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = binding20;", - " if (local instanceof MemoizedSentinel) {", - " local = new Binding20(", - " DaggerTestComponent.this.shard1.binding19Provider());", - " binding20 = DoubleCheck.reentrantCheck(binding20, local);", - " }", - " }", - " }", - " return (Binding20) local;", - " }", - "", - " private Provider<Binding20> binding20Provider() {", - " Object local = binding20Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(20);", - " binding20Provider = (Provider<Binding20>) local;", - " }", - " return (Provider<Binding20>) local;", - " }", - " }", - "}"); + "dagger.internal.codegen.TestComponent", + "package dagger.internal.codegen;", + "", + "import dagger.Component;", + "import javax.inject.Provider;", + "import javax.inject.Singleton;", + "", + "@Singleton", + "@Component", + "interface TestComponent {", + " Binding1 binding1();", + " Binding2 binding2();", + " Binding3 binding3();", + " Binding4 binding4();", + " Binding5 binding5();", + " Binding6 binding6();", + " Binding7 binding7();", + " Provider<Binding1> providerBinding1();", + " Provider<Binding2> providerBinding2();", + " Provider<Binding3> providerBinding3();", + " Provider<Binding4> providerBinding4();", + " Provider<Binding5> providerBinding5();", + " Provider<Binding6> providerBinding6();", + " Provider<Binding7> providerBinding7();", + "}")); - Compilation compilation = compilerWithAndroidMode().compile(javaFileObjects.build()); + Compilation compilation = compiler().compile(javaFileObjects.build()); assertThat(compilation).succeededWithoutWarnings(); assertThat(compilation) .generatedSourceFile("dagger.internal.codegen.DaggerTestComponent") - .containsElementsIn(generatedComponent); + .containsElementsIn( + compilerMode + .javaFileBuilder("dagger.internal.codegen.DaggerTestComponent") + .addLines( + "package dagger.internal.codegen;", + "", + GeneratedLines.generatedAnnotations(), + "final class DaggerTestComponent implements TestComponent {", + " private Shard1 shard1;", + " private Shard2 shard2;", + " private final DaggerTestComponent testComponent = this;", + " private Provider<Binding7> binding7Provider;", + " private Provider<Binding6> binding6Provider;", + "", + " private DaggerTestComponent() {", + " initialize();", + " shard1 = new Shard1();", + " shard2 = new Shard2();", + " }") + .addLinesIn( + DEFAULT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.binding7Provider =", + " DoubleCheck.provider(Binding7_Factory.create());", + " this.binding6Provider =", + " DoubleCheck.provider(Binding6_Factory.create(binding7Provider));", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.binding7Provider = DoubleCheck.provider(", + " new SwitchingProvider<Binding7>(testComponent, 6));", + " this.binding6Provider = DoubleCheck.provider(", + " new SwitchingProvider<Binding6>(testComponent, 5));", + " }") + .addLines( + " @Override", + " public Binding1 binding1() {", + " return testComponent.shard2.binding1Provider.get();", + " }", + "", + " @Override", + " public Binding2 binding2() {", + " return testComponent.shard1.binding2Provider.get();", + " }", + "", + " @Override", + " public Binding3 binding3() {", + " return testComponent.shard1.binding3Provider.get();", + " }", + "", + " @Override", + " public Binding4 binding4() {", + " return testComponent.shard1.binding4Provider.get();", + " }", + "", + " @Override", + " public Binding5 binding5() {", + " return testComponent.shard1.binding5Provider.get();", + " }", + "", + " @Override", + " public Binding6 binding6() {", + " return binding6Provider.get();", + " }", + "", + " @Override", + " public Binding7 binding7() {", + " return binding7Provider.get();", + " }", + "", + " @Override", + " public Provider<Binding1> providerBinding1() {", + " return testComponent.shard2.binding1Provider;", + " }", + "", + " @Override", + " public Provider<Binding2> providerBinding2() {", + " return testComponent.shard1.binding2Provider;", + " }", + "", + " @Override", + " public Provider<Binding3> providerBinding3() {", + " return testComponent.shard1.binding3Provider;", + " }", + "", + " @Override", + " public Provider<Binding4> providerBinding4() {", + " return testComponent.shard1.binding4Provider;", + " }", + "", + " @Override", + " public Provider<Binding5> providerBinding5() {", + " return testComponent.shard1.binding5Provider;", + " }", + "", + " @Override", + " public Provider<Binding6> providerBinding6() {", + " return binding6Provider;", + " }", + "", + " @Override", + " public Provider<Binding7> providerBinding7() {", + " return binding7Provider;", + " }", + "", + " private final class Shard1 {", + " private Provider<Binding5> binding5Provider;", + " private Provider<Binding2> binding2Provider;", + " private Provider<Binding4> binding4Provider;", + " private Provider<Binding3> binding3Provider;") + .addLinesIn( + DEFAULT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.binding5Provider =", + " DoubleCheck.provider(", + " Binding5_Factory.create(testComponent.binding6Provider));", + " this.binding2Provider = new DelegateFactory<>();", + " this.binding4Provider =", + " DoubleCheck.provider(", + " Binding4_Factory.create(binding5Provider, binding2Provider));", + " this.binding3Provider =", + " DoubleCheck.provider(Binding3_Factory.create(binding4Provider));", + " DelegateFactory.setDelegate(", + " binding2Provider,", + " DoubleCheck.provider(Binding2_Factory.create(binding3Provider)));", + " }", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.binding5Provider = DoubleCheck.provider(", + " new SwitchingProvider<Binding5>(testComponent, 4));", + " this.binding4Provider = DoubleCheck.provider(", + " new SwitchingProvider<Binding4>(testComponent, 3));", + " this.binding3Provider = DoubleCheck.provider(", + " new SwitchingProvider<Binding3>(testComponent, 2));", + " this.binding2Provider = DoubleCheck.provider(", + " new SwitchingProvider<Binding2>(testComponent, 1));", + " }", + " }") + .addLines( + " private final class Shard2 {", + " private Provider<Binding1> binding1Provider;") + .addLinesIn( + DEFAULT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.binding1Provider =", + " DoubleCheck.provider(", + " Binding1_Factory.create(", + " testComponent.shard1.binding2Provider));", + " }", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.binding1Provider = DoubleCheck.provider(", + " new SwitchingProvider<Binding1>(testComponent, 0));", + " }", + " }", + "", + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0: return (T) new Binding1(", + " testComponent.shard1.binding2Provider.get());", + " case 1: return (T) new Binding2(", + " testComponent.shard1.binding3Provider.get());", + " case 2: return (T) new Binding3(", + " testComponent.shard1.binding4Provider.get());", + " case 3: return (T) new Binding4(", + " testComponent.shard1.binding5Provider.get(),", + " testComponent.shard1.binding2Provider);", + " case 4: return (T) new Binding5(", + " testComponent.binding6Provider.get());", + " case 5: return (T) new Binding6(", + " testComponent.binding7Provider.get());", + " case 6: return (T) new Binding7();", + " default: throw new AssertionError(id);", + " }", + " }", + " }") + .build()); } private static JavaFileObject createBinding(String bindingName, String... deps) { @@ -252,29 +312,11 @@ public class ComponentShardTest { "}"); } - private static JavaFileObject createComponent(ImmutableList<String> entryPoints) { - return JavaFileObjects.forSourceLines( - "dagger.internal.codegen.TestComponent", - "package dagger.internal.codegen;", - "", - "import dagger.Component;", - "import javax.inject.Provider;", - "import javax.inject.Singleton;", - "", - "@Singleton", - "@Component", - "interface TestComponent {", - " " + entryPoints.stream().collect(joining("\n ")), - "}"); - } - - private static Compiler compilerWithAndroidMode() { - return javac() - .withProcessors(new ComponentProcessor()) - .withOptions( - ImmutableSet.builder() - .add("-Adagger.keysPerComponentShard=" + BINDINGS_PER_SHARD) - .addAll(CompilerMode.FAST_INIT_MODE.javacopts()) - .build()); + private Compiler compiler() { + return compilerWithOptions( + ImmutableSet.<String>builder() + .add("-Adagger.keysPerComponentShard=" + BINDINGS_PER_SHARD) + .addAll(compilerMode.javacopts()) + .build()); } } diff --git a/javatests/dagger/internal/codegen/ComponentValidationKtTest.java b/javatests/dagger/internal/codegen/ComponentValidationKtTest.java new file mode 100644 index 000000000..4600f9923 --- /dev/null +++ b/javatests/dagger/internal/codegen/ComponentValidationKtTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2021 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.truth.Truth.assertThat; +import static dagger.testing.compile.CompilerTests.kotlinCompiler; + +import com.google.common.collect.ImmutableList; +import com.tschuchort.compiletesting.KotlinCompilation; +import com.tschuchort.compiletesting.KotlinCompilation.ExitCode; +import com.tschuchort.compiletesting.SourceFile; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class ComponentValidationKtTest { + @Test + public void creatorMethodNameIsJavaKeyword_compilationError() { + SourceFile componentSrc = + SourceFile.Companion.kotlin( + "FooComponent.kt", + String.join( + "\n", + "package test", + "", + "import dagger.BindsInstance", + "import dagger.Component", + "", + "@Component", + "interface FooComponent {", + " @Component.Builder", + " interface Builder {", + " @BindsInstance public fun int(str: Int): Builder", + " public fun build(): FooComponent", + " }", + "}"), + false); + KotlinCompilation compilation = kotlinCompiler(); + compilation.setSources(ImmutableList.of(componentSrc)); + + KotlinCompilation.Result result = compilation.compile(); + + // TODO(b/192396673): Add error count when the feature request is fulfilled. + assertThat(result.getExitCode()).isEqualTo(ExitCode.COMPILATION_ERROR); + assertThat(result.getMessages()) + .contains("Can not use a Java keyword as method name: int(I)Ltest/FooComponent$Builder"); + } + + @Test + public void componentMethodNameIsJavaKeyword_compilationError() { + SourceFile componentSrc = + SourceFile.Companion.kotlin( + "FooComponent.kt", + String.join( + "\n", + "package test", + "", + "import dagger.BindsInstance", + "import dagger.Component", + "", + "@Component(modules = [TestModule::class])", + "interface FooComponent {", + " fun int(str: Int): String", + "}"), + false); + SourceFile moduleSrc = + SourceFile.Companion.kotlin( + "TestModule.kt", + String.join( + "\n", + "package test", + "", + "import dagger.Module", + "", + "@Module", + "interface TestModule {", + " fun providesString(): String {", + " return \"test\"", + " }", + "}"), + false); + KotlinCompilation compilation = kotlinCompiler(); + compilation.setSources(ImmutableList.of(componentSrc, moduleSrc)); + + KotlinCompilation.Result result = compilation.compile(); + + assertThat(result.getExitCode()).isEqualTo(ExitCode.COMPILATION_ERROR); + assertThat(result.getMessages()) + .contains("Can not use a Java keyword as method name: int(I)Ljava/lang/String"); + } +} diff --git a/javatests/dagger/internal/codegen/ComponentValidationTest.java b/javatests/dagger/internal/codegen/ComponentValidationTest.java index 6c642a409..157511131 100644 --- a/javatests/dagger/internal/codegen/ComponentValidationTest.java +++ b/javatests/dagger/internal/codegen/ComponentValidationTest.java @@ -44,6 +44,118 @@ public final class ComponentValidationTest { assertThat(compilation).hadErrorContaining("interface"); } + @Test + public void componentOnOverridingBuilder_failsWhenMethodNameConflictsWithStaticCreatorName() { + JavaFileObject componentFile = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules=TestModule.class)", + "interface TestComponent {", + " String builder();", + "}"); + JavaFileObject moduleFile = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "", + "@Module", + "interface TestModule {", + " @Provides", + " static String provideString() { return \"test\"; }", + "}"); + + Compilation compilation = daggerCompiler().compile(componentFile, moduleFile); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("Cannot override generated method: TestComponent.builder()"); + } + + @Test + public void componentOnOverridingCreate_failsWhenGeneratedCreateMethod() { + JavaFileObject componentFile = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules=TestModule.class)", + "interface TestComponent {", + " String create();", + "}"); + JavaFileObject moduleFile = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "", + "@Module", + "interface TestModule {", + " @Provides", + " static String provideString() { return \"test\"; }", + "}"); + + Compilation compilation = daggerCompiler().compile(componentFile, moduleFile); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("Cannot override generated method: TestComponent.create()"); + } + + @Test + public void subcomponentMethodNameBuilder_succeeds() { + JavaFileObject componentFile = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component", + "interface TestComponent {", + " TestSubcomponent.Builder subcomponent();", + "}"); + JavaFileObject subcomponentFile = + JavaFileObjects.forSourceLines( + "test.TestSubcomponent", + "package test;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent(modules=TestModule.class)", + "interface TestSubcomponent {", + " String builder();", + " @Subcomponent.Builder", + " interface Builder {", + " TestSubcomponent build();", + " }", + "}"); + JavaFileObject moduleFile = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "", + "@Module", + "interface TestModule {", + " @Provides", + " static String provideString() { return \"test\"; }", + "}"); + + Compilation compilation = daggerCompiler().compile(componentFile, subcomponentFile, moduleFile); + assertThat(compilation).succeeded(); + } + @Test public void componentOnEnum() { JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.NotAComponent", "package test;", diff --git a/javatests/dagger/internal/codegen/DaggerSuperficialValidationTest.java b/javatests/dagger/internal/codegen/DaggerSuperficialValidationTest.java new file mode 100644 index 000000000..a754108ea --- /dev/null +++ b/javatests/dagger/internal/codegen/DaggerSuperficialValidationTest.java @@ -0,0 +1,615 @@ +/* + * Copyright (C) 2021 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.truth.Truth.assertAbout; +import static com.google.common.truth.Truth.assertThat; +import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; +import static org.junit.Assert.assertThrows; + +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.XVariableElement; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import com.google.testing.compile.JavaFileObjects; +import dagger.Component; +import dagger.internal.codegen.base.DaggerSuperficialValidation; +import dagger.internal.codegen.base.DaggerSuperficialValidation.ValidationException; +import dagger.internal.codegen.javac.JavacPluginModule; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.inject.Singleton; +import javax.lang.model.element.TypeElement; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class DaggerSuperficialValidationTest { + private static final Joiner NEW_LINES = Joiner.on("\n "); + + @Test + public void missingReturnType() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "abstract class TestClass {", + " abstract MissingType blah();", + "}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass"); + ValidationException exception = + assertThrows( + ValidationException.KnownErrorType.class, + () -> superficialValidation.validateElement(testClassElement)); + assertThat(exception) + .hasMessageThat() + .contains( + NEW_LINES.join( + "Validation trace:", + " => element (CLASS): test.TestClass", + " => element (METHOD): blah()", + " => type (EXECUTABLE method): ()MissingType", + " => type (ERROR return type): MissingType")); + } + }) + .failsToCompile(); + } + + @Test + public void missingGenericReturnType() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "abstract class TestClass {", + " abstract MissingType<?> blah();", + "}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass"); + ValidationException exception = + assertThrows( + ValidationException.KnownErrorType.class, + () -> superficialValidation.validateElement(testClassElement)); + assertThat(exception) + .hasMessageThat() + .contains( + NEW_LINES.join( + "Validation trace:", + " => element (CLASS): test.TestClass", + " => element (METHOD): blah()", + " => type (EXECUTABLE method): ()<any>", + " => type (ERROR return type): <any>")); + } + }) + .failsToCompile(); + } + + @Test + public void missingReturnTypeTypeParameter() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "import java.util.Map;", + "import java.util.Set;", + "", + "abstract class TestClass {", + " abstract Map<Set<?>, MissingType<?>> blah();", + "}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass"); + ValidationException exception = + assertThrows( + ValidationException.KnownErrorType.class, + () -> superficialValidation.validateElement(testClassElement)); + assertThat(exception) + .hasMessageThat() + .contains( + NEW_LINES.join( + "Validation trace:", + " => element (CLASS): test.TestClass", + " => element (METHOD): blah()", + " => type (EXECUTABLE method): " + + "()java.util.Map<java.util.Set<?>,<any>>", + " => type (DECLARED return type): " + + "java.util.Map<java.util.Set<?>,<any>>", + " => type (ERROR type argument): <any>")); + } + }) + .failsToCompile(); + } + + @Test + public void missingTypeParameter() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", // + "package test;", + "", + "class TestClass<T extends MissingType> {}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass"); + ValidationException exception = + assertThrows( + ValidationException.KnownErrorType.class, + () -> superficialValidation.validateElement(testClassElement)); + assertThat(exception) + .hasMessageThat() + .contains( + NEW_LINES.join( + "Validation trace:", + " => element (CLASS): test.TestClass", + " => element (TYPE_PARAMETER): T", + " => type (ERROR bound type): MissingType")); + } + }) + .failsToCompile(); + } + + @Test + public void missingParameterType() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "abstract class TestClass {", + " abstract void foo(MissingType x);", + "}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass"); + ValidationException exception = + assertThrows( + ValidationException.KnownErrorType.class, + () -> superficialValidation.validateElement(testClassElement)); + assertThat(exception) + .hasMessageThat() + .contains( + NEW_LINES.join( + "Validation trace:", + " => element (CLASS): test.TestClass", + " => element (METHOD): foo(MissingType)", + " => type (EXECUTABLE method): (MissingType)void", + " => type (ERROR parameter type): MissingType")); + } + }) + .failsToCompile(); + } + + @Test + public void missingAnnotation() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", // + "package test;", + "", + "@MissingAnnotation", + "class TestClass {}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass"); + ValidationException exception = + assertThrows( + ValidationException.KnownErrorType.class, + () -> superficialValidation.validateElement(testClassElement)); + assertThat(exception) + .hasMessageThat() + .contains( + NEW_LINES.join( + "Validation trace:", + " => element (CLASS): test.TestClass", + " => annotation: @MissingAnnotation", + " => type (ERROR annotation type): MissingAnnotation")); + } + }) + .failsToCompile(); + } + + @Test + public void handlesRecursiveTypeParams() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", // + "package test;", + "", + "class TestClass<T extends Comparable<T>> {}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass"); + superficialValidation.validateElement(testClassElement); + } + }) + .compilesWithoutError(); + } + + @Test + public void handlesRecursiveType() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "abstract class TestClass {", + " abstract TestClass foo(TestClass x);", + "}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass"); + superficialValidation.validateElement(testClassElement); + } + }) + .compilesWithoutError(); + } + + @Test + public void missingWildcardBound() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "import java.util.Set;", + "", + "class TestClass {", + " Set<? extends MissingType> extendsTest() {", + " return null;", + " }", + "", + " Set<? super MissingType> superTest() {", + " return null;", + " }", + "}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass"); + ValidationException exception = + assertThrows( + ValidationException.KnownErrorType.class, + () -> superficialValidation.validateElement(testClassElement)); + assertThat(exception) + .hasMessageThat() + .contains( + NEW_LINES.join( + "Validation trace:", + " => element (CLASS): test.TestClass", + " => element (METHOD): extendsTest()", + " => type (EXECUTABLE method): ()java.util.Set<? extends MissingType>", + " => type (DECLARED return type): " + + "java.util.Set<? extends MissingType>", + " => type (WILDCARD type argument): ? extends MissingType", + " => type (ERROR extends bound type): MissingType")); + } + }) + .failsToCompile(); + } + + @Test + public void missingIntersection() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.TestClass", + "package test;", + "", + "class TestClass<T extends Number & Missing> {}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass"); + ValidationException exception = + assertThrows( + ValidationException.KnownErrorType.class, + () -> superficialValidation.validateElement(testClassElement)); + assertThat(exception) + .hasMessageThat() + .contains( + NEW_LINES.join( + "Validation trace:", + " => element (CLASS): test.TestClass", + " => element (TYPE_PARAMETER): T", + " => type (ERROR bound type): Missing")); + } + }) + .failsToCompile(); + } + + @Test + public void invalidAnnotationValue() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.Outer", + "package test;", + "", + "final class Outer {", + " @interface TestAnnotation {", + " Class[] classes();", + " }", + "", + " @TestAnnotation(classes = Foo)", + " static class TestClass {}", + "}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement testClassElement = + processingEnv.findTypeElement("test.Outer.TestClass"); + ValidationException exception = + assertThrows( + ValidationException.KnownErrorType.class, + () -> superficialValidation.validateElement(testClassElement)); + assertThat(exception) + .hasMessageThat() + .contains( + NEW_LINES.join( + "Validation trace:", + " => element (CLASS): test.Outer.TestClass", + " => annotation: @test.Outer.TestAnnotation(classes = \"<error>\")", + " => annotation method: java.lang.Class[] classes()", + " => annotation value (ARRAY): value '<error>' with expected type" + + " java.lang.Class[]", + " => annotation value (STRING): value '<error>' with expected type" + + " java.lang.Class")); + } + }) + .failsToCompile(); + } + + @Test + public void invalidAnnotationValueOnParameter() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.Outer", + "package test;", + "", + "final class Outer {", + " @interface TestAnnotation {", + " Class[] classes();", + " }", + "", + " static class TestClass {", + " TestClass(@TestAnnotation(classes = Foo) String strParam) {}", + " }", + "}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement testClassElement = + processingEnv.findTypeElement("test.Outer.TestClass"); + XConstructorElement constructor = testClassElement.getConstructors().get(0); + XVariableElement parameter = constructor.getParameters().get(0); + ValidationException exception = + assertThrows( + ValidationException.KnownErrorType.class, + () -> superficialValidation.validateElement(parameter)); + assertThat(exception) + .hasMessageThat() + .contains( + NEW_LINES.join( + "Validation trace:", + " => element (CLASS): test.Outer.TestClass", + " => element (CONSTRUCTOR): TestClass(java.lang.String)", + " => element (PARAMETER): strParam", + " => annotation: @test.Outer.TestAnnotation(classes = \"<error>\")", + " => annotation method: java.lang.Class[] classes()", + " => annotation value (ARRAY): value '<error>' with expected type" + + " java.lang.Class[]", + " => annotation value (STRING): value '<error>' with expected type" + + " java.lang.Class")); + } + }) + .failsToCompile(); + } + + @Test + public void invalidSuperclassInTypeHierarchy() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.Outer", + "package test;", + "", + "final class Outer {", + " Child<Long> getChild() { return null; }", + "", + " static class Child<T> extends Parent<T> {}", + "", + " static class Parent<T> extends MissingType<T> {}", + "}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement outerElement = processingEnv.findTypeElement("test.Outer"); + XMethodElement getChildMethod = outerElement.getDeclaredMethods().get(0); + ValidationException exception = + assertThrows( + ValidationException.KnownErrorType.class, + () -> + superficialValidation.validateTypeHierarchyOf( + "return type", getChildMethod, getChildMethod.getReturnType())); + assertThat(exception) + .hasMessageThat() + .contains( + NEW_LINES.join( + "Validation trace:", + " => element (CLASS): test.Outer", + " => element (METHOD): getChild()", + " => type (DECLARED return type): test.Outer.Child<java.lang.Long>", + " => type (DECLARED supertype): test.Outer.Parent<java.lang.Long>", + " => type (ERROR supertype): MissingType<T>")); + } + }) + .failsToCompile(); + } + + @Test + public void invalidSuperclassTypeParameterInTypeHierarchy() { + JavaFileObject javaFileObject = + JavaFileObjects.forSourceLines( + "test.Outer", + "package test;", + "", + "final class Outer {", + " Child getChild() { return null; }", + "", + " static class Child extends Parent<MissingType> {}", + "", + " static class Parent<T> {}", + "}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith( + new AssertingProcessor() { + @Override + void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) { + XTypeElement outerElement = processingEnv.findTypeElement("test.Outer"); + XMethodElement getChildMethod = outerElement.getDeclaredMethods().get(0); + ValidationException exception = + assertThrows( + ValidationException.KnownErrorType.class, + () -> + superficialValidation.validateTypeHierarchyOf( + "return type", getChildMethod, getChildMethod.getReturnType())); + assertThat(exception) + .hasMessageThat() + .contains( + NEW_LINES.join( + "Validation trace:", + " => element (CLASS): test.Outer", + " => element (METHOD): getChild()", + " => type (DECLARED return type): test.Outer.Child", + " => type (DECLARED supertype): test.Outer.Parent<MissingType>", + " => type (ERROR type argument): MissingType")); + } + }) + .failsToCompile(); + } + + private abstract static class AssertingProcessor extends AbstractProcessor { + private boolean processed = false; + + @Override + public Set<String> getSupportedAnnotationTypes() { + return ImmutableSet.of("*"); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (!processed) { + processed = true; // only process once. + TestComponent component = + DaggerDaggerSuperficialValidationTest_TestComponent.builder() + .javacPluginModule( + new JavacPluginModule( + processingEnv.getElementUtils(), processingEnv.getTypeUtils())) + .build(); + try { + runAssertions(component.processingEnv(), component.superficialValidation()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return false; + } + + abstract void runAssertions( + XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) + throws Exception; + } + + @Singleton + @Component(modules = JavacPluginModule.class) + interface TestComponent { + XProcessingEnv processingEnv(); + + DaggerSuperficialValidation superficialValidation(); + } +} diff --git a/javatests/dagger/internal/codegen/DelegateBindingExpressionTest.java b/javatests/dagger/internal/codegen/DelegateRequestRepresentationTest.java index dc8b7588d..e24bebb86 100644 --- a/javatests/dagger/internal/codegen/DelegateBindingExpressionTest.java +++ b/javatests/dagger/internal/codegen/DelegateRequestRepresentationTest.java @@ -32,7 +32,7 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) -public class DelegateBindingExpressionTest { +public class DelegateRequestRepresentationTest { @Parameters(name = "{0}") public static Collection<Object[]> parameters() { return CompilerMode.TEST_PARAMETERS; @@ -40,7 +40,7 @@ public class DelegateBindingExpressionTest { private final CompilerMode compilerMode; - public DelegateBindingExpressionTest(CompilerMode compilerMode) { + public DelegateRequestRepresentationTest(CompilerMode compilerMode) { this.compilerMode = compilerMode; } @@ -147,32 +147,20 @@ public class DelegateBindingExpressionTest { "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, - " private volatile Object regularScoped = new MemoizedSentinel();", - " private volatile ReusableScoped reusableScoped;", - "", - " private RegularScoped regularScoped() {", - " Object local = regularScoped;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = regularScoped;", - " if (local instanceof MemoizedSentinel) {", - " local = new RegularScoped();", - " regularScoped = DoubleCheck.reentrantCheck(regularScoped, local);", - " }", - " }", - " }", - " return (RegularScoped) local;", - " }", - "", - " private ReusableScoped reusableScoped() {", - " Object local = reusableScoped;", - " if (local == null) {", - " local = new ReusableScoped();", - " reusableScoped = (ReusableScoped) local;", - " }", - " return (ReusableScoped) local;", - " }", - "") + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.regularScopedProvider =", + " DoubleCheck.provider(", + " new SwitchingProvider<RegularScoped>(testComponent, 0));", + " this.reusableScopedProvider =", + " SingleCheck.provider(", + " new SwitchingProvider<ReusableScoped>(testComponent, 1));", + " this.reusableProvider =", + " DoubleCheck.provider((Provider) reusableScopedProvider);", + " this.unscopedProvider = new SwitchingProvider<>(testComponent, 2);", + " this.unscopedProvider2 =", + " DoubleCheck.provider((Provider) unscopedProvider);", + " }") .addLinesIn( DEFAULT_MODE, " @SuppressWarnings(\"unchecked\")", @@ -186,8 +174,20 @@ public class DelegateBindingExpressionTest { " this.unscopedProvider = DoubleCheck.provider(", " (Provider) Unscoped_Factory.create());", " }") - .addLines( // - "}") + .addLinesIn( + FAST_INIT_MODE, + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0: return (T) new RegularScoped();", + " case 1: return (T) new ReusableScoped();", + " case 2: return (T) new Unscoped();", + " default: throw new AssertionError(id);", + " }", + " }", + " }") .build()); } @@ -226,32 +226,18 @@ public class DelegateBindingExpressionTest { "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, - " private volatile Object regularScoped = new MemoizedSentinel();", - " private volatile ReusableScoped reusableScoped;", - "", - " private RegularScoped regularScoped() {", - " Object local = regularScoped;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = regularScoped;", - " if (local instanceof MemoizedSentinel) {", - " local = new RegularScoped();", - " regularScoped = DoubleCheck.reentrantCheck(regularScoped, local);", - " }", - " }", - " }", - " return (RegularScoped) local;", - " }", - "", - " private ReusableScoped reusableScoped() {", - " Object local = reusableScoped;", - " if (local == null) {", - " local = new ReusableScoped();", - " reusableScoped = (ReusableScoped) local;", - " }", - " return (ReusableScoped) local;", - " }", - "") + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.regularScopedProvider =", + " DoubleCheck.provider(", + " new SwitchingProvider<RegularScoped>(testComponent, 0));", + " this.reusableScopedProvider =", + " SingleCheck.provider(", + " new SwitchingProvider<ReusableScoped>(testComponent, 1));", + " this.unscopedProvider = new SwitchingProvider<>(testComponent, 2);", + " this.unscopedProvider2 =", + " SingleCheck.provider((Provider) unscopedProvider);", + " }") .addLinesIn( DEFAULT_MODE, " @SuppressWarnings(\"unchecked\")", @@ -263,8 +249,6 @@ public class DelegateBindingExpressionTest { " this.unscopedProvider = SingleCheck.provider(", " (Provider) Unscoped_Factory.create());", " }") - .addLines( // - "}") .build()); } @@ -299,35 +283,9 @@ public class DelegateBindingExpressionTest { "package test;", "", GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {") - .addLinesIn( - FAST_INIT_MODE, - " private volatile Object regularScoped = new MemoizedSentinel();", - " private volatile ReusableScoped reusableScoped;", - "", - " private RegularScoped regularScoped() {", - " Object local = regularScoped;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = regularScoped;", - " if (local instanceof MemoizedSentinel) {", - " local = new RegularScoped();", - " regularScoped = DoubleCheck.reentrantCheck(regularScoped, local);", - " }", - " }", - " }", - " return (RegularScoped) local;", - " }", - "", - " private ReusableScoped reusableScoped() {", - " Object local = reusableScoped;", - " if (local == null) {", - " local = new ReusableScoped();", - " reusableScoped = (ReusableScoped) local;", - " }", - " return (ReusableScoped) local;", - " }", - "") + "final class DaggerTestComponent implements TestComponent {", + " private Provider<RegularScoped> regularScopedProvider;", + " private Provider<ReusableScoped> reusableScopedProvider;") .addLinesIn( DEFAULT_MODE, " @SuppressWarnings(\"unchecked\")", @@ -337,8 +295,17 @@ public class DelegateBindingExpressionTest { " this.reusableScopedProvider = ", " SingleCheck.provider(ReusableScoped_Factory.create());", " }") - .addLines( // - "}") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.regularScopedProvider =", + " DoubleCheck.provider(", + " new SwitchingProvider<RegularScoped>(testComponent, 0));", + " this.reusableScopedProvider =", + " SingleCheck.provider(", + " new SwitchingProvider<ReusableScoped>(testComponent, 1));", + " }") .build()); } @@ -401,43 +368,29 @@ public class DelegateBindingExpressionTest { "package test;", "", GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {") + "final class DaggerTestComponent implements TestComponent {", + " @SuppressWarnings(\"rawtypes\")", + " private Provider subtypeProvider;") .addLinesIn( DEFAULT_MODE, - " @SuppressWarnings(\"rawtypes\")", - " private Provider subtypeProvider;", - "", " @SuppressWarnings(\"unchecked\")", " private void initialize() {", " this.subtypeProvider = DoubleCheck.provider(Subtype_Factory.create());", - " }", - "", - " @Override", - " public Supertype supertype() {", - " return (Supertype) subtypeProvider.get();", " }") .addLinesIn( FAST_INIT_MODE, - " private volatile Object subtype = new MemoizedSentinel();", - "", - " private Object subtype() {", - " Object local = subtype;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = subtype;", - " if (local instanceof MemoizedSentinel) {", - " local = Subtype_Factory.newInstance();", - " subtype = DoubleCheck.reentrantCheck(subtype, local);", - " }", - " }", - " }", - " return (Object) local;", - " }", - "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.subtypeProvider =", + " DoubleCheck.provider(", + " new SwitchingProvider<Object>(testComponent, 0));", + " }") + .addLines( " @Override", " public Supertype supertype() {", - " return (Supertype) subtype();", - " }") + " return (Supertype) subtypeProvider.get();", + " }", + "}") .build()); } @@ -509,9 +462,7 @@ public class DelegateBindingExpressionTest { "package test;", "", GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {") - .addLinesIn( - DEFAULT_MODE, + "final class DaggerTestComponent implements TestComponent {", " @SuppressWarnings(\"rawtypes\")", " private Provider subtypeProvider;", "", @@ -521,28 +472,6 @@ public class DelegateBindingExpressionTest { " return UsesSupertype_Factory.newInstance(subtypeProvider.get());", " }", "}") - .addLinesIn( - FAST_INIT_MODE, - " private volatile Object subtype = new MemoizedSentinel();", - "", - " private Object subtype() {", - " Object local = subtype;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = subtype;", - " if (local instanceof MemoizedSentinel) {", - " local = Subtype_Factory.newInstance();", - " subtype = DoubleCheck.reentrantCheck(subtype, local);", - " }", - " }", - " }", - " return (Object) local;", - " }", - "", - " @Override", - " public UsesSupertype usesSupertype() {", - " return UsesSupertype_Factory.newInstance(subtype());", - " }") .build()); } @@ -582,6 +511,7 @@ public class DelegateBindingExpressionTest { "@Component(modules = TestModule.class)", "interface TestComponent {", " Provider<CharSequence> charSequence();", + " CharSequence charSequenceInstance();", "", " @Named(\"named\") Provider<String> namedString();", "}"); @@ -604,7 +534,12 @@ public class DelegateBindingExpressionTest { DEFAULT_MODE, " @Override", " public Provider<CharSequence> charSequence() {", - " return (Provider) TestModule_ProvideStringFactory.create();", + " return ((Provider) TestModule_ProvideStringFactory.create());", + " }", + "", + " @Override", + " public CharSequence charSequenceInstance() {", + " return TestModule_ProvideStringFactory.provideString();", " }", "", " @Override", @@ -614,36 +549,30 @@ public class DelegateBindingExpressionTest { "}") .addLinesIn( FAST_INIT_MODE, - " private volatile Provider<String> provideStringProvider;", + " private Provider<String> provideStringProvider;", "", - " private Provider<String> stringProvider() {", - " Object local = provideStringProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " provideStringProvider = (Provider<String>) local;", - " }", - " return (Provider<String>) local;", + " @Override", + " public Provider<CharSequence> charSequence() {", + " return ((Provider) provideStringProvider);", " }", "", " @Override", - " public Provider<CharSequence> charSequence() {", - " return (Provider) stringProvider();", + " public CharSequence charSequenceInstance() {", + " return (CharSequence) ((Provider) provideStringProvider).get();", " }", "", " @Override", " public Provider<String> namedString() {", - " return stringProvider();", + " return provideStringProvider;", " }", "", - " private final class SwitchingProvider<T> implements Provider<T> {", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", - " case 0:", - " return (T) TestModule_ProvideStringFactory.provideString();", - " default:", - " throw new AssertionError(id);", + " case 0: return (T) TestModule_ProvideStringFactory.provideString();", + " default: throw new AssertionError(id);", " }", " }", " }") @@ -705,45 +634,39 @@ public class DelegateBindingExpressionTest { DEFAULT_MODE, " @Override", " public Provider<CharSequence> charSequence() {", - " return (Provider) TestModule_ProvideStringFactory.create();", + " return ((Provider) TestModule_ProvideStringFactory.create());", " }", " @Override", " public Provider<Object> object() {", - " return (Provider) TestModule_ProvideStringFactory.create();", + " return ((Provider) TestModule_ProvideStringFactory.create());", " }", "}") .addLinesIn( FAST_INIT_MODE, - " private volatile Provider<String> provideStringProvider;", + " private Provider<String> provideStringProvider;", "", - " private Provider<String> stringProvider() {", - " Object local = provideStringProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " provideStringProvider = (Provider<String>) local;", - " }", - " return (Provider<String>) local;", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.provideStringProvider = new SwitchingProvider<>(testComponent, 0);", " }", "", " @Override", " public Provider<CharSequence> charSequence() {", - " return (Provider) stringProvider();", + " return ((Provider) provideStringProvider);", " }", "", " @Override", " public Provider<Object> object() {", - " return (Provider) stringProvider();", + " return ((Provider) provideStringProvider);", " }", "", - " private final class SwitchingProvider<T> implements Provider<T> {", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", - " case 0:", - " return (T) TestModule_ProvideStringFactory.provideString();", - " default:", - " throw new AssertionError(id);", + " case 0: return (T) TestModule_ProvideStringFactory.provideString();", + " default: throw new AssertionError(id);", " }", " }", " }") @@ -811,28 +734,20 @@ public class DelegateBindingExpressionTest { DEFAULT_MODE, " @Override", " public Provider<Supertype> supertypeProvider() {", - " return (Provider) Subtype_Factory.create();", + " return ((Provider) Subtype_Factory.create());", " }", "}") .addLinesIn( FAST_INIT_MODE, - " private volatile Provider subtypeProvider;", - "", - " private Provider subtypeProvider() {", - " Object local = subtypeProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " subtypeProvider = (Provider) local;", - " }", - " return (Provider) local;", - " }", + " @SuppressWarnings(\"rawtypes\")", + " private Provider subtypeProvider;", "", " @Override", " public Provider<Supertype> supertypeProvider() {", - " return subtypeProvider();", + " return subtypeProvider;", " }", "", - " private final class SwitchingProvider<T> implements Provider<T> {", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", @@ -897,70 +812,41 @@ public class DelegateBindingExpressionTest { "package test;", "", GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {") + "final class DaggerTestComponent implements TestComponent {", + " private Provider<String> provideStringProvider;", + " private Provider<Object> bindStringProvider;") .addLinesIn( DEFAULT_MODE, - " private Provider<String> provideStringProvider;", - " private Provider<Object> bindStringProvider;", - "", " @SuppressWarnings(\"unchecked\")", " private void initialize() {", " this.provideStringProvider =", " SingleCheck.provider(TestModule_ProvideStringFactory.create());", " this.bindStringProvider =", " DoubleCheck.provider((Provider) provideStringProvider);", - " }", - "", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.provideStringProvider =", + " SingleCheck.provider(", + " new SwitchingProvider<String>(testComponent, 0));", + " this.bindStringProvider =", + " DoubleCheck.provider((Provider) provideStringProvider);", + " }") + .addLines( " @Override", " public Provider<Object> object() {", " return bindStringProvider;", - " }", - "}") + " }") .addLinesIn( FAST_INIT_MODE, - " private volatile String string;", - " private volatile Object object = new MemoizedSentinel();", - " private volatile Provider<Object> bindStringProvider;", - "", - " private String string() {", - " Object local = string;", - " if (local == null) {", - " local = TestModule_ProvideStringFactory.provideString();", - " string = (String) local;", - " }", - " return (String) local;", - " }", - "", - " private Object object2() {", - " Object local = object;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = object;", - " if (local instanceof MemoizedSentinel) {", - " local = string();", - " object = DoubleCheck.reentrantCheck(object, local);", - " }", - " }", - " }", - " return (Object) local;", - " }", - "", - " @Override", - " public Provider<Object> object() {", - " Object local = bindStringProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " bindStringProvider = (Provider<Object>) local;", - " }", - " return (Provider<Object>) local;", - " }", - "", - " private final class SwitchingProvider<T> implements Provider<T> {", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", - " case 0: return (T) DaggerTestComponent.this.object2();", + " case 0: return (T) TestModule_ProvideStringFactory.provideString();", " default: throw new AssertionError(id);", " }", " }", diff --git a/javatests/dagger/internal/codegen/DependencyCycleValidationTest.java b/javatests/dagger/internal/codegen/DependencyCycleValidationTest.java index 22547b5cb..2b7ae64da 100644 --- a/javatests/dagger/internal/codegen/DependencyCycleValidationTest.java +++ b/javatests/dagger/internal/codegen/DependencyCycleValidationTest.java @@ -82,6 +82,11 @@ public class DependencyCycleValidationTest { " Outer.B(aParam)", " Outer.B is injected at", " Outer.C(bParam)", + " Outer.C is injected at", + " Outer.A(cParam)", + " ...", + "", + "The cycle is requested via:", " Outer.C is requested at", " Outer.CComponent.getC()")) .inFile(SIMPLE_CYCLIC_DEPENDENCY) @@ -104,6 +109,9 @@ public class DependencyCycleValidationTest { " Outer.B(aParam)", " Outer.B is injected at", " Outer.C(bParam)", + " Outer.C is injected at", + " Outer.A(cParam)", + " ...", "", "======================", "Full classname legend:", @@ -178,6 +186,11 @@ public class DependencyCycleValidationTest { " Outer.B is injected at", " Outer.C(bParam)", " Outer.C is injected at", + " Outer.A(cParam)", + " ...", + "", + "The cycle is requested via:", + " Outer.C is injected at", " Outer.D(cParam)", " Outer.D is requested at", " Outer.DComponent.getD()")) @@ -242,6 +255,11 @@ public class DependencyCycleValidationTest { " Outer.B(aParam)", " Outer.B is injected at", " Outer.C(bParam)", + " Outer.C is injected at", + " Outer.CModule.c(c)", + " ...", + "", + "The cycle is requested via:", " Outer.C is requested at", " Outer.CComponent.getC()")) .inFile(component) @@ -303,6 +321,11 @@ public class DependencyCycleValidationTest { " Outer.B(aParam)", " Outer.B is injected at", " Outer.C(bParam)", + " Outer.C is injected at", + " Outer.CModule.c(c)", + " ...", + "", + "The cycle is requested via:", " Outer.C is requested at", " Outer.CComponent.getC()")) .inFile(component) @@ -357,6 +380,11 @@ public class DependencyCycleValidationTest { " Outer.B(aParam)", " Outer.B is injected at", " Outer.C(bParam)", + " Outer.C is injected at", + " Outer.A(cParam)", + " ...", + "", + "The cycle is requested via:", " Provider<Outer.C> is injected at", " Outer.D(cParam)", " Outer.D is requested at", @@ -439,6 +467,11 @@ public class DependencyCycleValidationTest { " CycleModule.object(string)", " Object is injected at", " CycleModule.string(object)", + " String is injected at", + " CycleModule.object(string)", + " ...", + "", + "The cycle is requested via:", " String is requested at", " Grandchild.entry()")) .inFile(parent) @@ -521,6 +554,11 @@ public class DependencyCycleValidationTest { " CycleModule.object(string)", " Object is injected at", " CycleModule.string(object)", + " String is injected at", + " CycleModule.object(string)", + " ...", + "", + "The cycle is requested via:", " String is requested at", " Child.entry() [Parent → Child]")) .inFile(parent) @@ -572,6 +610,11 @@ public class DependencyCycleValidationTest { " TestModule.bindQualified(unqualified)", " @SomeQualifier Object is injected at", " TestModule.bindUnqualified(qualified)", + " Object is injected at", + " TestModule.bindQualified(unqualified)", + " ...", + "", + "The cycle is requested via:", " Object is requested at", " TestComponent.unqualified()")) .inFile(component) @@ -612,6 +655,11 @@ public class DependencyCycleValidationTest { "Found a dependency cycle:", " Object is injected at", " TestModule.bindToSelf(sameKey)", + " Object is injected at", + " TestModule.bindToSelf(sameKey)", + " ...", + "", + "The cycle is requested via:", " Object is requested at", " TestComponent.selfReferential()")) .inFile(component) @@ -666,6 +714,11 @@ public class DependencyCycleValidationTest { " test.B.a", " test.B is injected at", " test.A.b", + " ...", + "", + "The cycle is requested via:", + " test.B is injected at", + " test.A.b", " test.A is injected at", " CycleComponent.inject(test.A)")) .inFile(component) diff --git a/javatests/dagger/internal/codegen/DuplicateBindingsValidationTest.java b/javatests/dagger/internal/codegen/DuplicateBindingsValidationTest.java index fbda59d78..ecb11468a 100644 --- a/javatests/dagger/internal/codegen/DuplicateBindingsValidationTest.java +++ b/javatests/dagger/internal/codegen/DuplicateBindingsValidationTest.java @@ -1096,4 +1096,74 @@ public class DuplicateBindingsValidationTest { .onLineContaining("interface Parent"); assertThat(compilation).hadErrorCount(1); } + + // Tests the format of the error for a somewhat complex binding method. + @Test + public void formatTest() { + JavaFileObject modules = + JavaFileObjects.forSourceLines( + "test.Modules", + "package test;", + "", + "import com.google.common.collect.ImmutableList;", + "import dagger.Module;", + "import dagger.Provides;", + "import javax.inject.Singleton;", + "", + "interface Modules {", + " @interface Foo {", + " Class<?> bar();", + " }", + "", + " @Module", + " interface Module1 {", + " @Provides", + " @Singleton", + " @Foo(bar = String.class)", + " static String foo(", + " @SuppressWarnings(\"unused\") int a,", + " @SuppressWarnings(\"unused\") ImmutableList<Boolean> blah) {", + " return \"\";", + " }", + " }", + "", + " @Module", + " interface Module2 {", + " @Provides", + " @Singleton", + " @Foo(bar = String.class)", + " static String foo(", + " @SuppressWarnings(\"unused\") int a,", + " @SuppressWarnings(\"unused\") ImmutableList<Boolean> blah) {", + " return \"\";", + " }", + " }", + "}"); + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.BindsInstance;", + "import dagger.Component;", + "import javax.inject.Singleton;", + "", + "@Singleton", + "@Component(modules = {Modules.Module1.class, Modules.Module2.class})", + "interface TestComponent {", + " @Modules.Foo(bar = String.class) String foo();", + "}"); + Compilation compilation = daggerCompiler().compile(modules, component); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + message( + "String is bound multiple times:", + " @Provides @Singleton @Modules.Foo(bar = String.class) String " + + "Modules.Module1.foo(int, ImmutableList<Boolean>)", + " @Provides @Singleton @Modules.Foo(bar = String.class) String " + + "Modules.Module2.foo(int, ImmutableList<Boolean>)")) + .inFile(component); + } } diff --git a/javatests/dagger/internal/codegen/ElidedFactoriesTest.java b/javatests/dagger/internal/codegen/ElidedFactoriesTest.java index 0c557c0a8..5180c4894 100644 --- a/javatests/dagger/internal/codegen/ElidedFactoriesTest.java +++ b/javatests/dagger/internal/codegen/ElidedFactoriesTest.java @@ -17,6 +17,8 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; +import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; import com.google.testing.compile.Compilation; @@ -84,6 +86,8 @@ public class ElidedFactoriesTest { "", GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", + " private final DaggerSimpleComponent simpleComponent = this;", + "", " private DaggerSimpleComponent() {}", "", " public static Builder builder() {", @@ -100,8 +104,7 @@ public class ElidedFactoriesTest { " }", "", " static final class Builder {", - " private Builder() {", - " }", + " private Builder() {}", "", " public SimpleComponent build() {", " return new DaggerSimpleComponent();", @@ -169,147 +172,64 @@ public class ElidedFactoriesTest { "interface SimpleComponent {", " NeedsProvider needsProvider();", "}"); - JavaFileObject generatedComponent; - switch (compilerMode) { - case FAST_INIT_MODE: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerSimpleComponent", - "package test;", - "", - GeneratedLines.generatedImports( - "import dagger.internal.DoubleCheck;", - "import dagger.internal.MemoizedSentinel;", - "import javax.inject.Provider;"), - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerSimpleComponent implements SimpleComponent {", - " private volatile Object scopedType = new MemoizedSentinel();", - " private volatile Provider<DependsOnScoped> dependsOnScopedProvider;", - "", - " private DaggerSimpleComponent() {}", - "", - " public static Builder builder() {", - " return new Builder();", - " }", - "", - " public static SimpleComponent create() {", - " return new Builder().build();", - " }", - "", - " private ScopedType scopedType() {", - " Object local = scopedType;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = scopedType;", - " if (local instanceof MemoizedSentinel) {", - " local = new ScopedType();", - " scopedType = DoubleCheck.reentrantCheck(scopedType, local);", - " }", - " }", - " }", - " return (ScopedType) local;", - " }", - "", - " private DependsOnScoped dependsOnScoped() {", - " return new DependsOnScoped(scopedType());", - " }", - "", - " private Provider<DependsOnScoped> dependsOnScopedProvider() {", - " Object local = dependsOnScopedProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " dependsOnScopedProvider = (Provider<DependsOnScoped>) local;", - " }", - " return (Provider<DependsOnScoped>) local;", - " }", - "", - " @Override", - " public NeedsProvider needsProvider() {", - " return new NeedsProvider(dependsOnScopedProvider());", - " }", - "", - " static final class Builder {", - " private Builder() {}", - "", - " public SimpleComponent build() {", - " return new DaggerSimpleComponent();", - " }", - " }", - " private final class SwitchingProvider<T> implements Provider<T> {", - " private final int id;", - "", - " SwitchingProvider(int id) {", - " this.id = id;", - " }", - "", - " @SuppressWarnings(\"unchecked\")", - " @Override", - " public T get() {", - " switch (id) {", - " case 0: return (T) DaggerSimpleComponent.this.dependsOnScoped();", - " default: throw new AssertionError(id);", - " }", - " }", - " }", - "}"); - break; - default: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerSimpleComponent", - "package test;", - "", - GeneratedLines.generatedImports( - "import dagger.internal.DoubleCheck;", - "import javax.inject.Provider;"), - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerSimpleComponent implements SimpleComponent {", - " private Provider<ScopedType> scopedTypeProvider;", - " private Provider<DependsOnScoped> dependsOnScopedProvider;", - "", - " private DaggerSimpleComponent() {", - " initialize();", - " }", - "", - " public static Builder builder() {", - " return new Builder();", - " }", - "", - " public static SimpleComponent create() {", - " return new Builder().build();", - " }", - "", - " @SuppressWarnings(\"unchecked\")", - " private void initialize() {", - " this.scopedTypeProvider = DoubleCheck.provider(ScopedType_Factory.create());", - " this.dependsOnScopedProvider = ", - " DependsOnScoped_Factory.create(scopedTypeProvider);", - " }", - "", - " @Override", - " public NeedsProvider needsProvider() {", - " return new NeedsProvider(dependsOnScopedProvider);", - " }", - "", - " static final class Builder {", - " private Builder() {", - " }", - "", - " public SimpleComponent build() {", - " return new DaggerSimpleComponent();", - " }", - " }", - "}"); - } + Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(scopedType, dependsOnScoped, componentFile, needsProvider); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerSimpleComponent") - .hasSourceEquivalentTo(generatedComponent); + .containsElementsIn( + compilerMode + .javaFileBuilder("test.DaggerSimpleComponent") + .addLines( + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "final class DaggerSimpleComponent implements SimpleComponent {", + " private final DaggerSimpleComponent simpleComponent = this;", + "", + " private Provider<ScopedType> scopedTypeProvider;", + " private Provider<DependsOnScoped> dependsOnScopedProvider;") + .addLinesIn( + DEFAULT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.scopedTypeProvider =", + " DoubleCheck.provider(ScopedType_Factory.create());", + " this.dependsOnScopedProvider = ", + " DependsOnScoped_Factory.create(scopedTypeProvider);", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.scopedTypeProvider =", + " DoubleCheck.provider(", + " new SwitchingProvider<ScopedType>(simpleComponent, 1));", + " this.dependsOnScopedProvider = ", + " new SwitchingProvider<>(simpleComponent, 0);", + " }") + .addLines( + " @Override", + " public NeedsProvider needsProvider() {", + " return new NeedsProvider(dependsOnScopedProvider);", + " }") + .addLinesIn( + FAST_INIT_MODE, + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0: return (T) new DependsOnScoped(", + " simpleComponent.scopedTypeProvider.get());", + " case 1: return (T) new ScopedType();", + " default: throw new AssertionError(id);", + " }", + " }", + " }") + .build()); } @Test @@ -363,130 +283,63 @@ public class ElidedFactoriesTest { " DependsOnScoped dependsOnScoped();", "}"); - JavaFileObject generatedComponent; - switch (compilerMode) { - case FAST_INIT_MODE: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerSimpleComponent", - "package test;", - "", - GeneratedLines.generatedImports( - "import dagger.internal.DoubleCheck;", - "import dagger.internal.MemoizedSentinel;"), - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerSimpleComponent implements SimpleComponent {", - " private volatile Object scopedType = new MemoizedSentinel();", - "", - " private DaggerSimpleComponent() {}", - "", - " public static Builder builder() {", - " return new Builder();", - " }", - "", - " public static SimpleComponent create() {", - " return new Builder().build();", - " }", - "", - " private ScopedType scopedType() {", - " Object local = scopedType;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = scopedType;", - " if (local instanceof MemoizedSentinel) {", - " local = new ScopedType();", - " scopedType = DoubleCheck.reentrantCheck(scopedType, local);", - " }", - " }", - " }", - " return (ScopedType) local;", - " }", - "", - " @Override", - " public Sub sub() {", - " return new SubImpl();", - " }", - "", - " static final class Builder {", - " private Builder() {}", - "", - " public SimpleComponent build() {", - " return new DaggerSimpleComponent();", - " }", - " }", - "", - " private final class SubImpl implements Sub {", - " private SubImpl() {}", - "", - " @Override", - " public DependsOnScoped dependsOnScoped() {", - " return new DependsOnScoped(DaggerSimpleComponent.this.scopedType());", - " }", - " }", - "}"); - break; - default: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerSimpleComponent", - "package test;", - "", - GeneratedLines.generatedImports( - "import dagger.internal.DoubleCheck;", - "import javax.inject.Provider;"), - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerSimpleComponent implements SimpleComponent {", - " private Provider<ScopedType> scopedTypeProvider;", - "", - " private DaggerSimpleComponent() {", - " initialize();", - " }", - "", - " public static Builder builder() {", - " return new Builder();", - " }", - "", - " public static SimpleComponent create() {", - " return new Builder().build();", - " }", - "", - " @SuppressWarnings(\"unchecked\")", - " private void initialize() {", - " this.scopedTypeProvider = DoubleCheck.provider(ScopedType_Factory.create());", - " }", - "", - " @Override", - " public Sub sub() {", - " return new SubImpl();", - " }", - "", - " static final class Builder {", - " private Builder() {}", - "", - " public SimpleComponent build() {", - " return new DaggerSimpleComponent();", - " }", - " }", - "", - " private final class SubImpl implements Sub {", - " private SubImpl() {}", - "", - " @Override", - " public DependsOnScoped dependsOnScoped() {", - " return new DependsOnScoped(", - " DaggerSimpleComponent.this.scopedTypeProvider.get());", - " }", - " }", - "}"); - } Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile(scopedType, dependsOnScoped, componentFile, subcomponentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerSimpleComponent") - .hasSourceEquivalentTo(generatedComponent); + .containsElementsIn( + compilerMode + .javaFileBuilder("test.DaggerSimpleComponent") + .addLines( + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "final class DaggerSimpleComponent implements SimpleComponent {", + " private final DaggerSimpleComponent simpleComponent = this;", + " private Provider<ScopedType> scopedTypeProvider;") + .addLinesIn( + DEFAULT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.scopedTypeProvider = DoubleCheck.provider(", + " ScopedType_Factory.create());", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.scopedTypeProvider = DoubleCheck.provider(", + " new SwitchingProvider<ScopedType>(simpleComponent, 0));", + " }") + .addLines( + " @Override", + " public Sub sub() {", + " return new SubImpl(simpleComponent);", + " }", + "", + " private static final class SubImpl implements Sub {", + " private final DaggerSimpleComponent simpleComponent;", + " private final SubImpl subImpl = this;", + "", + " @Override", + " public DependsOnScoped dependsOnScoped() {", + " return new DependsOnScoped(simpleComponent.scopedTypeProvider.get());", + " }", + " }") + .addLinesIn( + FAST_INIT_MODE, + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0: return (T) new ScopedType();", + " default: throw new AssertionError(id);", + " }", + " }", + " }") + .build()); } } diff --git a/javatests/dagger/internal/codegen/FrameworkTypeMapperTest.java b/javatests/dagger/internal/codegen/FrameworkTypeMapperTest.java index e27534d7b..3e7992b6d 100644 --- a/javatests/dagger/internal/codegen/FrameworkTypeMapperTest.java +++ b/javatests/dagger/internal/codegen/FrameworkTypeMapperTest.java @@ -17,11 +17,11 @@ package dagger.internal.codegen; import static com.google.common.truth.Truth.assertThat; -import static dagger.model.RequestKind.INSTANCE; -import static dagger.model.RequestKind.LAZY; -import static dagger.model.RequestKind.PRODUCED; -import static dagger.model.RequestKind.PRODUCER; -import static dagger.model.RequestKind.PROVIDER; +import static dagger.spi.model.RequestKind.INSTANCE; +import static dagger.spi.model.RequestKind.LAZY; +import static dagger.spi.model.RequestKind.PRODUCED; +import static dagger.spi.model.RequestKind.PRODUCER; +import static dagger.spi.model.RequestKind.PROVIDER; import dagger.internal.codegen.binding.FrameworkType; import dagger.internal.codegen.binding.FrameworkTypeMapper; diff --git a/javatests/dagger/internal/codegen/InjectConstructorFactoryGeneratorTest.java b/javatests/dagger/internal/codegen/InjectConstructorFactoryGeneratorTest.java index 3e78d6b5d..175903354 100644 --- a/javatests/dagger/internal/codegen/InjectConstructorFactoryGeneratorTest.java +++ b/javatests/dagger/internal/codegen/InjectConstructorFactoryGeneratorTest.java @@ -134,8 +134,12 @@ public final class InjectConstructorFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class GenericClass_Factory<T> implements Factory<GenericClass<T>> {", " private final Provider<T> tProvider;", @@ -183,8 +187,12 @@ public final class InjectConstructorFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class GenericClass_Factory<A, B> implements", " Factory<GenericClass<A, B>> {", @@ -234,8 +242,13 @@ public final class InjectConstructorFactoryGeneratorTest { "test.GenericClass_Factory", "package test;", "", - GeneratedLines.generatedImports("import dagger.internal.Factory;"), + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class GenericClass_Factory<T> implements Factory<GenericClass<T>> {", " @Override", @@ -279,8 +292,12 @@ public final class InjectConstructorFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class GenericClass_Factory<A, B>", " implements Factory<GenericClass<A, B>> {", @@ -331,9 +348,13 @@ public final class InjectConstructorFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import java.util.List;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class GenericClass_Factory<A extends Number & Comparable<A>,", " B extends List<? extends String>,", @@ -400,8 +421,12 @@ public final class InjectConstructorFactoryGeneratorTest { "import dagger.Lazy;", "import dagger.internal.DoubleCheck;", "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata(\"test.QualifierA\")", GeneratedLines.generatedAnnotations(), "public final class GenericClass_Factory<A, B>", " implements Factory<GenericClass<A, B>> {", @@ -547,14 +572,16 @@ public final class InjectConstructorFactoryGeneratorTest { "}"); Compilation compilation = daggerCompiler().compile(file); assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); assertThat(compilation) - .hadErrorContaining("Types may only contain one injected constructor") - .inFile(file) - .onLine(6); - assertThat(compilation) - .hadErrorContaining("Types may only contain one injected constructor") + .hadErrorContaining( + "Type test.TooManyInjectConstructors may only contain one injected constructor. " + + "Found: [" + + "TooManyInjectConstructors(), " + + "TooManyInjectConstructors(java.lang.String)" + + "]") .inFile(file) - .onLine(8); + .onLine(5); } @Test public void multipleQualifiersOnInjectConstructorParameter() { @@ -1067,8 +1094,12 @@ public final class InjectConstructorFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class InjectConstructor_Factory ", " implements Factory<InjectConstructor> {", @@ -1114,8 +1145,12 @@ public final class InjectConstructorFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class AllInjections_Factory implements Factory<AllInjections> {", " private final Provider<String> sProvider;", @@ -1174,9 +1209,13 @@ public final class InjectConstructorFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import java.util.List;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class InjectConstructor_Factory ", " implements Factory<InjectConstructor> {", @@ -1227,8 +1266,12 @@ public final class InjectConstructorFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class InjectConstructor_Factory ", " implements Factory<InjectConstructor> {", @@ -1283,9 +1326,13 @@ public final class InjectConstructorFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;", "import other.pkg.Outer;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class InjectConstructor_Factory ", " implements Factory<InjectConstructor> {", @@ -1342,8 +1389,12 @@ public final class InjectConstructorFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class InjectConstructor_Factory ", " implements Factory<InjectConstructor> {", @@ -1395,8 +1446,13 @@ public final class InjectConstructorFactoryGeneratorTest { "test.SimpleType_Factory", "package test;", "", - GeneratedLines.generatedImports("import dagger.internal.Factory;"), + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class SimpleType_Factory implements Factory<SimpleType> {", " @Override public SimpleType get() {", @@ -1442,8 +1498,13 @@ public final class InjectConstructorFactoryGeneratorTest { "test.OuterType_A_Factory", "package test;", "", - GeneratedLines.generatedImports("import dagger.internal.Factory;"), + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class OuterType_A_Factory implements Factory<OuterType.A> {", " @Override public OuterType.A get() {", @@ -1467,4 +1528,452 @@ public final class InjectConstructorFactoryGeneratorTest { .compilesWithoutError() .and().generatesSources(aFactory); } + + @Test + public void testScopedMetadata() { + JavaFileObject scopedBinding = + JavaFileObjects.forSourceLines( + "test.ScopedBinding", + "package test;", + "", + "import javax.inject.Inject;", + "import javax.inject.Singleton;", + "", + "@Singleton", + "class ScopedBinding {", + " @Inject", + " ScopedBinding() {}", + "}"); + Compilation compilation = daggerCompiler().compile(scopedBinding); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.ScopedBinding_Factory") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.ScopedBinding_Factory", + "package test;", + "", + "@ScopeMetadata(\"javax.inject.Singleton\")", + "@QualifierMetadata", + GeneratedLines.generatedAnnotations(), + "public final class ScopedBinding_Factory implements Factory<ScopedBinding> {}")); + } + + @Test + public void testScopedMetadataWithCustomScope() { + JavaFileObject customScope = + JavaFileObjects.forSourceLines( + "test.CustomScope", + "package test;", + "", + "import javax.inject.Scope;", + "", + "@Scope", + "@interface CustomScope {", + " String value();", + "}"); + + JavaFileObject customAnnotation = + JavaFileObjects.forSourceLines( + "test.CustomAnnotation", + "package test;", + "", + "@interface CustomAnnotation {", + " String value();", + "}"); + + JavaFileObject scopedBinding = + JavaFileObjects.forSourceLines( + "test.ScopedBinding", + "package test;", + "", + "import javax.inject.Inject;", + "import javax.inject.Singleton;", + "", + "@CustomAnnotation(\"someValue\")", + "@CustomScope(\"someOtherValue\")", + "class ScopedBinding {", + " @Inject", + " ScopedBinding() {}", + "}"); + Compilation compilation = + daggerCompiler().compile(scopedBinding, customScope, customAnnotation); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.ScopedBinding_Factory") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.ScopedBinding_Factory", + "package test;", + "", + "@ScopeMetadata(\"test.CustomScope\")", + "@QualifierMetadata", + GeneratedLines.generatedAnnotations(), + "public final class ScopedBinding_Factory implements Factory<ScopedBinding> {}")); + } + + @Test + public void testQualifierMetadata() { + JavaFileObject someBinding = + JavaFileObjects.forSourceLines( + "test.SomeBinding", + "package test;", + "", + "import javax.inject.Inject;", + "import javax.inject.Singleton;", + "", + "@NonQualifier", + "@MisplacedQualifier", + "class SomeBinding {", + " @NonQualifier @FieldQualifier @Inject String injectField;", + " @NonQualifier @MisplacedQualifier String nonDaggerField;", + "", + " @NonQualifier", + " @Inject", + " SomeBinding(@NonQualifier @ConstructorParameterQualifier Double d) {}", + "", + " @NonQualifier", + " @MisplacedQualifier", + " SomeBinding(@NonQualifier @MisplacedQualifier Double d, int i) {}", + "", + " @NonQualifier", + " @MisplacedQualifier", + " @Inject", + " void injectMethod(@NonQualifier @MethodParameterQualifier Float f) {}", + "", + " @NonQualifier", + " @MisplacedQualifier", + " void nonDaggerMethod(@NonQualifier @MisplacedQualifier Float f) {}", + "}"); + JavaFileObject fieldQualifier = + JavaFileObjects.forSourceLines( + "test.FieldQualifier", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface FieldQualifier {}"); + JavaFileObject constructorParameterQualifier = + JavaFileObjects.forSourceLines( + "test.ConstructorParameterQualifier", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface ConstructorParameterQualifier {}"); + JavaFileObject methodParameterQualifier = + JavaFileObjects.forSourceLines( + "test.MethodParameterQualifier", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface MethodParameterQualifier {}"); + JavaFileObject misplacedQualifier = + JavaFileObjects.forSourceLines( + "test.MisplacedQualifier", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface MisplacedQualifier {}"); + JavaFileObject nonQualifier = + JavaFileObjects.forSourceLines( + "test.NonQualifier", + "package test;", + "", + "@interface NonQualifier {}"); + Compilation compilation = + daggerCompiler().compile( + someBinding, + fieldQualifier, + constructorParameterQualifier, + methodParameterQualifier, + misplacedQualifier, + nonQualifier); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.SomeBinding_Factory") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.SomeBinding_Factory", + "package test;", + "", + // Verifies that the @QualifierMetadata for the generated Factory does not contain + // @MisplacedQualifier, @NonQualifier, @MethodParameterQualifier or @FieldQualifier. + "@ScopeMetadata", + "@QualifierMetadata(\"test.ConstructorParameterQualifier\")", + GeneratedLines.generatedAnnotations(), + "public final class SomeBinding_Factory implements Factory<SomeBinding> {}")); + assertThat(compilation) + .generatedSourceFile("test.SomeBinding_MembersInjector") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.SomeBinding_MembersInjector", + "package test;", + "", + // Verifies that the @QualifierMetadata for the generated MembersInjector does not + // contain @MisplacedQualifier, @NonQualifier, or @ConstructorParameterQualifier. + "@QualifierMetadata({", + " \"test.FieldQualifier\",", + " \"test.MethodParameterQualifier\"", + "})", + GeneratedLines.generatedAnnotations(), + "public final class SomeBinding_MembersInjector", + " implements MembersInjector<SomeBinding> {}")); + } + + @Test + public void testComplexQualifierMetadata() { + JavaFileObject someBinding = + JavaFileObjects.forSourceLines( + "test.SomeBinding", + "package test;", + "", + "import javax.inject.Inject;", + "import javax.inject.Inject;", + "", + "class SomeBinding {", + " @QualifierWithValue(1) @Inject String injectField;", + "", + " @Inject", + " SomeBinding(", + " @pkg1.SameNameQualifier String str1,", + " @pkg2.SameNameQualifier String str2) {}", + "", + " @Inject", + " void injectMethod(@test.Outer.NestedQualifier Float f) {}", + "}"); + JavaFileObject qualifierWithValue = + JavaFileObjects.forSourceLines( + "test.QualifierWithValue", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface QualifierWithValue {", + " int value();", + "}"); + JavaFileObject pkg1SameNameQualifier = + JavaFileObjects.forSourceLines( + "pkg1.SameNameQualifier", + "package pkg1;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "public @interface SameNameQualifier {}"); + JavaFileObject pkg2SameNameQualifier = + JavaFileObjects.forSourceLines( + "pkg2.SameNameQualifier", + "package pkg2;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "public @interface SameNameQualifier {}"); + JavaFileObject nestedQualifier = + JavaFileObjects.forSourceLines( + "test.Outer", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "interface Outer {", + " @Qualifier", + " @interface NestedQualifier {}", + "}"); + Compilation compilation = + daggerCompiler().compile( + someBinding, + qualifierWithValue, + pkg1SameNameQualifier, + pkg2SameNameQualifier, + nestedQualifier); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.SomeBinding_Factory") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.SomeBinding_Factory", + "package test;", + "", + "@ScopeMetadata", + "@QualifierMetadata({\"pkg1.SameNameQualifier\", \"pkg2.SameNameQualifier\"})", + GeneratedLines.generatedAnnotations(), + "public final class SomeBinding_Factory implements Factory<SomeBinding> {}")); + assertThat(compilation) + .generatedSourceFile("test.SomeBinding_MembersInjector") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.SomeBinding_MembersInjector", + "package test;", + "", + "@QualifierMetadata({", + " \"test.QualifierWithValue\",", + " \"test.Outer.NestedQualifier\"", + "})", + GeneratedLines.generatedAnnotations(), + "public final class SomeBinding_MembersInjector", + " implements MembersInjector<SomeBinding> {}")); + } + + @Test + public void testBaseClassQualifierMetadata() { + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import javax.inject.Inject;", + "import javax.inject.Singleton;", + "", + "class Foo extends FooBase {", + " @FooFieldQualifier @Inject String injectField;", + "", + " @Inject", + " Foo(@FooConstructorQualifier int i) { super(i); }", + "", + " @Inject", + " void injectMethod(@FooMethodQualifier float f) {}", + "}"); + JavaFileObject fooFieldQualifier = + JavaFileObjects.forSourceLines( + "test.FooFieldQualifier", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface FooFieldQualifier {}"); + JavaFileObject fooConstructorQualifier = + JavaFileObjects.forSourceLines( + "test.FooConstructorQualifier", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface FooConstructorQualifier {}"); + JavaFileObject fooMethodQualifier = + JavaFileObjects.forSourceLines( + "test.FooMethodQualifier", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface FooMethodQualifier {}"); + JavaFileObject fooBase = + JavaFileObjects.forSourceLines( + "test.FooBase", + "package test;", + "", + "import javax.inject.Inject;", + "import javax.inject.Singleton;", + "", + "class FooBase {", + " @FooBaseFieldQualifier @Inject String injectField;", + "", + " @Inject", + " FooBase(@FooBaseConstructorQualifier int i) {}", + "", + " @Inject", + " void injectMethod(@FooBaseMethodQualifier float f) {}", + "}"); + JavaFileObject fooBaseFieldQualifier = + JavaFileObjects.forSourceLines( + "test.FooBaseFieldQualifier", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface FooBaseFieldQualifier {}"); + JavaFileObject fooBaseConstructorQualifier = + JavaFileObjects.forSourceLines( + "test.FooBaseConstructorQualifier", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface FooBaseConstructorQualifier {}"); + JavaFileObject fooBaseMethodQualifier = + JavaFileObjects.forSourceLines( + "test.FooBaseMethodQualifier", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface FooBaseMethodQualifier {}"); + Compilation compilation = + daggerCompiler().compile( + foo, + fooBase, + fooFieldQualifier, + fooConstructorQualifier, + fooMethodQualifier, + fooBaseFieldQualifier, + fooBaseConstructorQualifier, + fooBaseMethodQualifier); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.Foo_Factory") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Foo_Factory", + "package test;", + "", + "@ScopeMetadata", + // Verifies that Foo_Factory only contains Foo's qualifiers and not FooBase's too. + "@QualifierMetadata(\"test.FooConstructorQualifier\")", + GeneratedLines.generatedAnnotations(), + "public final class Foo_Factory implements Factory<Foo> {}")); + assertThat(compilation) + .generatedSourceFile("test.Foo_MembersInjector") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Foo_MembersInjector", + "package test;", + "", + // Verifies that Foo_Factory only contains Foo's qualifiers and not FooBase's too. + "@QualifierMetadata({", + " \"test.FooFieldQualifier\",", + " \"test.FooMethodQualifier\"", + "})", + GeneratedLines.generatedAnnotations(), + "public final class Foo_MembersInjector implements MembersInjector<Foo> {}")); + assertThat(compilation) + .generatedSourceFile("test.FooBase_Factory") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Foo_Factory", + "package test;", + "", + "@ScopeMetadata", + "@QualifierMetadata(\"test.FooBaseConstructorQualifier\")", + GeneratedLines.generatedAnnotations(), + "public final class FooBase_Factory implements Factory<FooBase> {}")); + assertThat(compilation) + .generatedSourceFile("test.FooBase_MembersInjector") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.FooBase_MembersInjector", + "package test;", + "", + "@QualifierMetadata({", + " \"test.FooBaseFieldQualifier\",", + " \"test.FooBaseMethodQualifier\"", + "})", + GeneratedLines.generatedAnnotations(), + "public final class FooBase_MembersInjector", + " implements MembersInjector<FooBase> {}")); + } } diff --git a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/KotlinApplication.kt b/javatests/dagger/internal/codegen/InvalidInjectConstructor.java index 765fb3614..ac3e61f70 100644 --- a/java/dagger/hilt/android/example/gradle/simpleKotlin/app/src/main/java/dagger/hilt/android/example/gradle/simpleKotlin/KotlinApplication.kt +++ b/javatests/dagger/internal/codegen/InvalidInjectConstructor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2022 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. @@ -14,17 +14,20 @@ * limitations under the License. */ -package dagger.hilt.android.example.gradle.simpleKotlin +package dagger.internal.codegen; -import android.app.Application -import dagger.hilt.android.HiltAndroidApp -import javax.inject.Inject +import javax.inject.Inject; + + +/** A class that invalidly declares 2 inject constructors. */ +@SuppressWarnings("MoreThanOneInjectableConstructor") // Testing too many constructors +public final class InvalidInjectConstructor { + + @Inject String str; + + @Inject + InvalidInjectConstructor() {} -@HiltAndroidApp -class KotlinApplication : Application() { - // Shows that we can inject SingletonComponent bindings into an application. @Inject - @Model - @JvmField - var model: String? = null + InvalidInjectConstructor(String str) {} } diff --git a/javatests/dagger/internal/codegen/InvalidInjectConstructorTest.java b/javatests/dagger/internal/codegen/InvalidInjectConstructorTest.java new file mode 100644 index 000000000..b49593ab3 --- /dev/null +++ b/javatests/dagger/internal/codegen/InvalidInjectConstructorTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 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.testing.compile.CompilationSubject.assertThat; +import static dagger.internal.codegen.Compilers.daggerCompiler; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +// Tests an invalid inject constructor that avoids validation in its own library by using +// a dependency on jsr330 rather than Dagger gets validated when used in a component. +@RunWith(JUnit4.class) +public final class InvalidInjectConstructorTest { + + @Test + public void usedInvalidConstructorFails() { + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "import dagger.internal.codegen.InvalidInjectConstructor;", + "", + "@Component", + "interface TestComponent {", + " InvalidInjectConstructor invalidInjectConstructor();", + "}"); + Compilation compilation = daggerCompiler().compile(component); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(2); + assertThat(compilation) + .hadErrorContaining( + "Type dagger.internal.codegen.InvalidInjectConstructor may only contain one injected " + + "constructor. Found: [" + + "InvalidInjectConstructor(), " + + "InvalidInjectConstructor(java.lang.String)" + + "]"); + // TODO(b/215620949): Avoid reporting missing bindings on a type that has errors. + assertThat(compilation) + .hadErrorContaining( + "InvalidInjectConstructor cannot be provided without an @Inject constructor or an " + + "@Provides-annotated method."); + } + + @Test + public void unusedInvalidConstructorFails() { + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "import dagger.internal.codegen.InvalidInjectConstructor;", + "", + "@Component", + "interface TestComponent {", + // Here we're only using the members injection, but we're testing that we still validate + // the constructors + " void inject(InvalidInjectConstructor instance);", + "}"); + Compilation compilation = daggerCompiler().compile(component); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(2); + assertThat(compilation) + .hadErrorContaining( + "Type dagger.internal.codegen.InvalidInjectConstructor may only contain one injected " + + "constructor. Found: [" + + "InvalidInjectConstructor(), " + + "InvalidInjectConstructor(java.lang.String)" + + "]"); + // TODO(b/215620949): Avoid reporting missing bindings on a type that has errors. + assertThat(compilation) + .hadErrorContaining( + "InvalidInjectConstructor cannot be provided without an @Inject constructor or an " + + "@Provides-annotated method."); + } +} diff --git a/javatests/dagger/internal/codegen/KeyFactoryTest.java b/javatests/dagger/internal/codegen/KeyFactoryTest.java index fdf62cbbf..604bfe2fe 100644 --- a/javatests/dagger/internal/codegen/KeyFactoryTest.java +++ b/javatests/dagger/internal/codegen/KeyFactoryTest.java @@ -16,27 +16,31 @@ package dagger.internal.codegen; +import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XProcessingEnv; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.ListenableFuture; import com.google.testing.compile.CompilationRule; -import dagger.BindsInstance; +import com.squareup.javapoet.TypeName; import dagger.Component; import dagger.Module; import dagger.Provides; import dagger.internal.codegen.binding.KeyFactory; +import dagger.internal.codegen.javac.JavacPluginModule; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import dagger.model.Key; -import dagger.model.Key.MultibindingContributionIdentifier; import dagger.multibindings.ElementsIntoSet; import dagger.multibindings.IntoSet; import dagger.producers.ProducerModule; import dagger.producers.Produces; +import dagger.spi.model.DaggerAnnotation; +import dagger.spi.model.DaggerType; +import dagger.spi.model.Key; +import dagger.spi.model.Key.MultibindingContributionIdentifier; import java.lang.annotation.Retention; import java.util.Set; import javax.inject.Inject; @@ -61,12 +65,17 @@ import org.junit.runners.JUnit4; public class KeyFactoryTest { @Rule public CompilationRule compilationRule = new CompilationRule(); + @Inject XProcessingEnv processingEnv; @Inject DaggerElements elements; @Inject DaggerTypes types; @Inject KeyFactory keyFactory; @Before public void setUp() { - DaggerKeyFactoryTest_TestComponent.factory().create(compilationRule).inject(this); + DaggerKeyFactoryTest_TestComponent.builder() + .javacPluginModule( + new JavacPluginModule(compilationRule.getElements(), compilationRule.getTypes())) + .build() + .inject(this); } @Test public void forInjectConstructorWithResolvedType() { @@ -76,7 +85,7 @@ public class KeyFactoryTest { Iterables.getOnlyElement(ElementFilter.constructorsIn(typeElement.getEnclosedElements())); Key key = keyFactory.forInjectConstructorWithResolvedType(constructor.getEnclosingElement().asType()); - assertThat(key).isEqualTo(Key.builder(typeElement.asType()).build()); + assertThat(key).isEqualTo(Key.builder(fromJava(typeElement.asType())).build()); assertThat(key.toString()).isEqualTo("dagger.internal.codegen.KeyFactoryTest.InjectedClass"); } @@ -92,7 +101,7 @@ public class KeyFactoryTest { ExecutableElement providesMethod = Iterables.getOnlyElement(ElementFilter.methodsIn(moduleElement.getEnclosedElements())); Key key = keyFactory.forProvidesMethod(providesMethod, moduleElement); - assertThat(key).isEqualTo(Key.builder(stringType).build()); + assertThat(key).isEqualTo(Key.builder(fromJava(stringType)).build()); assertThat(key.toString()).isEqualTo("java.lang.String"); } @@ -112,10 +121,9 @@ public class KeyFactoryTest { ExecutableElement providesMethod = Iterables.getOnlyElement(ElementFilter.methodsIn(moduleElement.getEnclosedElements())); Key key = keyFactory.forProvidesMethod(providesMethod, moduleElement); - assertThat(MoreTypes.equivalence().wrap(key.qualifier().get().getAnnotationType())) - .isEqualTo(MoreTypes.equivalence().wrap(qualifierElement.asType())); - assertThat(MoreTypes.equivalence().wrap(key.type())) - .isEqualTo(MoreTypes.equivalence().wrap(stringType)); + assertThat(TypeName.get(key.qualifier().get().java().getAnnotationType())) + .isEqualTo(TypeName.get(qualifierElement.asType())); + assertThat(TypeName.get(key.type().java())).isEqualTo(TypeName.get(stringType)); assertThat(key.toString()) .isEqualTo( "@dagger.internal.codegen.KeyFactoryTest.TestQualifier({" @@ -141,7 +149,7 @@ public class KeyFactoryTest { Element injectionField = Iterables.getOnlyElement(ElementFilter.fieldsIn(injectableElement.getEnclosedElements())); AnnotationMirror qualifier = Iterables.getOnlyElement(injectionField.getAnnotationMirrors()); - Key injectionKey = Key.builder(type).qualifier(qualifier).build(); + Key injectionKey = Key.builder(fromJava(type)).qualifier(fromJava(qualifier)).build(); assertThat(provisionKey).isEqualTo(injectionKey); assertThat(injectionKey.toString()) @@ -203,7 +211,7 @@ public class KeyFactoryTest { Key key = keyFactory.forProvidesMethod(providesMethod, moduleElement); assertThat(key) .isEqualTo( - Key.builder(setOfStringsType) + Key.builder(fromJava(setOfStringsType)) .multibindingContributionIdentifier( new MultibindingContributionIdentifier(providesMethod, moduleElement)) .build()); @@ -270,7 +278,7 @@ public class KeyFactoryTest { for (ExecutableElement producesMethod : ElementFilter.methodsIn(moduleElement.getEnclosedElements())) { Key key = keyFactory.forProducesMethod(producesMethod, moduleElement); - assertThat(key).isEqualTo(Key.builder(stringType).build()); + assertThat(key).isEqualTo(Key.builder(fromJava(stringType)).build()); assertThat(key.toString()).isEqualTo("java.lang.String"); } } @@ -297,7 +305,7 @@ public class KeyFactoryTest { Key key = keyFactory.forProducesMethod(producesMethod, moduleElement); assertThat(key) .isEqualTo( - Key.builder(setOfStringsType) + Key.builder(fromJava(setOfStringsType)) .multibindingContributionIdentifier( new MultibindingContributionIdentifier(producesMethod, moduleElement)) .build()); @@ -310,6 +318,14 @@ public class KeyFactoryTest { } } + private DaggerAnnotation fromJava(AnnotationMirror annotation) { + return DaggerAnnotation.from(toXProcessing(annotation, processingEnv)); + } + + private DaggerType fromJava(TypeMirror typeMirror) { + return DaggerType.from(toXProcessing(typeMirror, processingEnv)); + } + @ProducerModule static final class SetProducesMethodsModule { @Produces @IntoSet String produceString() { @@ -331,26 +347,8 @@ public class KeyFactoryTest { } @Singleton - @Component(modules = {TestModule.class}) + @Component(modules = JavacPluginModule.class) interface TestComponent { void inject(KeyFactoryTest test); - - @Component.Factory - interface Factory { - TestComponent create(@BindsInstance CompilationRule compilationRule); - } - } - - @Module - static class TestModule { - @Provides - static DaggerElements elements(CompilationRule compilationRule) { - return new DaggerElements(compilationRule.getElements(), compilationRule.getTypes()); - } - - @Provides - static DaggerTypes types(CompilationRule compilationRule, DaggerElements elements) { - return new DaggerTypes(compilationRule.getTypes(), elements); - } } } diff --git a/javatests/dagger/internal/codegen/MapBindingComponentProcessorTest.java b/javatests/dagger/internal/codegen/MapBindingComponentProcessorTest.java index aa2a0ff6b..622ca8601 100644 --- a/javatests/dagger/internal/codegen/MapBindingComponentProcessorTest.java +++ b/javatests/dagger/internal/codegen/MapBindingComponentProcessorTest.java @@ -17,6 +17,8 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; +import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; import static dagger.internal.codegen.Compilers.daggerCompiler; @@ -93,22 +95,24 @@ public class MapBindingComponentProcessorTest { " LOGIN;", "}"); - JavaFileObject HandlerFile = JavaFileObjects.forSourceLines("test.Handler", - "package test;", - "", - "interface Handler {}"); - JavaFileObject LoginHandlerFile = JavaFileObjects.forSourceLines("test.LoginHandler", - "package test;", - "", - "class LoginHandler implements Handler {", - " public LoginHandler() {}", - "}"); - JavaFileObject AdminHandlerFile = JavaFileObjects.forSourceLines("test.AdminHandler", - "package test;", - "", - "class AdminHandler implements Handler {", - " public AdminHandler() {}", - "}"); + JavaFileObject handlerFile = + JavaFileObjects.forSourceLines("test.Handler", "package test;", "", "interface Handler {}"); + JavaFileObject loginHandlerFile = + JavaFileObjects.forSourceLines( + "test.LoginHandler", + "package test;", + "", + "class LoginHandler implements Handler {", + " public LoginHandler() {}", + "}"); + JavaFileObject adminHandlerFile = + JavaFileObjects.forSourceLines( + "test.AdminHandler", + "package test;", + "", + "class AdminHandler implements Handler {", + " public AdminHandler() {}", + "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", @@ -120,119 +124,7 @@ public class MapBindingComponentProcessorTest { "interface TestComponent {", " Provider<Map<PathEnum, Provider<Handler>>> dispatcher();", "}"); - JavaFileObject generatedComponent; - switch (compilerMode) { - case FAST_INIT_MODE: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerTestComponent", - "package test;", - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {", - " private final MapModuleOne mapModuleOne;", - " private final MapModuleTwo mapModuleTwo;", - " private volatile Provider<Handler> provideAdminHandlerProvider;", - " private volatile Provider<Handler> provideLoginHandlerProvider;", - " private volatile Provider<Map<PathEnum, Provider<Handler>>>", - " mapOfPathEnumAndProviderOfHandlerProvider;", - "", - " private Provider<Handler> provideAdminHandlerProvider() {", - " Object local = provideAdminHandlerProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(1);", - " provideAdminHandlerProvider = (Provider<Handler>) local;", - " }", - " return (Provider<Handler>) local;", - " }", - "", - " private Provider<Handler> provideLoginHandlerProvider() {", - " Object local = provideLoginHandlerProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(2);", - " provideLoginHandlerProvider = (Provider<Handler>) local;", - " }", - " return (Provider<Handler>) local;", - " }", - "", - " private Map<PathEnum, Provider<Handler>>", - " mapOfPathEnumAndProviderOfHandler() {", - " return ImmutableMap.<PathEnum, Provider<Handler>>of(", - " PathEnum.ADMIN, provideAdminHandlerProvider(),", - " PathEnum.LOGIN, provideLoginHandlerProvider());", - " }", - "", - " @Override", - " public Provider<Map<PathEnum, Provider<Handler>>> dispatcher() {", - " Object local = mapOfPathEnumAndProviderOfHandlerProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " mapOfPathEnumAndProviderOfHandlerProvider =", - " (Provider<Map<PathEnum, Provider<Handler>>>) local;", - " }", - " return (Provider<Map<PathEnum, Provider<Handler>>>) local;", - " }", - "", - " private final class SwitchingProvider<T> implements Provider<T> {", - " private final int id;", - "", - " SwitchingProvider(int id) {", - " this.id = id;", - " }", - "", - " @SuppressWarnings(\"unchecked\")", - " @Override", - " public T get() {", - " switch (id) {", - " case 0:", - " return (T) DaggerTestComponent.this", - " .mapOfPathEnumAndProviderOfHandler();", - " case 1:", - " return (T) MapModuleOne_ProvideAdminHandlerFactory", - " .provideAdminHandler(DaggerTestComponent.this.mapModuleOne);", - " case 2:", - " return (T) MapModuleTwo_ProvideLoginHandlerFactory", - " .provideLoginHandler(DaggerTestComponent.this.mapModuleTwo);", - " default: throw new AssertionError(id);", - " }", - " }", - " }", - "}"); - break; - default: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerTestComponent", - "package test;", - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {", - " private Provider<Handler> provideAdminHandlerProvider;", - " private Provider<Handler> provideLoginHandlerProvider;", - " private Provider<Map<PathEnum, Provider<Handler>>>", - " mapOfPathEnumAndProviderOfHandlerProvider;", - "", - " @SuppressWarnings(\"unchecked\")", - " private void initialize(", - " final MapModuleOne mapModuleOneParam,", - " final MapModuleTwo mapModuleTwoParam) {", - " this.provideAdminHandlerProvider =", - " MapModuleOne_ProvideAdminHandlerFactory.create(mapModuleOneParam);", - " this.provideLoginHandlerProvider =", - " MapModuleTwo_ProvideLoginHandlerFactory.create(mapModuleTwoParam);", - " this.mapOfPathEnumAndProviderOfHandlerProvider =", - " MapProviderFactory.<PathEnum, Handler>builder(2)", - " .put(PathEnum.ADMIN, provideAdminHandlerProvider)", - " .put(PathEnum.LOGIN, provideLoginHandlerProvider)", - " .build();", - " }", - "", - " @Override", - " public Provider<Map<PathEnum, Provider<Handler>>> dispatcher() {", - " return mapOfPathEnumAndProviderOfHandlerProvider;", - " }", - "}"); - } + Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile( @@ -240,14 +132,82 @@ public class MapBindingComponentProcessorTest { mapModuleTwoFile, enumKeyFile, pathEnumFile, - HandlerFile, - LoginHandlerFile, - AdminHandlerFile, + handlerFile, + loginHandlerFile, + adminHandlerFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") - .containsElementsIn(generatedComponent); + .containsElementsIn( + compilerMode + .javaFileBuilder("test.DaggerTestComponent") + .addLines( + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "final class DaggerTestComponent implements TestComponent {", + " private final DaggerTestComponent testComponent = this;", + " private Provider<Handler> provideAdminHandlerProvider;", + " private Provider<Handler> provideLoginHandlerProvider;", + " private Provider<Map<PathEnum, Provider<Handler>>>", + " mapOfPathEnumAndProviderOfHandlerProvider;") + .addLinesIn( + DEFAULT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize(", + " final MapModuleOne mapModuleOneParam,", + " final MapModuleTwo mapModuleTwoParam) {", + " this.provideAdminHandlerProvider =", + " MapModuleOne_ProvideAdminHandlerFactory.create(mapModuleOneParam);", + " this.provideLoginHandlerProvider =", + " MapModuleTwo_ProvideLoginHandlerFactory.create(mapModuleTwoParam);", + " this.mapOfPathEnumAndProviderOfHandlerProvider =", + " MapProviderFactory.<PathEnum, Handler>builder(2)", + " .put(PathEnum.ADMIN, provideAdminHandlerProvider)", + " .put(PathEnum.LOGIN, provideLoginHandlerProvider)", + " .build();", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize(final MapModuleOne mapModuleOneParam,", + " final MapModuleTwo mapModuleTwoParam) {", + " this.provideAdminHandlerProvider =", + " new SwitchingProvider<>(testComponent, 1);", + " this.provideLoginHandlerProvider =", + " new SwitchingProvider<>(testComponent, 2);", + " this.mapOfPathEnumAndProviderOfHandlerProvider =", + " new SwitchingProvider<>(testComponent, 0);", + " }") + .addLines( + " @Override", + " public Provider<Map<PathEnum, Provider<Handler>>> dispatcher() {", + " return mapOfPathEnumAndProviderOfHandlerProvider;", + " }") + .addLinesIn( + FAST_INIT_MODE, + "", + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0: return (T) ImmutableMap.<PathEnum, Provider<Handler>>of(", + " PathEnum.ADMIN,", + " testComponent.provideAdminHandlerProvider,", + " PathEnum.LOGIN,", + " testComponent.provideLoginHandlerProvider);", + " case 1: return (T) MapModuleOne_ProvideAdminHandlerFactory", + " .provideAdminHandler(testComponent.mapModuleOne);", + " case 2: return (T) MapModuleTwo_ProvideLoginHandlerFactory", + " .provideLoginHandler(testComponent.mapModuleTwo);", + " default: throw new AssertionError(id);", + " }", + " }", + " }", + "}") + .build()); } @Test @@ -500,22 +460,24 @@ public class MapBindingComponentProcessorTest { " return new LoginHandler();", " }", "}"); - JavaFileObject HandlerFile = JavaFileObjects.forSourceLines("test.Handler", - "package test;", - "", - "interface Handler {}"); - JavaFileObject LoginHandlerFile = JavaFileObjects.forSourceLines("test.LoginHandler", - "package test;", - "", - "class LoginHandler implements Handler {", - " public LoginHandler() {}", - "}"); - JavaFileObject AdminHandlerFile = JavaFileObjects.forSourceLines("test.AdminHandler", - "package test;", - "", - "class AdminHandler implements Handler {", - " public AdminHandler() {}", - "}"); + JavaFileObject handlerFile = + JavaFileObjects.forSourceLines("test.Handler", "package test;", "", "interface Handler {}"); + JavaFileObject loginHandlerFile = + JavaFileObjects.forSourceLines( + "test.LoginHandler", + "package test;", + "", + "class LoginHandler implements Handler {", + " public LoginHandler() {}", + "}"); + JavaFileObject adminHandlerFile = + JavaFileObjects.forSourceLines( + "test.AdminHandler", + "package test;", + "", + "class AdminHandler implements Handler {", + " public AdminHandler() {}", + "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", @@ -527,132 +489,88 @@ public class MapBindingComponentProcessorTest { "interface TestComponent {", " Provider<Map<String, Provider<Handler>>> dispatcher();", "}"); - JavaFileObject generatedComponent; - switch (compilerMode) { - case FAST_INIT_MODE: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerTestComponent", - "package test;", - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {", - " private final MapModuleOne mapModuleOne;", - " private final MapModuleTwo mapModuleTwo;", - " private volatile Provider<Handler> provideAdminHandlerProvider;", - " private volatile Provider<Handler> provideLoginHandlerProvider;", - " private volatile Provider<Map<String, Provider<Handler>>>", - " mapOfStringAndProviderOfHandlerProvider;", - "", - " private Provider<Handler> provideAdminHandlerProvider() {", - " Object local = provideAdminHandlerProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(1);", - " provideAdminHandlerProvider = (Provider<Handler>) local;", - " }", - " return (Provider<Handler>) local;", - " }", - "", - " private Provider<Handler> provideLoginHandlerProvider() {", - " Object local = provideLoginHandlerProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(2);", - " provideLoginHandlerProvider = (Provider<Handler>) local;", - " }", - " return (Provider<Handler>) local;", - " }", - "", - " private Map<String, Provider<Handler>>", - " mapOfStringAndProviderOfHandler() {", - " return ImmutableMap.<String, Provider<Handler>>of(", - " \"Admin\", provideAdminHandlerProvider(),", - " \"Login\", provideLoginHandlerProvider());", - " }", - "", - " @Override", - " public Provider<Map<String, Provider<Handler>>> dispatcher() {", - " Object local = mapOfStringAndProviderOfHandlerProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " mapOfStringAndProviderOfHandlerProvider =", - " (Provider<Map<String, Provider<Handler>>>) local;", - " }", - " return (Provider<Map<String, Provider<Handler>>>) local;", - " }", - "", - " private final class SwitchingProvider<T> implements Provider<T> {", - " private final int id;", - "", - " SwitchingProvider(int id) {", - " this.id = id;", - " }", - "", - " @SuppressWarnings(\"unchecked\")", - " @Override", - " public T get() {", - " switch (id) {", - " case 0:", - " return (T) DaggerTestComponent.this", - " .mapOfStringAndProviderOfHandler();", - " case 1:", - " return (T) MapModuleOne_ProvideAdminHandlerFactory", - " .provideAdminHandler(DaggerTestComponent.this.mapModuleOne);", - " case 2:", - " return (T) MapModuleTwo_ProvideLoginHandlerFactory", - " .provideLoginHandler(DaggerTestComponent.this.mapModuleTwo);", - " default: throw new AssertionError(id);", - " }", - " }", - " }", - "}"); - break; - default: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerTestComponent", - "package test;", - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {", - " private Provider<Handler> provideAdminHandlerProvider;", - " private Provider<Handler> provideLoginHandlerProvider;", - " private Provider<Map<String, Provider<Handler>>>", - " mapOfStringAndProviderOfHandlerProvider;", - "", - " @SuppressWarnings(\"unchecked\")", - " private void initialize(", - " final MapModuleOne mapModuleOneParam,", - " final MapModuleTwo mapModuleTwoParam) {", - " this.provideAdminHandlerProvider =", - " MapModuleOne_ProvideAdminHandlerFactory.create(mapModuleOneParam);", - " this.provideLoginHandlerProvider =", - " MapModuleTwo_ProvideLoginHandlerFactory.create(mapModuleTwoParam);", - " this.mapOfStringAndProviderOfHandlerProvider =", - " MapProviderFactory.<String, Handler>builder(2)", - " .put(\"Admin\", provideAdminHandlerProvider)", - " .put(\"Login\", provideLoginHandlerProvider)", - " .build();", - " }", - "", - " @Override", - " public Provider<Map<String, Provider<Handler>>> dispatcher() {", - " return mapOfStringAndProviderOfHandlerProvider;", - " }", - "}"); - } + Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile( mapModuleOneFile, mapModuleTwoFile, - HandlerFile, - LoginHandlerFile, - AdminHandlerFile, + handlerFile, + loginHandlerFile, + adminHandlerFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") - .containsElementsIn(generatedComponent); + .containsElementsIn( + compilerMode + .javaFileBuilder("test.DaggerTestComponent") + .addLines( + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "final class DaggerTestComponent implements TestComponent {", + " private final DaggerTestComponent testComponent = this;", + " private Provider<Handler> provideAdminHandlerProvider;", + " private Provider<Handler> provideLoginHandlerProvider;", + " private Provider<Map<String, Provider<Handler>>>", + " mapOfStringAndProviderOfHandlerProvider;") + .addLinesIn( + DEFAULT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize(", + " final MapModuleOne mapModuleOneParam,", + " final MapModuleTwo mapModuleTwoParam) {", + " this.provideAdminHandlerProvider =", + " MapModuleOne_ProvideAdminHandlerFactory.create(mapModuleOneParam);", + " this.provideLoginHandlerProvider =", + " MapModuleTwo_ProvideLoginHandlerFactory.create(mapModuleTwoParam);", + " this.mapOfStringAndProviderOfHandlerProvider =", + " MapProviderFactory.<String, Handler>builder(2)", + " .put(\"Admin\", provideAdminHandlerProvider)", + " .put(\"Login\", provideLoginHandlerProvider)", + " .build();", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize(", + " final MapModuleOne mapModuleOneParam,", + " final MapModuleTwo mapModuleTwoParam) {", + " this.provideAdminHandlerProvider =", + " new SwitchingProvider<>(testComponent, 1);", + " this.provideLoginHandlerProvider =", + " new SwitchingProvider<>(testComponent, 2);", + " this.mapOfStringAndProviderOfHandlerProvider =", + " new SwitchingProvider<>(testComponent, 0);", + " }") + .addLines( + " @Override", + " public Provider<Map<String, Provider<Handler>>> dispatcher() {", + " return mapOfStringAndProviderOfHandlerProvider;", + " }") + .addLinesIn( + FAST_INIT_MODE, + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0: return (T) ImmutableMap.<String, Provider<Handler>>of(", + " \"Admin\",", + " testComponent.provideAdminHandlerProvider,", + " \"Login\",", + " testComponent.provideLoginHandlerProvider);", + " case 1: return (T) MapModuleOne_ProvideAdminHandlerFactory", + " .provideAdminHandler(testComponent.mapModuleOne);", + " case 2: return (T) MapModuleTwo_ProvideLoginHandlerFactory", + " .provideLoginHandler(testComponent.mapModuleTwo);", + " default: throw new AssertionError(id);", + " }", + " }", + " }", + "}") + .build()); } @Test @@ -700,22 +618,24 @@ public class MapBindingComponentProcessorTest { "public @interface WrappedClassKey {", " Class<?> value();", "}"); - JavaFileObject HandlerFile = JavaFileObjects.forSourceLines("test.Handler", - "package test;", - "", - "interface Handler {}"); - JavaFileObject LoginHandlerFile = JavaFileObjects.forSourceLines("test.LoginHandler", - "package test;", - "", - "class LoginHandler implements Handler {", - " public LoginHandler() {}", - "}"); - JavaFileObject AdminHandlerFile = JavaFileObjects.forSourceLines("test.AdminHandler", - "package test;", - "", - "class AdminHandler implements Handler {", - " public AdminHandler() {}", - "}"); + JavaFileObject handlerFile = + JavaFileObjects.forSourceLines("test.Handler", "package test;", "", "interface Handler {}"); + JavaFileObject loginHandlerFile = + JavaFileObjects.forSourceLines( + "test.LoginHandler", + "package test;", + "", + "class LoginHandler implements Handler {", + " public LoginHandler() {}", + "}"); + JavaFileObject adminHandlerFile = + JavaFileObjects.forSourceLines( + "test.AdminHandler", + "package test;", + "", + "class AdminHandler implements Handler {", + " public AdminHandler() {}", + "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", @@ -727,144 +647,91 @@ public class MapBindingComponentProcessorTest { "interface TestComponent {", " Provider<Map<WrappedClassKey, Provider<Handler>>> dispatcher();", "}"); - JavaFileObject generatedComponent; - switch (compilerMode) { - case FAST_INIT_MODE: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerTestComponent", - "package test;", - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {", - " private final MapModuleOne mapModuleOne;", - " private final MapModuleTwo mapModuleTwo;", - " private volatile Provider<Handler> provideAdminHandlerProvider;", - " private volatile Provider<Handler> provideLoginHandlerProvider;", - " private volatile Provider<Map<WrappedClassKey, Provider<Handler>>>", - " mapOfWrappedClassKeyAndProviderOfHandlerProvider;", - "", - " private DaggerTestComponent(", - " MapModuleOne mapModuleOneParam,", - " MapModuleTwo mapModuleTwoParam) {", - " this.mapModuleOne = mapModuleOneParam;", - " this.mapModuleTwo = mapModuleTwoParam;", - " }", - "", - " private Provider<Handler> provideAdminHandlerProvider() {", - " Object local = provideAdminHandlerProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(1);", - " provideAdminHandlerProvider = (Provider<Handler>) local;", - " }", - " return (Provider<Handler>) local;", - " }", - "", - " private Provider<Handler> provideLoginHandlerProvider() {", - " Object local = provideLoginHandlerProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(2);", - " provideLoginHandlerProvider = (Provider<Handler>) local;", - " }", - " return (Provider<Handler>) local;", - " }", - "", - " private Map<WrappedClassKey, Provider<Handler>>", - " mapOfWrappedClassKeyAndProviderOfHandler() {", - " return ImmutableMap.<WrappedClassKey, Provider<Handler>>of(", - " WrappedClassKeyCreator.createWrappedClassKey(Integer.class),", - " provideAdminHandlerProvider(),", - " WrappedClassKeyCreator.createWrappedClassKey(Long.class),", - " provideLoginHandlerProvider());", - " }", - "", - " @Override", - " public Provider<Map<WrappedClassKey, Provider<Handler>>> dispatcher() {", - " Object local = mapOfWrappedClassKeyAndProviderOfHandlerProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " mapOfWrappedClassKeyAndProviderOfHandlerProvider =", - " (Provider<Map<WrappedClassKey, Provider<Handler>>>) local;", - " }", - " return (Provider<Map<WrappedClassKey, Provider<Handler>>>) local;", - " }", - "", - " private final class SwitchingProvider<T> implements Provider<T> {", - " private final int id;", - "", - " SwitchingProvider(int id) {", - " this.id = id;", - " }", - "", - " @SuppressWarnings(\"unchecked\")", - " @Override", - " public T get() {", - " switch (id) {", - " case 0:", - " return (T) DaggerTestComponent.this", - " .mapOfWrappedClassKeyAndProviderOfHandler();", - " case 1:", - " return (T) MapModuleOne_ProvideAdminHandlerFactory", - " .provideAdminHandler(DaggerTestComponent.this.mapModuleOne);", - " case 2:", - " return (T) MapModuleTwo_ProvideLoginHandlerFactory", - " .provideLoginHandler(DaggerTestComponent.this.mapModuleTwo);", - " default: throw new AssertionError(id);", - " }", - " }", - " }", - "}"); - break; - default: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerTestComponent", - "package test;", - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {", - " private Provider<Handler> provideAdminHandlerProvider;", - " private Provider<Handler> provideLoginHandlerProvider;", - " private Provider<Map<WrappedClassKey, Provider<Handler>>>", - " mapOfWrappedClassKeyAndProviderOfHandlerProvider;", - "", - " @SuppressWarnings(\"unchecked\")", - " private void initialize(", - " final MapModuleOne mapModuleOneParam,", - " final MapModuleTwo mapModuleTwoParam) {", - " this.provideAdminHandlerProvider =", - " MapModuleOne_ProvideAdminHandlerFactory.create(mapModuleOneParam);", - " this.provideLoginHandlerProvider =", - " MapModuleTwo_ProvideLoginHandlerFactory.create(mapModuleTwoParam);", - " this.mapOfWrappedClassKeyAndProviderOfHandlerProvider =", - " MapProviderFactory.<WrappedClassKey, Handler>builder(2)", - " .put(WrappedClassKeyCreator.createWrappedClassKey(Integer.class),", - " provideAdminHandlerProvider)", - " .put(WrappedClassKeyCreator.createWrappedClassKey(Long.class),", - " provideLoginHandlerProvider)", - " .build();", - " }", - "", - " @Override", - " public Provider<Map<WrappedClassKey, Provider<Handler>>> dispatcher() {", - " return mapOfWrappedClassKeyAndProviderOfHandlerProvider;", - " }", - "}"); - } + Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile( mapModuleOneFile, mapModuleTwoFile, wrappedClassKeyFile, - HandlerFile, - LoginHandlerFile, - AdminHandlerFile, + handlerFile, + loginHandlerFile, + adminHandlerFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") - .containsElementsIn(generatedComponent); + .containsElementsIn( + compilerMode + .javaFileBuilder("test.DaggerTestComponent") + .addLines( + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "final class DaggerTestComponent implements TestComponent {", + " private final DaggerTestComponent testComponent = this;", + " private Provider<Handler> provideAdminHandlerProvider;", + " private Provider<Handler> provideLoginHandlerProvider;", + " private Provider<Map<WrappedClassKey, Provider<Handler>>>", + " mapOfWrappedClassKeyAndProviderOfHandlerProvider;") + .addLinesIn( + DEFAULT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize(", + " final MapModuleOne mapModuleOneParam,", + " final MapModuleTwo mapModuleTwoParam) {", + " this.provideAdminHandlerProvider =", + " MapModuleOne_ProvideAdminHandlerFactory.create(mapModuleOneParam);", + " this.provideLoginHandlerProvider =", + " MapModuleTwo_ProvideLoginHandlerFactory.create(mapModuleTwoParam);", + " this.mapOfWrappedClassKeyAndProviderOfHandlerProvider =", + " MapProviderFactory.<WrappedClassKey, Handler>builder(2)", + " .put(WrappedClassKeyCreator.createWrappedClassKey(Integer.class),", + " provideAdminHandlerProvider)", + " .put(WrappedClassKeyCreator.createWrappedClassKey(Long.class),", + " provideLoginHandlerProvider)", + " .build();", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize(final MapModuleOne mapModuleOneParam,", + " final MapModuleTwo mapModuleTwoParam) {", + " this.provideAdminHandlerProvider =", + " new SwitchingProvider<>(testComponent, 1);", + " this.provideLoginHandlerProvider =", + " new SwitchingProvider<>(testComponent, 2);", + " this.mapOfWrappedClassKeyAndProviderOfHandlerProvider =", + " new SwitchingProvider<>(testComponent, 0);", + " }") + .addLines( + " @Override", + " public Provider<Map<WrappedClassKey, Provider<Handler>>> dispatcher() {", + " return mapOfWrappedClassKeyAndProviderOfHandlerProvider;", + " }") + .addLinesIn( + FAST_INIT_MODE, + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0:", + " return (T) ImmutableMap.<WrappedClassKey, Provider<Handler>>of(", + " WrappedClassKeyCreator.createWrappedClassKey(Integer.class),", + " testComponent.provideAdminHandlerProvider,", + " WrappedClassKeyCreator.createWrappedClassKey(Long.class),", + " testComponent.provideLoginHandlerProvider);", + " case 1: return (T) MapModuleOne_ProvideAdminHandlerFactory", + " .provideAdminHandler(testComponent.mapModuleOne);", + " case 2: return (T) MapModuleTwo_ProvideLoginHandlerFactory", + " .provideLoginHandler(testComponent.mapModuleTwo);", + " default: throw new AssertionError(id);", + " }", + " }", + " }", + "}") + .build()); } @Test @@ -913,22 +780,24 @@ public class MapBindingComponentProcessorTest { " ADMIN,", " LOGIN;", "}"); - JavaFileObject HandlerFile = JavaFileObjects.forSourceLines("test.Handler", - "package test;", - "", - "interface Handler {}"); - JavaFileObject LoginHandlerFile = JavaFileObjects.forSourceLines("test.LoginHandler", - "package test;", - "", - "class LoginHandler implements Handler {", - " public LoginHandler() {}", - "}"); - JavaFileObject AdminHandlerFile = JavaFileObjects.forSourceLines("test.AdminHandler", - "package test;", - "", - "class AdminHandler implements Handler {", - " public AdminHandler() {}", - "}"); + JavaFileObject handlerFile = + JavaFileObjects.forSourceLines("test.Handler", "package test;", "", "interface Handler {}"); + JavaFileObject loginHandlerFile = + JavaFileObjects.forSourceLines( + "test.LoginHandler", + "package test;", + "", + "class LoginHandler implements Handler {", + " public LoginHandler() {}", + "}"); + JavaFileObject adminHandlerFile = + JavaFileObjects.forSourceLines( + "test.AdminHandler", + "package test;", + "", + "class AdminHandler implements Handler {", + " public AdminHandler() {}", + "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", @@ -940,92 +809,7 @@ public class MapBindingComponentProcessorTest { "interface TestComponent {", " Provider<Map<PathEnum, Handler>> dispatcher();", "}"); - JavaFileObject generatedComponent; - switch (compilerMode) { - case FAST_INIT_MODE: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerTestComponent", - "package test;", - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {", - " private final MapModuleOne mapModuleOne;", - " private final MapModuleTwo mapModuleTwo;", - " private volatile Provider<Map<PathEnum, Handler>>", - " mapOfPathEnumAndHandlerProvider;", - "", - " private Map<PathEnum, Handler> mapOfPathEnumAndHandler() {", - " return ImmutableMap.<PathEnum, Handler>of(", - " PathEnum.ADMIN,", - " MapModuleOne_ProvideAdminHandlerFactory.provideAdminHandler(", - " mapModuleOne),", - " PathEnum.LOGIN,", - " MapModuleTwo_ProvideLoginHandlerFactory.provideLoginHandler(", - " mapModuleTwo));", - " }", - "", - " @Override", - " public Provider<Map<PathEnum, Handler>> dispatcher() {", - " Object local = mapOfPathEnumAndHandlerProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " mapOfPathEnumAndHandlerProvider = (Provider<Map<PathEnum, Handler>>) local;", - " }", - " return (Provider<Map<PathEnum, Handler>>) local;", - " }", - "", - " private final class SwitchingProvider<T> implements Provider<T> {", - " private final int id;", - "", - " SwitchingProvider(int id) {", - " this.id = id;", - " }", - "", - " @SuppressWarnings(\"unchecked\")", - " @Override", - " public T get() {", - " switch (id) {", - " case 0: return (T) DaggerTestComponent.this.mapOfPathEnumAndHandler();", - " default: throw new AssertionError(id);", - " }", - " }", - " }", - "}"); - break; - default: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerTestComponent", - "package test;", - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {", - " private Provider<Handler> provideAdminHandlerProvider;", - " private Provider<Handler> provideLoginHandlerProvider;", - " private Provider<Map<PathEnum, Handler>> mapOfPathEnumAndHandlerProvider;", - "", - " @SuppressWarnings(\"unchecked\")", - " private void initialize(", - " final MapModuleOne mapModuleOneParam,", - " final MapModuleTwo mapModuleTwoParam) {", - " this.provideAdminHandlerProvider =", - " MapModuleOne_ProvideAdminHandlerFactory.create(mapModuleOneParam);", - " this.provideLoginHandlerProvider =", - " MapModuleTwo_ProvideLoginHandlerFactory.create(mapModuleTwoParam);", - " this.mapOfPathEnumAndHandlerProvider =", - " MapFactory.<PathEnum, Handler>builder(2)", - " .put(PathEnum.ADMIN, provideAdminHandlerProvider)", - " .put(PathEnum.LOGIN, provideLoginHandlerProvider)", - " .build();", - " }", - "", - " @Override", - " public Provider<Map<PathEnum, Handler>> dispatcher() {", - " return mapOfPathEnumAndHandlerProvider;", - " }", - "}"); - } + Compilation compilation = compilerWithOptions(compilerMode.javacopts()) .compile( @@ -1033,14 +817,76 @@ public class MapBindingComponentProcessorTest { mapModuleTwoFile, enumKeyFile, pathEnumFile, - HandlerFile, - LoginHandlerFile, - AdminHandlerFile, + handlerFile, + loginHandlerFile, + adminHandlerFile, componentFile); assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("test.DaggerTestComponent") - .containsElementsIn(generatedComponent); + .containsElementsIn( + compilerMode + .javaFileBuilder("test.DaggerTestComponent") + .addLines( + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "final class DaggerTestComponent implements TestComponent {") + .addLinesIn( + DEFAULT_MODE, + " private Provider<Handler> provideAdminHandlerProvider;", + " private Provider<Handler> provideLoginHandlerProvider;", + " private Provider<Map<PathEnum, Handler>> mapOfPathEnumAndHandlerProvider;", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize(", + " final MapModuleOne mapModuleOneParam,", + " final MapModuleTwo mapModuleTwoParam) {", + " this.provideAdminHandlerProvider =", + " MapModuleOne_ProvideAdminHandlerFactory.create(mapModuleOneParam);", + " this.provideLoginHandlerProvider =", + " MapModuleTwo_ProvideLoginHandlerFactory.create(mapModuleTwoParam);", + " this.mapOfPathEnumAndHandlerProvider =", + " MapFactory.<PathEnum, Handler>builder(2)", + " .put(PathEnum.ADMIN, provideAdminHandlerProvider)", + " .put(PathEnum.LOGIN, provideLoginHandlerProvider)", + " .build();", + " }") + .addLinesIn( + FAST_INIT_MODE, + " private Provider<Map<PathEnum, Handler>> mapOfPathEnumAndHandlerProvider;", + "", + " @SuppressWarnings(\"unchecked\")", + " private void initialize(final MapModuleOne mapModuleOneParam,", + " final MapModuleTwo mapModuleTwoParam) {", + " this.mapOfPathEnumAndHandlerProvider =", + " new SwitchingProvider<>(testComponent, 0);", + " }") + .addLines( + " @Override", + " public Provider<Map<PathEnum, Handler>> dispatcher() {", + " return mapOfPathEnumAndHandlerProvider;", + " }") + .addLinesIn( + FAST_INIT_MODE, + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0: return (T) ImmutableMap.<PathEnum, Handler>of(", + " PathEnum.ADMIN,", + " MapModuleOne_ProvideAdminHandlerFactory.provideAdminHandler(", + " testComponent.mapModuleOne),", + " PathEnum.LOGIN,", + " MapModuleTwo_ProvideLoginHandlerFactory.provideLoginHandler(", + " testComponent.mapModuleTwo));", + " default: throw new AssertionError(id);", + " }", + " }", + " }", + "}") + .build()); } @Test diff --git a/javatests/dagger/internal/codegen/MapBindingExpressionTest.java b/javatests/dagger/internal/codegen/MapRequestRepresentationTest.java index 9acc20d5a..1f59bd8de 100644 --- a/javatests/dagger/internal/codegen/MapBindingExpressionTest.java +++ b/javatests/dagger/internal/codegen/MapRequestRepresentationTest.java @@ -33,7 +33,7 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) -public class MapBindingExpressionTest { +public class MapRequestRepresentationTest { @Parameters(name = "{0}") public static Collection<Object[]> parameters() { return CompilerMode.TEST_PARAMETERS; @@ -41,7 +41,7 @@ public class MapBindingExpressionTest { private final CompilerMode compilerMode; - public MapBindingExpressionTest(CompilerMode compilerMode) { + public MapRequestRepresentationTest(CompilerMode compilerMode) { this.compilerMode = compilerMode; } @@ -89,51 +89,21 @@ public class MapBindingExpressionTest { .addLines( "package test;", "", - "import dagger.internal.MapBuilder;", - "", GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, - " private volatile Provider<Integer> provideIntProvider;", - " private volatile Provider<Long> provideLong0Provider;", - " private volatile Provider<Long> provideLong1Provider;", - " private volatile Provider<Long> provideLong2Provider;", - "", - " private Provider<Integer> provideIntProvider() {", - " Object local = provideIntProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " provideIntProvider = (Provider<Integer>) local;", - " }", - " return (Provider<Integer>) local;", - " }", - "", - " private Provider<Long> provideLong0Provider() {", - " Object local = provideLong0Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(1);", - " provideLong0Provider = (Provider<Long>) local;", - " }", - " return (Provider<Long>) local;", - " }", - "", - " private Provider<Long> provideLong1Provider() {", - " Object local = provideLong1Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(2);", - " provideLong1Provider = (Provider<Long>) local;", - " }", - " return (Provider<Long>) local;", - " }", + " private Provider<Integer> provideIntProvider;", + " private Provider<Long> provideLong0Provider;", + " private Provider<Long> provideLong1Provider;", + " private Provider<Long> provideLong2Provider;", "", - " private Provider<Long> provideLong2Provider() {", - " Object local = provideLong2Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(3);", - " provideLong2Provider = (Provider<Long>) local;", - " }", - " return (Provider<Long>) local;", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.provideIntProvider = new SwitchingProvider<>(testComponent, 0);", + " this.provideLong0Provider = new SwitchingProvider<>(testComponent, 1);", + " this.provideLong1Provider = new SwitchingProvider<>(testComponent, 2);", + " this.provideLong2Provider = new SwitchingProvider<>(testComponent, 3);", " }") .addLines( " @Override", @@ -145,22 +115,31 @@ public class MapBindingExpressionTest { " public Map<String, Provider<String>> providerStrings() {", " return Collections.<String, Provider<String>>emptyMap();", " }", - "", + "") + .addLinesIn( + DEFAULT_MODE, " @Override", " public Map<Integer, Integer> ints() {", - " return Collections.<Integer, Integer>singletonMap(0, MapModule.provideInt());", - " }", - "", + " return Collections.<Integer, Integer>", + " singletonMap(0, MapModule.provideInt());", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @Override", + " public Map<Integer, Integer> ints() {", + " return Collections.<Integer, Integer>singletonMap(0," + + " provideIntProvider.get());", + " }") + .addLines( " @Override", " public Map<Integer, Provider<Integer>> providerInts() {", " return Collections.<Integer, Provider<Integer>>singletonMap(") .addLinesIn( DEFAULT_MODE, // " 0, MapModule_ProvideIntFactory.create());") + .addLinesIn(FAST_INIT_MODE, " 0, provideIntProvider;") .addLinesIn( - FAST_INIT_MODE, - " 0, provideIntProvider());") - .addLines( + DEFAULT_MODE, " }", "", " @Override", @@ -170,9 +149,21 @@ public class MapBindingExpressionTest { " .put(1L, MapModule.provideLong1())", " .put(2L, MapModule.provideLong2())", " .build();", + " }") + .addLinesIn( + FAST_INIT_MODE, " }", "", " @Override", + " public Map<Long, Long> longs() {", + " return MapBuilder.<Long, Long>newMapBuilder(3)", + " .put(0L, provideLong0Provider.get())", + " .put(1L, provideLong1Provider.get())", + " .put(2L, provideLong2Provider.get())", + " .build();", + " }") + .addLines( + " @Override", " public Map<Long, Provider<Long>> providerLongs() {", " return MapBuilder.<Long, Provider<Long>>newMapBuilder(3)") .addLinesIn( @@ -182,20 +173,14 @@ public class MapBindingExpressionTest { " .put(2L, MapModule_ProvideLong2Factory.create())") .addLinesIn( FAST_INIT_MODE, - " .put(0L, provideLong0Provider())", - " .put(1L, provideLong1Provider())", - " .put(2L, provideLong2Provider())") + " .put(0L, provideLong0Provider)", + " .put(1L, provideLong1Provider)", + " .put(2L, provideLong2Provider)") .addLines( // " .build();", " }") .addLinesIn( FAST_INIT_MODE, - " private final class SwitchingProvider<T> implements Provider<T> {", - " private final int id;", - "", - " SwitchingProvider(int id) {", - " this.id = id;", - " }", - "", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", @@ -340,13 +325,12 @@ public class MapBindingExpressionTest { "final class DaggerParent implements Parent {", " private final ParentModule parentModule;", "", - " private final class ChildImpl implements Child {", + " private static final class ChildImpl implements Child {", " @Override", " public Map<String, Object> objectMap() {", " return Collections.<String, Object>singletonMap(", " \"parent key\",", - " ParentModule_ParentKeyObjectFactory.parentKeyObject(", - " DaggerParent.this.parentModule));", + " ParentModule_ParentKeyObjectFactory.parentKeyObject(parent.parentModule));", " }", " }", "}"); diff --git a/javatests/dagger/internal/codegen/MapBindingExpressionWithGuavaTest.java b/javatests/dagger/internal/codegen/MapRequestRepresentationWithGuavaTest.java index 30b05190a..db7d039f8 100644 --- a/javatests/dagger/internal/codegen/MapBindingExpressionWithGuavaTest.java +++ b/javatests/dagger/internal/codegen/MapRequestRepresentationWithGuavaTest.java @@ -31,7 +31,7 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) -public class MapBindingExpressionWithGuavaTest { +public class MapRequestRepresentationWithGuavaTest { @Parameters(name = "{0}") public static Collection<Object[]> parameters() { return CompilerMode.TEST_PARAMETERS; @@ -39,7 +39,7 @@ public class MapBindingExpressionWithGuavaTest { private final CompilerMode compilerMode; - public MapBindingExpressionWithGuavaTest(CompilerMode compilerMode) { + public MapRequestRepresentationWithGuavaTest(CompilerMode compilerMode) { this.compilerMode = compilerMode; } @@ -130,45 +130,17 @@ public class MapBindingExpressionWithGuavaTest { "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, - " private volatile Provider<Integer> provideIntProvider;", - " private volatile Provider<Long> provideLong0Provider;", - " private volatile Provider<Long> provideLong1Provider;", - " private volatile Provider<Long> provideLong2Provider;", + " private Provider<Integer> provideIntProvider;", + " private Provider<Long> provideLong0Provider;", + " private Provider<Long> provideLong1Provider;", + " private Provider<Long> provideLong2Provider;", "", - " private Provider<Integer> provideIntProvider() {", - " Object local = provideIntProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " provideIntProvider = (Provider<Integer>) local;", - " }", - " return (Provider<Integer>) local;", - " }", - "", - " private Provider<Long> provideLong0Provider() {", - " Object local = provideLong0Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(1);", - " provideLong0Provider = (Provider<Long>) local;", - " }", - " return (Provider<Long>) local;", - " }", - "", - " private Provider<Long> provideLong1Provider() {", - " Object local = provideLong1Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(2);", - " provideLong1Provider = (Provider<Long>) local;", - " }", - " return (Provider<Long>) local;", - " }", - "", - " private Provider<Long> provideLong2Provider() {", - " Object local = provideLong2Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(3);", - " provideLong2Provider = (Provider<Long>) local;", - " }", - " return (Provider<Long>) local;", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.provideIntProvider = new SwitchingProvider<>(testComponent, 0);", + " this.provideLong0Provider = new SwitchingProvider<>(testComponent, 1);", + " this.provideLong1Provider = new SwitchingProvider<>(testComponent, 2);", + " this.provideLong2Provider = new SwitchingProvider<>(testComponent, 3);", " }") .addLines( " @Override", @@ -179,89 +151,92 @@ public class MapBindingExpressionWithGuavaTest { " @Override", " public Map<String, Provider<String>> providerStrings() {", " return ImmutableMap.<String, Provider<String>>of();", - " }", - "", + " }") + .addLinesIn( + DEFAULT_MODE, " @Override", " public Map<Integer, Integer> ints() {", " return ImmutableMap.<Integer, Integer>of(0, MapModule.provideInt());", - " }", - "", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @Override", + " public Map<Integer, Integer> ints() {", + " return ImmutableMap.<Integer, Integer>of(0, provideIntProvider.get());", + " }") + .addLinesIn( + DEFAULT_MODE, " @Override", " public Map<Integer, Provider<Integer>> providerInts() {", - " return ImmutableMap.<Integer, Provider<Integer>>of(") + " return ImmutableMap.<Integer, Provider<Integer>>of(", + " 0, MapModule_ProvideIntFactory.create());", + " }") .addLinesIn( - DEFAULT_MODE, // - " 0, MapModule_ProvideIntFactory.create());") + FAST_INIT_MODE, + " @Override", + " public Map<Integer, Provider<Integer>> providerInts() {", + " return ImmutableMap.<Integer, Provider<Integer>>of(0, provideIntProvider);", + " }") .addLinesIn( - FAST_INIT_MODE, // - " 0, provideIntProvider());") - .addLines( - " }", - "", + DEFAULT_MODE, " @Override", " public Map<Long, Long> longs() {", " return ImmutableMap.<Long, Long>of(", " 0L, MapModule.provideLong0(),", " 1L, MapModule.provideLong1(),", " 2L, MapModule.provideLong2());", - " }", - "", + " }") + .addLinesIn( + FAST_INIT_MODE, " @Override", - " public Map<Long, Provider<Long>> providerLongs() {", - " return ImmutableMap.<Long, Provider<Long>>of(") + " public Map<Long, Long> longs() {", + " return ImmutableMap.<Long, Long>of(", + " 0L, provideLong0Provider.get(),", + " 1L, provideLong1Provider.get(),", + " 2L, provideLong2Provider.get());", + " }") .addLinesIn( DEFAULT_MODE, + " @Override", + " public Map<Long, Provider<Long>> providerLongs() {", + " return ImmutableMap.<Long, Provider<Long>>of(", " 0L, MapModule_ProvideLong0Factory.create(),", " 1L, MapModule_ProvideLong1Factory.create(),", - " 2L, MapModule_ProvideLong2Factory.create());") + " 2L, MapModule_ProvideLong2Factory.create());", + " }") .addLinesIn( FAST_INIT_MODE, - " 0L, provideLong0Provider(),", - " 1L, provideLong1Provider(),", - " 2L, provideLong2Provider());") + " @Override", + " public Map<Long, Provider<Long>> providerLongs() {", + " return ImmutableMap.<Long, Provider<Long>>of(", + " 0L, provideLong0Provider,", + " 1L, provideLong1Provider,", + " 2L, provideLong2Provider);", + " }") .addLines( - " }", - "", " @Override", " public Sub sub() {", - " return new SubImpl();", + " return new SubImpl(testComponent);", " }", "", - " private final class SubImpl implements Sub {") + " private static final class SubImpl implements Sub {") .addLinesIn( FAST_INIT_MODE, - " private volatile Provider<Long> provideLong3Provider;", - " private volatile Provider<Long> provideLong4Provider;", - " private volatile Provider<Long> provideLong5Provider;", - " private SubImpl() {}", - "", - " private Provider<Long> provideLong3Provider() {", - " Object local = provideLong3Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " provideLong3Provider = (Provider<Long>) local;", - " }", - " return (Provider<Long>) local;", - " }", + " private Provider<Long> provideLong3Provider;", + " private Provider<Long> provideLong4Provider;", + " private Provider<Long> provideLong5Provider;", "", - " private Provider<Long> provideLong4Provider() {", - " Object local = provideLong4Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(1);", - " provideLong4Provider = (Provider<Long>) local;", - " }", - " return (Provider<Long>) local;", - " }", - "", - " private Provider<Long> provideLong5Provider() {", - " Object local = provideLong5Provider;", - " if (local == null) {", - " local = new SwitchingProvider<>(2);", - " provideLong5Provider = (Provider<Long>) local;", - " }", - " return (Provider<Long>) local;", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.provideLong3Provider =", + " new SwitchingProvider<>(testComponent, subImpl, 0);", + " this.provideLong4Provider =", + " new SwitchingProvider<>(testComponent, subImpl, 1);", + " this.provideLong5Provider =", + " new SwitchingProvider<>(testComponent, subImpl, 2);", " }") - .addLines( + .addLinesIn( + DEFAULT_MODE, " @Override", " public Map<Long, Long> longs() {", " return ImmutableMap.<Long, Long>builderWithExpectedSize(6)", @@ -272,38 +247,49 @@ public class MapBindingExpressionWithGuavaTest { " .put(4L, SubcomponentMapModule.provideLong4())", " .put(5L, SubcomponentMapModule.provideLong5())", " .build();", - " }", - "", + " }") + .addLinesIn( + FAST_INIT_MODE, " @Override", - " public Map<Long, Provider<Long>> providerLongs() {", - " return ImmutableMap.<Long, Provider<Long>>builderWithExpectedSize(6)") + " public Map<Long, Long> longs() {", + " return ImmutableMap.<Long, Long>builderWithExpectedSize(6)", + " .put(0L, testComponent.provideLong0Provider.get())", + " .put(1L, testComponent.provideLong1Provider.get())", + " .put(2L, testComponent.provideLong2Provider.get())", + " .put(3L, provideLong3Provider.get())", + " .put(4L, provideLong4Provider.get())", + " .put(5L, provideLong5Provider.get())", + " .build();", + " }") .addLinesIn( DEFAULT_MODE, + " @Override", + " public Map<Long, Provider<Long>> providerLongs() {", + " return ImmutableMap.<Long, Provider<Long>>builderWithExpectedSize(6)", " .put(0L, MapModule_ProvideLong0Factory.create())", " .put(1L, MapModule_ProvideLong1Factory.create())", " .put(2L, MapModule_ProvideLong2Factory.create())", " .put(3L, SubcomponentMapModule_ProvideLong3Factory.create())", " .put(4L, SubcomponentMapModule_ProvideLong4Factory.create())", - " .put(5L, SubcomponentMapModule_ProvideLong5Factory.create())") + " .put(5L, SubcomponentMapModule_ProvideLong5Factory.create())", + " .build();", + " }") .addLinesIn( FAST_INIT_MODE, - " .put(0L, DaggerTestComponent.this.provideLong0Provider())", - " .put(1L, DaggerTestComponent.this.provideLong1Provider())", - " .put(2L, DaggerTestComponent.this.provideLong2Provider())", - " .put(3L, provideLong3Provider())", - " .put(4L, provideLong4Provider())", - " .put(5L, provideLong5Provider())") - .addLines( // - " .build();", " }") + " @Override", + " public Map<Long, Provider<Long>> providerLongs() {", + " return ImmutableMap.<Long, Provider<Long>>builderWithExpectedSize(6)", + " .put(0L, testComponent.provideLong0Provider)", + " .put(1L, testComponent.provideLong1Provider)", + " .put(2L, testComponent.provideLong2Provider)", + " .put(3L, provideLong3Provider)", + " .put(4L, provideLong4Provider)", + " .put(5L, provideLong5Provider)", + " .build();", + " }") .addLinesIn( FAST_INIT_MODE, - " private final class SwitchingProvider<T> implements Provider<T> {", - " private final int id;", - "", - " SwitchingProvider(int id) {", - " this.id = id;", - " }", - "", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", @@ -317,13 +303,7 @@ public class MapBindingExpressionWithGuavaTest { " }", " }", "", - " private final class SwitchingProvider<T> implements Provider<T> {", - " private final int id;", - "", - " SwitchingProvider(int id) {", - " this.id = id;", - " }", - "", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", @@ -466,13 +446,12 @@ public class MapBindingExpressionWithGuavaTest { "final class DaggerParent implements Parent {", " private final ParentModule parentModule;", "", - " private final class ChildImpl implements Child {", + " private static final class ChildImpl implements Child {", " @Override", " public Map<String, Object> objectMap() {", " return ImmutableMap.<String, Object>of(", " \"parent key\",", - " ParentModule_ParentKeyObjectFactory.parentKeyObject(", - " DaggerParent.this.parentModule));", + " ParentModule_ParentKeyObjectFactory.parentKeyObject(parent.parentModule));", " }", " }", "}"); diff --git a/javatests/dagger/internal/codegen/MembersInjectionTest.java b/javatests/dagger/internal/codegen/MembersInjectionTest.java index c44277922..ca22d563a 100644 --- a/javatests/dagger/internal/codegen/MembersInjectionTest.java +++ b/javatests/dagger/internal/codegen/MembersInjectionTest.java @@ -189,8 +189,10 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class GenericClass_MembersInjector<A, B>", " implements MembersInjector<GenericClass<A, B>> {", @@ -279,8 +281,10 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class Child_MembersInjector<T>", " implements MembersInjector<Child<T>> {", @@ -364,8 +368,10 @@ public class MembersInjectionTest { "import dagger.MembersInjector;", "import dagger.internal.DoubleCheck;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class FieldInjection_MembersInjector", " implements MembersInjector<FieldInjection> {", @@ -444,9 +450,11 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Named;", "import javax.inject.Provider;"), "", + "@QualifierMetadata(\"javax.inject.Named\")", GeneratedLines.generatedAnnotations(), "public final class FieldInjectionWithQualifier_MembersInjector", " implements MembersInjector<FieldInjectionWithQualifier> {", @@ -514,8 +522,10 @@ public class MembersInjectionTest { "import dagger.Lazy;", "import dagger.MembersInjector;", "import dagger.internal.DoubleCheck;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class MethodInjection_MembersInjector", " implements MembersInjector<MethodInjection> {", @@ -603,8 +613,10 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class MixedMemberInjection_MembersInjector", " implements MembersInjector<MixedMemberInjection> {", @@ -686,8 +698,10 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class AllInjections_MembersInjector ", " implements MembersInjector<AllInjections> {", @@ -747,14 +761,16 @@ public class MembersInjectionTest { "}"); JavaFileObject expectedMembersInjector = JavaFileObjects.forSourceLines( - "test.AllInjections_MembersInjector", + "test.B_MembersInjector", "package test;", "", GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class B_MembersInjector implements MembersInjector<B> {", " private final Provider<String> sProvider;", @@ -815,8 +831,10 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class OuterType_B_MembersInjector", " implements MembersInjector<OuterType.B> {", @@ -881,8 +899,10 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class OuterType_B_MembersInjector", " implements MembersInjector<OuterType.B> {", @@ -1057,8 +1077,10 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class Child_MembersInjector implements MembersInjector<Child> {", " private final Provider<Foo> objectProvider;", @@ -1255,8 +1277,10 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class InjectedType_MembersInjector ", " implements MembersInjector<InjectedType> {", @@ -1296,8 +1320,12 @@ public class MembersInjectionTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class InjectedType_Factory implements Factory<InjectedType> {", " private final Provider<Integer> primitiveIntProvider;", @@ -1400,8 +1428,10 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class Inaccessible_MembersInjector", " implements MembersInjector<Inaccessible> {", @@ -1537,65 +1567,52 @@ public class MembersInjectionTest { .addLines( "package test;", "", - GeneratedLines.generatedImports( - "import com.google.errorprone.annotations.CanIgnoreReturnValue;", - "import other.InaccessiblesModule;", - "import other.InaccessiblesModule_InaccessiblesFactory;", - "import other.UsesInaccessibles;", - "import other.UsesInaccessibles_Factory;", - "import other.UsesInaccessibles_MembersInjector;"), - "", GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent {") - .addLinesIn( - FAST_INIT_MODE, - " private volatile Object listOfInaccessible = new MemoizedSentinel();", + "final class DaggerTestComponent implements TestComponent {", + " private final DaggerTestComponent testComponent = this;", "", - " private List listOfInaccessible() {", - " Object local = listOfInaccessible;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = listOfInaccessible;", - " if (local instanceof MemoizedSentinel) {", - " local = InaccessiblesModule_InaccessiblesFactory.inaccessibles();", - " listOfInaccessible =", - " DoubleCheck.reentrantCheck(listOfInaccessible, local);", - " }", - " }", - " }", - " return (List) local;", - " }") + " @SuppressWarnings(\"rawtypes\")", + " private Provider inaccessiblesProvider;") .addLinesIn( DEFAULT_MODE, - " @SuppressWarnings(\"rawtypes\")", - " private Provider inaccessiblesProvider;", - "", " @SuppressWarnings(\"unchecked\")", " private void initialize() {", " this.inaccessiblesProvider =", " DoubleCheck.provider(InaccessiblesModule_InaccessiblesFactory.create());", " }") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.inaccessiblesProvider =", + " DoubleCheck.provider(", + " new SwitchingProvider<List>(testComponent, 0));", + " }") .addLines( - "", " @Override", " public UsesInaccessibles usesInaccessibles() {", - " return injectUsesInaccessibles(", - " UsesInaccessibles_Factory.newInstance());", - " }", - "", - " @CanIgnoreReturnValue", - " private UsesInaccessibles injectUsesInaccessibles(", - " UsesInaccessibles instance) {", - " UsesInaccessibles_MembersInjector.injectInaccessibles(") + " return injectUsesInaccessibles(UsesInaccessibles_Factory.newInstance());", + " }") .addLinesIn( FAST_INIT_MODE, - " instance, (List) listOfInaccessible());") - .addLinesIn( - DEFAULT_MODE, - " instance, (List) inaccessiblesProvider.get());") - .addLines( + " @CanIgnoreReturnValue", + " private UsesInaccessibles injectUsesInaccessibles(UsesInaccessibles instance) {", + " UsesInaccessibles_MembersInjector", + " .injectInaccessibles(instance, (List) inaccessiblesProvider.get());", " return instance;", " }", + "", + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0:", + " return (T) InaccessiblesModule_InaccessiblesFactory.inaccessibles();", + " default: throw new AssertionError(id);", + " }", + " }", + " }", "}") .build(); assertThat(compilation) @@ -1738,8 +1755,10 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class A_MembersInjector implements MembersInjector<A> {", " private final Provider<String> valueCProvider;", @@ -1776,8 +1795,10 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class C_MembersInjector implements MembersInjector<C> {", " private final Provider<String> valueCProvider;", @@ -1855,8 +1876,10 @@ public class MembersInjectionTest { "", GeneratedLines.generatedImports( "import dagger.MembersInjector;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class A_MembersInjector implements MembersInjector<A> {", " private final Provider<String> valueBProvider;", @@ -1883,8 +1906,10 @@ public class MembersInjectionTest { GeneratedLines.generatedImports( "import dagger.MembersInjector;", "import dagger.internal.InjectedFieldSignature;", + "import dagger.internal.QualifierMetadata;", "import javax.inject.Provider;"), "", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class B_MembersInjector implements MembersInjector<B> {", " private final Provider<String> valueBProvider;", @@ -1921,4 +1946,223 @@ public class MembersInjectionTest { .generatedSourceFile("test.B_MembersInjector") .hasSourceEquivalentTo(expectedBMembersInjector); } + + // Regression test for https://github.com/google/dagger/issues/3143 + @Test + public void testMembersInjectionBindingExistsInParentComponent() { + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.MyComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = MyComponentModule.class)", + "public interface MyComponent {", + " void inject(Bar bar);", + "", + " MySubcomponent subcomponent();", + "}"); + + JavaFileObject subcomponent = + JavaFileObjects.forSourceLines( + "test.MySubcomponent", + "package test;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent(modules = MySubcomponentModule.class)", + "interface MySubcomponent {", + " Foo foo();", + "}"); + + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Foo {", + " @Inject Foo(Bar bar) {}", + "}"); + + JavaFileObject bar = + JavaFileObjects.forSourceLines( + "test.Bar", + "package test;", + "", + "import java.util.Set;", + "import javax.inject.Inject;", + "", + "class Bar {", + " @Inject Set<String> multibindingStrings;", + " @Inject Bar() {}", + "}"); + + JavaFileObject componentModule = + JavaFileObjects.forSourceLines( + "test.MyComponentModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import dagger.multibindings.IntoSet;", + "", + "@Module", + "interface MyComponentModule {", + " @Provides", + " @IntoSet", + " static String provideString() {", + " return \"\";", + " }", + "}"); + + JavaFileObject subcomponentModule = + JavaFileObjects.forSourceLines( + "test.MySubcomponentModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import dagger.multibindings.IntoSet;", + "", + "@Module", + "interface MySubcomponentModule {", + " @Provides", + " @IntoSet", + " static String provideString() {", + " return \"\";", + " }", + "}"); + + Compilation compilation = + compilerWithOptions(compilerMode.javacopts()) + .compile(component, subcomponent, foo, bar, componentModule, subcomponentModule); + assertThat(compilation).succeeded(); + + // Check that the injectBar() method is not shared across components. + // We avoid sharing them in general because they may be different (e.g. in this case we inject + // multibindings that are different across components). + assertThat(compilation) + .generatedSourceFile("test.DaggerMyComponent") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.DaggerMyComponent", + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "public final class DaggerMyComponent implements MyComponent {", + " private Set<String> setOfString() {", + " return ImmutableSet.<String>of(", + " MyComponentModule_ProvideStringFactory.provideString());", + " }", + "", + " @Override", + " public void inject(Bar bar) {", + " injectBar(bar);", + " }", + "", + " @CanIgnoreReturnValue", + " private Bar injectBar(Bar instance) {", + " Bar_MembersInjector.injectMultibindingStrings(instance, setOfString());", + " return instance;", + " }", + "", + " private static final class MySubcomponentImpl implements MySubcomponent {", + " private Set<String> setOfString() {", + " return ImmutableSet.<String>of(", + " MyComponentModule_ProvideStringFactory.provideString(),", + " MySubcomponentModule_ProvideStringFactory.provideString());", + " }", + "", + " private Bar bar() {", + " return injectBar(Bar_Factory.newInstance());", + " }", + "", + " @Override", + " public Foo foo() {", + " return new Foo(bar());", + " }", + "", + " @CanIgnoreReturnValue", + " private Bar injectBar(Bar instance) {", + " Bar_MembersInjector.injectMultibindingStrings(instance, setOfString());", + " return instance;", + " }", + " }", + "}")); + } + + // Test that if both a MembersInjectionBinding and ProvisionBinding both exist in the same + // component they share the same inject methods rather than generating their own. + @Test + public void testMembersInjectionBindingSharesInjectMethodsWithProvisionBinding() { + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.MyComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component", + "public interface MyComponent {", + " Foo foo();", + "", + " void inject(Foo foo);", + "}"); + + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Foo {", + " @Inject Bar bar;", + " @Inject Foo() {}", + "}"); + + JavaFileObject bar = + JavaFileObjects.forSourceLines( + "test.Bar", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Bar {", + " @Inject Bar() {}", + "}"); + + Compilation compilation = + compilerWithOptions(compilerMode.javacopts()).compile(component, foo, bar); + assertThat(compilation).succeeded(); + + assertThat(compilation) + .generatedSourceFile("test.DaggerMyComponent") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.DaggerMyComponent", + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "public final class DaggerMyComponent implements MyComponent {", + " @Override", + " public Foo foo() {", + " return injectFoo(Foo_Factory.newInstance());", + " }", + "", + " @Override", + " public void inject(Foo foo) {", + " injectFoo(foo);", + " }", + "", + " @CanIgnoreReturnValue", + " private Foo injectFoo(Foo instance) {", + " Foo_MembersInjector.injectBar(instance, new Bar());", + " return instance;", + " }", + "}")); + } } diff --git a/javatests/dagger/internal/codegen/MethodSignatureFormatterTest.java b/javatests/dagger/internal/codegen/MethodSignatureFormatterTest.java deleted file mode 100644 index 6d2f98988..000000000 --- a/javatests/dagger/internal/codegen/MethodSignatureFormatterTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2014 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.truth.Truth.assertThat; -import static javax.lang.model.util.ElementFilter.methodsIn; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.testing.compile.CompilationRule; -import dagger.BindsInstance; -import dagger.Component; -import dagger.Module; -import dagger.Provides; -import dagger.internal.codegen.MethodSignatureFormatterTest.OuterClass.InnerClass; -import dagger.internal.codegen.binding.InjectionAnnotations; -import dagger.internal.codegen.binding.MethodSignatureFormatter; -import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class MethodSignatureFormatterTest { - @Rule public CompilationRule compilationRule = new CompilationRule(); - - @Inject DaggerElements elements; - @Inject DaggerTypes types; - @Inject InjectionAnnotations injectionAnnotations; - - static class OuterClass { - @interface Foo { - Class<?> bar(); - } - - static class InnerClass { - @Foo(bar = String.class) - @Singleton - String foo( - @SuppressWarnings("unused") int a, - @SuppressWarnings("unused") ImmutableList<Boolean> blah) { - return "foo"; - } - } - } - - @Before - public void setUp() { - DaggerMethodSignatureFormatterTest_TestComponent.factory().create(compilationRule).inject(this); - } - - @Test public void methodSignatureTest() { - TypeElement inner = elements.getTypeElement(InnerClass.class); - ExecutableElement method = Iterables.getOnlyElement(methodsIn(inner.getEnclosedElements())); - String formatted = new MethodSignatureFormatter(types, injectionAnnotations).format(method); - // This is gross, but it turns out that annotation order is not guaranteed when getting - // all the AnnotationMirrors from an Element, so I have to test this chopped-up to make it - // less brittle. - assertThat(formatted).contains("@Singleton"); - assertThat(formatted).doesNotContain("@javax.inject.Singleton"); // maybe more importantly - assertThat(formatted) - .contains("@dagger.internal.codegen.MethodSignatureFormatterTest.OuterClass.Foo" - + "(bar=String.class)"); - assertThat(formatted).contains(" String "); // return type compressed - assertThat(formatted).contains("int, ImmutableList<Boolean>)"); // parameters compressed. - } - - @Singleton - @Component(modules = TestModule.class) - interface TestComponent { - void inject(MethodSignatureFormatterTest test); - - @Component.Factory - interface Factory { - TestComponent create(@BindsInstance CompilationRule compilationRule); - } - } - - @Module - static class TestModule { - @Provides - static DaggerElements elements(CompilationRule compilationRule) { - return new DaggerElements(compilationRule.getElements(), compilationRule.getTypes()); - } - - @Provides - static DaggerTypes types(CompilationRule compilationRule, DaggerElements elements) { - return new DaggerTypes(compilationRule.getTypes(), elements); - } - } -} diff --git a/javatests/dagger/internal/codegen/MissingBindingSuggestionsTest.java b/javatests/dagger/internal/codegen/MissingBindingSuggestionsTest.java index 3c066bab2..a5a72f7d3 100644 --- a/javatests/dagger/internal/codegen/MissingBindingSuggestionsTest.java +++ b/javatests/dagger/internal/codegen/MissingBindingSuggestionsTest.java @@ -96,8 +96,7 @@ public class MissingBindingSuggestionsTest { daggerCompiler().compile(fooComponent, barComponent, topComponent, foo, bar, barModule); assertThat(compilation).failed(); assertThat(compilation).hadErrorCount(1); - assertThat(compilation) - .hadErrorContaining("A binding with matching key exists in component: BarComponent"); + assertThat(compilation).hadErrorContaining("A binding for Bar exists in BarComponent:"); } @Test public void suggestsBindingInNestedSubcomponent() { @@ -157,8 +156,7 @@ public class MissingBindingSuggestionsTest { .compile(fooComponent, barComponent, bazComponent, topComponent, foo, baz, bazModule); assertThat(compilation).failed(); assertThat(compilation).hadErrorCount(1); - assertThat(compilation) - .hadErrorContaining("A binding with matching key exists in component: BazComponent"); + assertThat(compilation).hadErrorContaining("A binding for Baz exists in BazComponent:"); } @Test @@ -220,11 +218,11 @@ public class MissingBindingSuggestionsTest { message( "\033[1;31m[Dagger/MissingBinding]\033[0m Baz cannot be provided without an " + "@Inject constructor or an @Provides-annotated method.", - "A binding with matching key exists in component: Child", + "A binding for Baz exists in Child:", " Baz is injected at", - " Bar(baz)", + " [Parent] Bar(baz)", " Bar is requested at", - " Parent.bar()", + " [Parent] Parent.bar()", "The following other entry points also depend on it:", " Parent.foo()", " Child.foo() [Parent → Child]")) @@ -303,11 +301,11 @@ public class MissingBindingSuggestionsTest { message( "\033[1;31m[Dagger/MissingBinding]\033[0m Baz cannot be provided without an " + "@Inject constructor or an @Provides-annotated method.", - "A binding with matching key exists in component: Child2", + "A binding for Baz exists in Child2:", " Baz is injected at", - " Bar(baz)", + " [Parent] Bar(baz)", " Bar is requested at", - " Parent.bar()", + " [Parent] Parent.bar()", "The following other entry points also depend on it:", " Parent.foo()", " Child1.foo() [Parent → Child1]", diff --git a/javatests/dagger/internal/codegen/MissingBindingValidationTest.java b/javatests/dagger/internal/codegen/MissingBindingValidationTest.java index 1aabd4899..887ffa4b6 100644 --- a/javatests/dagger/internal/codegen/MissingBindingValidationTest.java +++ b/javatests/dagger/internal/codegen/MissingBindingValidationTest.java @@ -538,7 +538,7 @@ public class MissingBindingValidationTest { assertThat(compilation).hadErrorCount(1); assertThat(compilation) .hadErrorContainingMatch( - "(?s)\\QString cannot be provided\\E.*\\QChild.needsString()\\E") + "(?s)\\QString cannot be provided\\E.*\\Q[Child] Child.needsString()\\E") .inFile(parent) .onLineContaining("interface Parent"); } @@ -928,4 +928,239 @@ public class MissingBindingValidationTest { .onLineContaining("interface Parent"); } + @Test + public void sameSubcomponentUsedInDifferentHierarchiesMissingBindingFromOneSide() { + JavaFileObject parent = + JavaFileObjects.forSourceLines( + "test.Parent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component", + "interface Parent {", + " Child1 getChild1();", + " Child2 getChild2();", + "}"); + JavaFileObject child1 = + JavaFileObjects.forSourceLines( + "test.Child1", + "package test;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent(modules = Child1Module.class)", + "interface Child1 {", + " RepeatedSub getSub();", + "}"); + JavaFileObject child2 = + JavaFileObjects.forSourceLines( + "test.Child2", + "package test;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent(modules = Child2Module.class)", + "interface Child2 {", + " RepeatedSub getSub();", + "}"); + JavaFileObject repeatedSub = + JavaFileObjects.forSourceLines( + "test.RepeatedSub", + "package test;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent(modules = RepeatedSubModule.class)", + "interface RepeatedSub {", + " Object getObject();", + "}"); + JavaFileObject child1Module = + JavaFileObjects.forSourceLines( + "test.Child1Module", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import java.util.Set;", + "import dagger.multibindings.Multibinds;", + "", + "@Module", + "interface Child1Module {", + " @Multibinds Set<Integer> multibindIntegerSet();", + "}"); + JavaFileObject child2Module = + JavaFileObjects.forSourceLines( + "test.Child2Module", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import java.util.Set;", + "import dagger.multibindings.Multibinds;", + "", + "@Module", + "interface Child2Module {", + " @Multibinds Set<Integer> multibindIntegerSet();", + "", + " @Provides", + " static Object provideObject(Set<Integer> intSet) {", + " return new Object();", + " }", + "}"); + JavaFileObject repeatedSubModule = + JavaFileObjects.forSourceLines( + "test.RepeatedSubModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import dagger.multibindings.IntoSet;", + "import java.util.Set;", + "import dagger.multibindings.Multibinds;", + "", + "@Module", + "interface RepeatedSubModule {", + " @Provides", + " @IntoSet", + " static Integer provideInt() {", + " return 9;", + " }", + "}"); + + Compilation compilation = + daggerCompiler() + .compile( + parent, child1, child2, repeatedSub, child1Module, child2Module, repeatedSubModule); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining("A binding for Object exists in [Parent → Child2 → RepeatedSub]:"); + assertThat(compilation) + .hadErrorContaining( + "[Parent → Child1 → RepeatedSub] RepeatedSub.getObject() [Parent → Child1 →" + + " RepeatedSub]"); + } + + @Test + public void differentComponentPkgSameSimpleNameMissingBinding() { + JavaFileObject parent = + JavaFileObjects.forSourceLines( + "test.Parent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component", + "interface Parent {", + " Child1 getChild1();", + " Child2 getChild2();", + "}"); + JavaFileObject child1 = + JavaFileObjects.forSourceLines( + "test.Child1", + "package test;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent(modules = Child1Module.class)", + "interface Child1 {", + " foo.Sub getSub();", + "}"); + JavaFileObject child2 = + JavaFileObjects.forSourceLines( + "test.Child2", + "package test;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent(modules = Child2Module.class)", + "interface Child2 {", + " bar.Sub getSub();", + "}"); + JavaFileObject sub1 = + JavaFileObjects.forSourceLines( + "foo.Sub", + "package foo;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent(modules = test.RepeatedSubModule.class)", + "public interface Sub {", + " Object getObject();", + "}"); + JavaFileObject sub2 = + JavaFileObjects.forSourceLines( + "bar.Sub", + "package bar;", + "", + "import dagger.Subcomponent;", + "", + "@Subcomponent(modules = test.RepeatedSubModule.class)", + "public interface Sub {", + " Object getObject();", + "}"); + JavaFileObject child1Module = + JavaFileObjects.forSourceLines( + "test.Child1Module", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import java.util.Set;", + "import dagger.multibindings.Multibinds;", + "", + "@Module", + "interface Child1Module {", + " @Multibinds Set<Integer> multibindIntegerSet();", + "}"); + JavaFileObject child2Module = + JavaFileObjects.forSourceLines( + "test.Child2Module", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import java.util.Set;", + "import dagger.multibindings.Multibinds;", + "", + "@Module", + "interface Child2Module {", + " @Multibinds Set<Integer> multibindIntegerSet();", + "", + " @Provides", + " static Object provideObject(Set<Integer> intSet) {", + " return new Object();", + " }", + "}"); + JavaFileObject repeatedSubModule = + JavaFileObjects.forSourceLines( + "test.RepeatedSubModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import dagger.multibindings.IntoSet;", + "import java.util.Set;", + "import dagger.multibindings.Multibinds;", + "", + "@Module", + "public interface RepeatedSubModule {", + " @Provides", + " @IntoSet", + " static Integer provideInt() {", + " return 9;", + " }", + "}"); + + Compilation compilation = + daggerCompiler() + .compile( + parent, child1, child2, sub1, sub2, child1Module, child2Module, repeatedSubModule); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation).hadErrorContaining("A binding for Object exists in bar.Sub:"); + assertThat(compilation) + .hadErrorContaining("[foo.Sub] foo.Sub.getObject() [Parent → Child1 → foo.Sub]"); + } } diff --git a/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java b/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java index 84c7be4b8..3882b47cf 100644 --- a/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java +++ b/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java @@ -189,16 +189,28 @@ public class ModuleFactoryGeneratorTest { } @Test public void validatesIncludedModules() { - JavaFileObject module = JavaFileObjects.forSourceLines("test.Parent", - "package test;", - "", - "import dagger.Module;", - "", - "@Module(includes = Void.class)", - "class TestModule {}"); + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.Parent", + "package test;", + "", + "import dagger.Module;", + "", + "@Module(", + " includes = {", + " Void.class,", + " String.class,", + " }", + ")", + "class TestModule {}"); Compilation compilation = daggerCompiler().compile(module); assertThat(compilation).failed(); + // In java 11, errors reported on individual items in an annotation value's list will show up + // as separate errors to the user on the associated lines reported. However, in java 8, errors + // reported on individual items in an annotation value's list will show up as a single error + // (which ever is reported first) on the list itself, rather than on the items reported. + assertThat(compilation).hadErrorCount(1); assertThat(compilation) .hadErrorContaining( "java.lang.Void is listed as a module, but is not annotated with @Module"); @@ -224,8 +236,12 @@ public class ModuleFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;"), + "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideStringFactory implements Factory<String> {", " private final TestModule module;", @@ -270,8 +286,13 @@ public class ModuleFactoryGeneratorTest { "TestModule_ProvideStringFactory", "package test;", "", - GeneratedLines.generatedImports("import dagger.internal.Factory;"), + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideStringFactory implements Factory<String> {", " private final TestModule module;", @@ -315,8 +336,13 @@ public class ModuleFactoryGeneratorTest { "TestModule_ProvideStringFactory", "package test;", "", - GeneratedLines.generatedImports("import dagger.internal.Factory;"), + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideStringFactory implements Factory<String> {", " private final TestModule module;", @@ -388,9 +414,16 @@ public class ModuleFactoryGeneratorTest { "import dagger.MembersInjector;", "import dagger.internal.Factory;", "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import java.util.List;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata({", + " \"test.QualifierA\",", + " \"test.QualifierB\"", + "})", GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideObjectsFactory", " implements Factory<List<Object>> {", @@ -459,8 +492,12 @@ public class ModuleFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;"), + "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideStringFactory implements Factory<String> {", " private final TestModule module;", @@ -512,8 +549,12 @@ public class ModuleFactoryGeneratorTest { GeneratedLines.generatedImports( "import dagger.internal.Factory;", "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import java.util.List;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideWildcardListFactory implements " + "Factory<List<List<?>>> {", @@ -565,8 +606,12 @@ public class ModuleFactoryGeneratorTest { GeneratedLines.generatedImports( "import dagger.internal.Factory;", "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import java.util.Set;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideStringsFactory implements Factory<Set<String>> {", " private final TestModule module;", @@ -876,9 +921,13 @@ public class ModuleFactoryGeneratorTest { GeneratedLines.generatedImports( "import dagger.internal.Factory;", "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import java.util.List;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class ParentModule_ProvideListBFactory<A extends CharSequence,", " B, C extends Number & Comparable<C>> implements Factory<List<B>> {", @@ -915,8 +964,12 @@ public class ModuleFactoryGeneratorTest { GeneratedLines.generatedImports( "import dagger.internal.Factory;", "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class ParentModule_ProvideBElementFactory<A extends CharSequence,", " B, C extends Number & Comparable<C>> implements Factory<B> {", @@ -954,8 +1007,12 @@ public class ModuleFactoryGeneratorTest { GeneratedLines.generatedImports( "import dagger.internal.Factory;", "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class ParentModule_ProvideBEntryFactory<A extends CharSequence,", " B, C extends Number & Comparable<C>> implements Factory<B>> {", @@ -992,8 +1049,12 @@ public class ModuleFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;"), + "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class ChildNumberModule_ProvideNumberFactory", " implements Factory<Number> {", @@ -1024,8 +1085,12 @@ public class ModuleFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;"), + "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class ChildIntegerModule_ProvideIntegerFactory", " implements Factory<Integer> {", @@ -1098,8 +1163,12 @@ public class ModuleFactoryGeneratorTest { GeneratedLines.generatedImports( "import dagger.internal.Factory;", "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import java.util.Map;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class ParameterizedModule_ProvideMapStringNumberFactory", " implements Factory<Map<String, Number>> {", @@ -1130,8 +1199,12 @@ public class ModuleFactoryGeneratorTest { "", GeneratedLines.generatedImports( "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;"), + "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class ParameterizedModule_ProvideNonGenericTypeFactory", " implements Factory<Object> {", @@ -1163,8 +1236,12 @@ public class ModuleFactoryGeneratorTest { GeneratedLines.generatedImports( "import dagger.internal.Factory;", "import dagger.internal.Preconditions;", + "import dagger.internal.QualifierMetadata;", + "import dagger.internal.ScopeMetadata;", "import javax.inject.Provider;"), "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class ParameterizedModule_ProvideNonGenericTypeWithDepsFactory", " implements Factory<String> {", @@ -1402,6 +1479,8 @@ public class ModuleFactoryGeneratorTest { "test.TestModule_GetFactory", "package test;", "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class TestModule_GetFactory implements Factory<Integer> {", " @Override", @@ -1425,6 +1504,8 @@ public class ModuleFactoryGeneratorTest { "test.TestModule_CreateFactory", "package test;", "", + "@ScopeMetadata", + "@QualifierMetadata", GeneratedLines.generatedAnnotations(), "public final class TestModule_CreateFactory implements Factory<Boolean> {", " @Override", @@ -1442,6 +1523,202 @@ public class ModuleFactoryGeneratorTest { "}")); } + @Test + public void testScopedMetadataOnStaticProvides() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.ScopedBinding", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import javax.inject.Singleton;", + "", + "@Module", + "interface MyModule {", + " @NonScope", + " @Singleton", + " @Provides", + " static String provideString() {", + " return \"\";", + " }", + "}"); + JavaFileObject nonScope = + JavaFileObjects.forSourceLines( + "test.NonScope", + "package test;", + "", + "@interface NonScope {}"); + Compilation compilation = daggerCompiler().compile(module, nonScope); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.MyModule_ProvideStringFactory") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.MyModule_ProvideStringFactory", + "package test;", + "", + "@ScopeMetadata(\"javax.inject.Singleton\")", + "@QualifierMetadata", + GeneratedLines.generatedAnnotations(), + "public final class MyModule_ProvideStringFactory implements Factory<String> {}")); + } + + @Test + public void testScopedMetadataOnNonStaticProvides() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.ScopedBinding", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import javax.inject.Singleton;", + "", + "@Module", + "class MyModule {", + " @NonScope", + " @Singleton", + " @Provides", + " String provideString() {", + " return \"\";", + " }", + "}"); + JavaFileObject nonScope = + JavaFileObjects.forSourceLines( + "test.NonScope", + "package test;", + "", + "@interface NonScope {}"); + Compilation compilation = daggerCompiler().compile(module, nonScope); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.MyModule_ProvideStringFactory") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.MyModule_ProvideStringFactory", + "package test;", + "", + "@ScopeMetadata(\"javax.inject.Singleton\")", + "@QualifierMetadata", + GeneratedLines.generatedAnnotations(), + "public final class MyModule_ProvideStringFactory implements Factory<String> {}")); + } + + @Test + public void testScopeMetadataWithCustomScope() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.ScopedBinding", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import javax.inject.Singleton;", + "", + "@Module", + "interface MyModule {", + " @NonScope(\"someValue\")", + " @CustomScope(\"someOtherValue\")", + " @Provides", + " static String provideString() {", + " return \"\";", + " }", + "}"); + JavaFileObject customScope = + JavaFileObjects.forSourceLines( + "test.CustomScope", + "package test;", + "", + "import javax.inject.Scope;", + "", + "@Scope", + "@interface CustomScope {", + " String value();", + "}"); + JavaFileObject nonScope = + JavaFileObjects.forSourceLines( + "test.NonScope", + "package test;", + "", + "@interface NonScope {", + " String value();", + "}"); + Compilation compilation = daggerCompiler().compile(module, customScope, nonScope); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.MyModule_ProvideStringFactory") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.MyModule_ProvideStringFactory", + "package test;", + "", + "@ScopeMetadata(\"test.CustomScope\")", + "@QualifierMetadata", + GeneratedLines.generatedAnnotations(), + "public final class MyModule_ProvideStringFactory implements Factory<String> {}")); + } + + @Test + public void testQualifierMetadataOnProvides() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.ScopedBinding", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import javax.inject.Singleton;", + "", + "@Module", + "interface MyModule {", + " @Provides", + " @NonQualifier", + " @MethodQualifier", + " static String provideString(@NonQualifier @ParamQualifier int i) {", + " return \"\";", + " }", + "}"); + JavaFileObject methodQualifier = + JavaFileObjects.forSourceLines( + "test.MethodQualifier", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface MethodQualifier {}"); + JavaFileObject paramQualifier = + JavaFileObjects.forSourceLines( + "test.ParamQualifier", + "package test;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "@interface ParamQualifier {}"); + JavaFileObject nonQualifier = + JavaFileObjects.forSourceLines( + "test.NonQualifier", + "package test;", + "", + "@interface NonQualifier {}"); + Compilation compilation = + daggerCompiler().compile(module, methodQualifier, paramQualifier, nonQualifier); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.MyModule_ProvideStringFactory") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.MyModule_ProvideStringFactory", + "package test;", + "", + "@ScopeMetadata", + "@QualifierMetadata({\"test.MethodQualifier\", \"test.ParamQualifier\"})", + GeneratedLines.generatedAnnotations(), + "public final class MyModule_ProvideStringFactory implements Factory<String> {}")); + } + private static final String BINDS_METHOD = "@Binds abstract Foo bindFoo(FooImpl impl);"; private static final String MULTIBINDS_METHOD = "@Multibinds abstract Set<Foo> foos();"; private static final String STATIC_PROVIDES_METHOD = diff --git a/javatests/dagger/internal/codegen/OptionalBindingRequestFulfillmentTest.java b/javatests/dagger/internal/codegen/OptionalBindingRequestFulfillmentTest.java index 9b4353042..c78d1995f 100644 --- a/javatests/dagger/internal/codegen/OptionalBindingRequestFulfillmentTest.java +++ b/javatests/dagger/internal/codegen/OptionalBindingRequestFulfillmentTest.java @@ -107,41 +107,42 @@ public class OptionalBindingRequestFulfillmentTest { .addLines( "package test;", "", - "import com.google.common.base.Optional;", - "", GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, - " private volatile Provider<Maybe> provideMaybeProvider;", + " private Provider<Maybe> provideMaybeProvider;", "", - " private Provider<Maybe> maybeProvider() {", - " Object local = provideMaybeProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " provideMaybeProvider = (Provider<Maybe>) local;", - " }", - " return (Provider<Maybe>) local;", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.provideMaybeProvider = new SwitchingProvider<>(testComponent, 0);", " }") - .addLines( + .addLinesIn( + DEFAULT_MODE, " @Override", " public Optional<Maybe> maybe() {", - " return Optional.of(", - " Maybe_MaybeModule_ProvideMaybeFactory.provideMaybe());", - " }", - "", + " return Optional.of(Maybe_MaybeModule_ProvideMaybeFactory.provideMaybe());", + " }") + .addLinesIn( + FAST_INIT_MODE, " @Override", - " public Optional<Provider<Lazy<Maybe>>> providerOfLazyOfMaybe() {", - " return Optional.of(ProviderOfLazy.create(") + " public Optional<Maybe> maybe() {", + " return Optional.of(provideMaybeProvider.get());", + " }") .addLinesIn( - DEFAULT_MODE, // - " Maybe_MaybeModule_ProvideMaybeFactory.create()));") + DEFAULT_MODE, + " @Override", + " public Optional<Provider<Lazy<Maybe>>> providerOfLazyOfMaybe() {", + " return Optional.of(ProviderOfLazy.create(", + " Maybe_MaybeModule_ProvideMaybeFactory.create()));", + " }") .addLinesIn( - FAST_INIT_MODE, // - " maybeProvider()));") + FAST_INIT_MODE, + " @Override", + " public Optional<Provider<Lazy<Maybe>>> providerOfLazyOfMaybe() {", + " return Optional.of(ProviderOfLazy.create(provideMaybeProvider));", + " }") .addLines( - " }", - "", " @Override", " public Optional<DefinitelyNot> definitelyNot() {", " return Optional.<DefinitelyNot>absent();", @@ -154,21 +155,13 @@ public class OptionalBindingRequestFulfillmentTest { " }") .addLinesIn( FAST_INIT_MODE, - " private final class SwitchingProvider<T> implements Provider<T> {", - " private final int id;", - "", - " SwitchingProvider(int id) {", - " this.id = id;", - " }", - "", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", - " case 0:", - " return (T) Maybe_MaybeModule_ProvideMaybeFactory.provideMaybe();", - " default:", - " throw new AssertionError(id);", + " case 0: return (T) Maybe_MaybeModule_ProvideMaybeFactory.provideMaybe();", + " default: throw new AssertionError(id);", " }", " }", " }", diff --git a/javatests/dagger/internal/codegen/ProductionComponentProcessorTest.java b/javatests/dagger/internal/codegen/ProductionComponentProcessorTest.java index 4043e768f..e0ad55abc 100644 --- a/javatests/dagger/internal/codegen/ProductionComponentProcessorTest.java +++ b/javatests/dagger/internal/codegen/ProductionComponentProcessorTest.java @@ -16,9 +16,9 @@ package dagger.internal.codegen; -import static com.google.common.truth.Truth.assertAbout; import static com.google.testing.compile.CompilationSubject.assertThat; -import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; +import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; +import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; import static dagger.internal.codegen.Compilers.daggerCompiler; @@ -224,321 +224,111 @@ public class ProductionComponentProcessorTest { " ListenableFuture<A> a();", " }", "}"); - JavaFileObject generatedComponent; - switch (compilerMode) { - case FAST_INIT_MODE: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerTestClass_SimpleComponent", - "package test;", - "", - GeneratedLines.generatedImports( - "import com.google.common.util.concurrent.ListenableFuture;", - "import dagger.internal.DoubleCheck;", - "import dagger.internal.InstanceFactory;", - "import dagger.internal.MemoizedSentinel;", - "import dagger.internal.Preconditions;", - "import dagger.internal.SetFactory;", - "import dagger.producers.Producer;", - "import dagger.producers.internal.CancellationListener;", - "import dagger.producers.internal.Producers;", - "import dagger.producers.monitoring.ProductionComponentMonitor;", - "import java.util.concurrent.Executor;", - "import javax.inject.Provider;"), - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestClass_SimpleComponent", - " implements TestClass.SimpleComponent, CancellationListener {", - " private final TestClass.BModule bModule;", - " private volatile Object productionImplementationExecutor =", - " new MemoizedSentinel();", - " private volatile Provider<Executor> productionImplementationExecutorProvider;", - " private volatile Object productionComponentMonitor = new MemoizedSentinel();", - " private volatile Provider<ProductionComponentMonitor> monitorProvider;", - " private volatile Provider<TestClass.B> bProvider;", - " private Producer<TestClass.A> aEntryPoint;", - " private Provider<TestClass.SimpleComponent> simpleComponentProvider;", - " private Producer<TestClass.B> bProducer;", - " private Producer<TestClass.A> aProducer;", - "", - " private DaggerTestClass_SimpleComponent(", - " TestClass.AModule aModuleParam,", - " TestClass.BModule bModuleParam) {", - " this.bModule = bModuleParam;", - " initialize(aModuleParam, bModuleParam);", - " }", - "", - " public static Builder builder() {", - " return new Builder();", - " }", - "", - " public static TestClass.SimpleComponent create() {", - " return new Builder().build();", - " }", - "", - " private Executor productionImplementationExecutor() {", - " Object local = productionImplementationExecutor;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = productionImplementationExecutor;", - " if (local instanceof MemoizedSentinel) {", - " local =", - " TestClass_BModule_ExecutorFactory.executor(bModule);", - " productionImplementationExecutor =", - " DoubleCheck.reentrantCheck(", - " productionImplementationExecutor, local);", - " }", - " }", - " }", - " return (Executor) local;", - " }", - "", - " private Provider<Executor> productionImplementationExecutorProvider() {", - " Object local = productionImplementationExecutorProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " productionImplementationExecutorProvider = (Provider<Executor>) local;", - " }", - " return (Provider<Executor>) local;", - " }", - "", - " private ProductionComponentMonitor productionComponentMonitor() {", - " Object local = productionComponentMonitor;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = productionComponentMonitor;", - " if (local instanceof MemoizedSentinel) {", - " local =", - " TestClass_SimpleComponent_MonitoringModule_MonitorFactory", - " .monitor(", - " simpleComponentProvider,", - " SetFactory.<ProductionComponentMonitor.Factory>empty());", - " productionComponentMonitor =", - " DoubleCheck.reentrantCheck(", - " productionComponentMonitor, local);", - " }", - " }", - " }", - " return (ProductionComponentMonitor) local;", - " }", - "", - " private Provider<ProductionComponentMonitor>", - " productionComponentMonitorProvider() {", - " Object local = monitorProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(1);", - " monitorProvider = (Provider<ProductionComponentMonitor>) local;", - " }", - " return (Provider<ProductionComponentMonitor>) local;", - " }", - "", - " private TestClass.B b() {", - " return TestClass_BModule_BFactory.b(bModule, new TestClass.C());", - " }", - "", - " private Provider<TestClass.B> bProvider() {", - " Object local = bProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(2);", - " bProvider = (Provider<TestClass.B>) local;", - " }", - " return (Provider<TestClass.B>) local;", - " }", - "", - " @SuppressWarnings(\"unchecked\")", - " private void initialize(", - " final TestClass.AModule aModuleParam,", - " final TestClass.BModule bModuleParam) {", - " this.simpleComponentProvider =", - " InstanceFactory.create((TestClass.SimpleComponent) this);", - " this.bProducer = Producers.producerFromProvider(bProvider());", - " this.aProducer =", - " TestClass_AModule_AFactory.create(", - " aModuleParam,", - " productionImplementationExecutorProvider(),", - " productionComponentMonitorProvider(),", - " bProducer);", - " this.aEntryPoint = Producers.entryPointViewOf(aProducer, this);", - " }", - "", - " @Override", - " public ListenableFuture<TestClass.A> a() {", - " return aEntryPoint.get();", - " }", - "", - " @Override", - " public void onProducerFutureCancelled(boolean mayInterruptIfRunning) {", - " Producers.cancel(aProducer, mayInterruptIfRunning);", - " Producers.cancel(bProducer, mayInterruptIfRunning);", - " }", - "", - " static final class Builder {", - " private TestClass.AModule aModule;", - " private TestClass.BModule bModule;", - "", - " private Builder() {}", - "", - " public Builder aModule(TestClass.AModule aModule) {", - " this.aModule = Preconditions.checkNotNull(aModule);", - " return this;", - " }", - "", - " public Builder bModule(TestClass.BModule bModule) {", - " this.bModule = Preconditions.checkNotNull(bModule);", - " return this;", - " }", - "", - " public TestClass.SimpleComponent build() {", - " if (aModule == null) {", - " this.aModule = new TestClass.AModule();", - " }", - " if (bModule == null) {", - " this.bModule = new TestClass.BModule();", - " }", - " return new DaggerTestClass_SimpleComponent(aModule, bModule);", - " }", - " }", - "", - " private final class SwitchingProvider<T> implements Provider<T> {", - " private final int id;", - "", - " SwitchingProvider(int id) {", - " this.id = id;", - " }", - "", - " @SuppressWarnings(\"unchecked\")", - " @Override", - " public T get() {", - " switch (id) {", - " case 0: return (T) DaggerTestClass_SimpleComponent.this", - " .productionImplementationExecutor();", - " case 1: return (T)", - " DaggerTestClass_SimpleComponent.this.productionComponentMonitor();", - " case 2: return (T)", - " DaggerTestClass_SimpleComponent.this.b();", - " default: throw new AssertionError(id);", - " }", - " }", - " }", - "}"); - break; - default: - generatedComponent = - JavaFileObjects.forSourceLines( - "test.DaggerTestClass_SimpleComponent", - "package test;", - "", - GeneratedLines.generatedImports( - "import com.google.common.util.concurrent.ListenableFuture;", - "import dagger.internal.DoubleCheck;", - "import dagger.internal.InstanceFactory;", - "import dagger.internal.Preconditions;", - "import dagger.internal.SetFactory;", - "import dagger.producers.Producer;", - "import dagger.producers.internal.CancellationListener;", - "import dagger.producers.internal.Producers;", - "import dagger.producers.monitoring.ProductionComponentMonitor;", - "import java.util.concurrent.Executor;", - "import javax.inject.Provider;"), - "", - GeneratedLines.generatedAnnotations(), - "final class DaggerTestClass_SimpleComponent", - " implements TestClass.SimpleComponent, CancellationListener {", - " private Producer<TestClass.A> aEntryPoint;", - " private Provider<Executor> executorProvider;", - " private Provider<Executor> productionImplementationExecutorProvider;", - " private Provider<TestClass.SimpleComponent> simpleComponentProvider;", - " private Provider<ProductionComponentMonitor> monitorProvider;", - " private Provider<TestClass.B> bProvider;", - " private Producer<TestClass.B> bProducer;", - " private Producer<TestClass.A> aProducer;", - "", - " private DaggerTestClass_SimpleComponent(", - " TestClass.AModule aModuleParam,", - " TestClass.BModule bModuleParam) {", - " initialize(aModuleParam, bModuleParam);", - " }", - "", - " public static Builder builder() {", - " return new Builder();", - " }", - "", - " public static TestClass.SimpleComponent create() {", - " return new Builder().build();", - " }", - "", - " @SuppressWarnings(\"unchecked\")", - " private void initialize(", - " final TestClass.AModule aModuleParam,", - " final TestClass.BModule bModuleParam) {", - " this.executorProvider =", - " TestClass_BModule_ExecutorFactory.create(bModuleParam);", - " this.productionImplementationExecutorProvider =", - " DoubleCheck.provider((Provider) executorProvider);", - " this.simpleComponentProvider = ", - " InstanceFactory.create((TestClass.SimpleComponent) this);", - " this.monitorProvider =", - " DoubleCheck.provider(", - " TestClass_SimpleComponent_MonitoringModule_MonitorFactory.create(", - " simpleComponentProvider,", - " SetFactory.<ProductionComponentMonitor.Factory>empty()));", - " this.bProvider = TestClass_BModule_BFactory.create(", - " bModuleParam, TestClass_C_Factory.create());", - " this.bProducer = Producers.producerFromProvider(bProvider);", - " this.aProducer = TestClass_AModule_AFactory.create(", - " aModuleParam,", - " productionImplementationExecutorProvider,", - " monitorProvider,", - " bProducer);", - " this.aEntryPoint = Producers.entryPointViewOf(aProducer, this);", - " }", - "", - " @Override", - " public ListenableFuture<TestClass.A> a() {", - " return aEntryPoint.get();", - " }", - "", - " @Override", - " public void onProducerFutureCancelled(boolean mayInterruptIfRunning) {", - " Producers.cancel(aProducer, mayInterruptIfRunning);", - " Producers.cancel(bProducer, mayInterruptIfRunning);", - " }", - "", - " static final class Builder {", - " private TestClass.AModule aModule;", - " private TestClass.BModule bModule;", - "", - " private Builder() {}", - "", - " public Builder aModule(TestClass.AModule aModule) {", - " this.aModule = Preconditions.checkNotNull(aModule);", - " return this;", - " }", - "", - " public Builder bModule(TestClass.BModule bModule) {", - " this.bModule = Preconditions.checkNotNull(bModule);", - " return this;", - " }", - "", - " public TestClass.SimpleComponent build() {", - " if (aModule == null) {", - " this.aModule = new TestClass.AModule();", - " }", - " if (bModule == null) {", - " this.bModule = new TestClass.BModule();", - " }", - " return new DaggerTestClass_SimpleComponent(aModule, bModule);", - " }", - " }", - "}"); - } - assertAbout(javaSource()) - .that(component) - .withCompilerOptions(compilerMode.javacopts()) - .processedWith(new ComponentProcessor()) - .compilesWithoutError() - .and() - .generatesSources(generatedComponent); + + Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(component); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test.DaggerTestClass_SimpleComponent") + .containsElementsIn( + compilerMode + .javaFileBuilder("test.DaggerTestClass_SimpleComponent") + .addLines( + "package test;", + "", + GeneratedLines.generatedAnnotations(), + "final class DaggerTestClass_SimpleComponent", + " implements TestClass.SimpleComponent, CancellationListener {", + " private Producer<TestClass.A> aEntryPoint;", + " private Provider<Executor> executorProvider;", + " private Provider<Executor> productionImplementationExecutorProvider;", + " private Provider<TestClass.SimpleComponent> simpleComponentProvider;", + " private Provider<ProductionComponentMonitor> monitorProvider;", + " private Provider<TestClass.B> bProvider;", + " private Producer<TestClass.B> bProducer;", + " private Producer<TestClass.A> aProducer;") + .addLinesIn( + DEFAULT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize(", + " final TestClass.AModule aModuleParam,", + " final TestClass.BModule bModuleParam) {", + " this.executorProvider =", + " TestClass_BModule_ExecutorFactory.create(bModuleParam);", + " this.productionImplementationExecutorProvider =", + " DoubleCheck.provider((Provider) executorProvider);", + " this.simpleComponentProvider = ", + " InstanceFactory.create((TestClass.SimpleComponent) simpleComponent);", + " this.monitorProvider =", + " DoubleCheck.provider(", + " TestClass_SimpleComponent_MonitoringModule_MonitorFactory.create(", + " simpleComponentProvider,", + " SetFactory.<ProductionComponentMonitor.Factory>empty()));", + " this.bProvider = TestClass_BModule_BFactory.create(", + " bModuleParam, TestClass_C_Factory.create());", + " this.bProducer = Producers.producerFromProvider(bProvider);", + " this.aProducer = TestClass_AModule_AFactory.create(", + " aModuleParam,", + " productionImplementationExecutorProvider,", + " monitorProvider,", + " bProducer);", + " this.aEntryPoint = Producers.entryPointViewOf(aProducer, this);", + " }") + .addLinesIn( + FAST_INIT_MODE, + " @SuppressWarnings(\"unchecked\")", + " private void initialize(", + " final TestClass.AModule aModuleParam,", + " final TestClass.BModule bModuleParam) {", + " this.executorProvider = new SwitchingProvider<>(simpleComponent, 0);", + " this.productionImplementationExecutorProvider =", + " DoubleCheck.provider((Provider) executorProvider);", + " this.simpleComponentProvider =", + " InstanceFactory.create((TestClass.SimpleComponent) simpleComponent);", + " this.monitorProvider = DoubleCheck.provider(", + " new SwitchingProvider<ProductionComponentMonitor>(", + " simpleComponent, 1));", + " this.bProvider = new SwitchingProvider<>(simpleComponent, 2);", + " this.bProducer = Producers.producerFromProvider(bProvider);", + " this.aProducer = TestClass_AModule_AFactory.create(", + " aModuleParam,", + " productionImplementationExecutorProvider,", + " monitorProvider,", + " bProducer);", + " this.aEntryPoint = Producers.entryPointViewOf(aProducer, this);", + " }") + .addLines( + " @Override", + " public ListenableFuture<TestClass.A> a() {", + " return aEntryPoint.get();", + " }", + "", + " @Override", + " public void onProducerFutureCancelled(boolean mayInterruptIfRunning) {", + " Producers.cancel(aProducer, mayInterruptIfRunning);", + " Producers.cancel(bProducer, mayInterruptIfRunning);", + " }") + .addLinesIn( + FAST_INIT_MODE, + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0: return (T) TestClass_BModule_ExecutorFactory.executor(", + " simpleComponent.bModule);", + " case 1: return (T)", + " TestClass_SimpleComponent_MonitoringModule_MonitorFactory", + " .monitor(", + " simpleComponent.simpleComponentProvider,", + " SetFactory.<ProductionComponentMonitor.Factory>empty());", + " case 2: return (T) TestClass_BModule_BFactory.b(", + " simpleComponent.bModule, new TestClass.C());", + " default: throw new AssertionError(id);", + " }", + " }", + " }", + "}") + .build()); } @Test public void nullableProducersAreNotErrors() { @@ -648,20 +438,16 @@ public class ProductionComponentProcessorTest { new JavaFileBuilder(compilerMode, "test.DaggerRoot") .addLines( "package test;", + "", GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent, CancellationListener {", - " private final class ChildImpl implements Child, CancellationListener {", + " private static final class ChildImpl", + " implements Child, CancellationListener {", " @Override", - " public ProductionScoped productionScoped() {") - .addLinesIn( - CompilerMode.DEFAULT_MODE, // - " return DaggerParent.this.productionScopedProvider.get();") - .addLinesIn( - CompilerMode.FAST_INIT_MODE, // - " return DaggerParent.this.productionScoped();") - .addLines( - " }", // - " }", // + " public ProductionScoped productionScoped() {", + " return parent.productionScopedProvider.get();", + " }", + " }", "}") .build()); } diff --git a/javatests/dagger/internal/codegen/RawTypeInjectionTest.java b/javatests/dagger/internal/codegen/RawTypeInjectionTest.java new file mode 100644 index 000000000..62eaea45d --- /dev/null +++ b/javatests/dagger/internal/codegen/RawTypeInjectionTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2021 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.testing.compile.CompilationSubject.assertThat; +import static dagger.internal.codegen.Compilers.daggerCompiler; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RawTypeInjectionTest { + @Test + public void rawEntryPointTest() { + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component", + "interface TestComponent {", + " Foo foo();", // Fail: requesting raw type + "}"); + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Foo<T> {", + " @Inject Foo() {}", + "}"); + + Compilation compilation = daggerCompiler().compile(component, foo); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("Foo cannot be provided without an @Provides-annotated method.") + .inFile(component) + .onLine(6); + } + + @Test + public void rawProvidesRequestTest() { + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " int integer();", + "}"); + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Foo<T> {", + " @Inject Foo() {}", + "}"); + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "", + "@Module", + "class TestModule {", + " @Provides", + " int provideFoo(Foo foo) {", // Fail: requesting raw type + " return 0;", + " }", + "}"); + + + Compilation compilation = daggerCompiler().compile(component, foo, module); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("Foo cannot be provided without an @Provides-annotated method.") + .inFile(component) + .onLine(6); + } + + @Test + public void rawInjectConstructorRequestTest() { + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component", + "interface TestComponent {", + " Foo foo();", + "}"); + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Foo<T> {", + " @Inject Foo() {}", + "}"); + JavaFileObject bar = + JavaFileObjects.forSourceLines( + "test.Bar", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Bar {", + " @Inject Bar(Foo foo) {}", // Fail: requesting raw type + "}"); + + + Compilation compilation = daggerCompiler().compile(component, foo, bar); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining("Foo cannot be provided without an @Provides-annotated method.") + .inFile(component) + .onLine(6); + } + + @Test + public void rawProvidesReturnTest() { + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + // Test that we can request the raw type if it's provided by a module. + " Foo foo();", + "}"); + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Foo<T> {", + " @Inject Foo() {}", + "}"); + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "", + "@Module", + "class TestModule {", + // Test that Foo<T> can still be requested and is independent of Foo (otherwise we'd + // get a cyclic dependency error). + " @Provides", + " Foo provideFoo(Foo<Integer> fooInteger) {", + " return fooInteger;", + " }", + "", + " @Provides", + " int provideInt() {", + " return 0;", + " }", + "}"); + + Compilation compilation = daggerCompiler().compile(component, foo, module); + assertThat(compilation).succeeded(); + } +} diff --git a/javatests/dagger/internal/codegen/ScopingValidationTest.java b/javatests/dagger/internal/codegen/ScopingValidationTest.java index 58ab2af44..740a44594 100644 --- a/javatests/dagger/internal/codegen/ScopingValidationTest.java +++ b/javatests/dagger/internal/codegen/ScopingValidationTest.java @@ -79,6 +79,9 @@ public class ScopingValidationTest { message( "MyComponent (unscoped) may not reference scoped bindings:", " @Singleton class ScopedType", + " ScopedType is requested at", + " MyComponent.string()", + "", " @Provides @Singleton String ScopedModule.string()")); } @@ -236,7 +239,11 @@ public class ScopingValidationTest { "MyComponent scoped with @Singleton " + "may not reference bindings with different scopes:", " @PerTest class ScopedType", + " ScopedType is requested at", + " MyComponent.string()", + "", " @Provides @PerTest String ScopedModule.string()", + "", " @Provides @Per(MyComponent.class) boolean " + "ScopedModule.bool()")) .inFile(componentFile) @@ -251,7 +258,9 @@ public class ScopingValidationTest { message( "ScopedModule contains bindings with different scopes:", " @Provides @PerTest String ScopedModule.string()", + "", " @Provides @Singleton float ScopedModule.floatingPoint()", + "", " @Provides @Per(MyComponent.class) boolean " + "ScopedModule.bool()")) .inFile(moduleFile) diff --git a/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentTest.java b/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentTest.java index a343a601d..e1c49d6ad 100644 --- a/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentTest.java +++ b/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentTest.java @@ -266,6 +266,8 @@ public class SetBindingRequestFulfillmentTest { "", GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", + " private final DaggerParent parent = this;", + "", " private DaggerParent() {}", "", " public static Builder builder() {", @@ -278,7 +280,7 @@ public class SetBindingRequestFulfillmentTest { "", " @Override", " public Child child() {", - " return new ChildImpl();", + " return new ChildImpl(parent);", " }", "", " static final class Builder {", @@ -295,8 +297,14 @@ public class SetBindingRequestFulfillmentTest { " }", " }", "", - " private final class ChildImpl implements Child {", - " private ChildImpl() {}", + " private static final class ChildImpl implements Child {", + " private final DaggerParent parent;", + "", + " private final ChildImpl childImpl = this;", + "", + " private ChildImpl(DaggerParent parent) {", + " this.parent = parent;", + " }", "", " @Override", " public Set<Object> objectSet() {", diff --git a/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentWithGuavaTest.java b/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentWithGuavaTest.java index a08113023..6e6236c99 100644 --- a/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentWithGuavaTest.java +++ b/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentWithGuavaTest.java @@ -272,7 +272,7 @@ public class SetBindingRequestFulfillmentWithGuavaTest { "", GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", - " private final class ChildImpl implements Child {", + " private static final class ChildImpl implements Child {", " @Override", " public Set<Object> objectSet() {", " return ImmutableSet.<Object>of(", @@ -330,8 +330,10 @@ public class SetBindingRequestFulfillmentWithGuavaTest { "import java.util.Set;"), "", GeneratedLines.generatedAnnotations(), - "final class DaggerTestComponent implements TestComponent, " - + "CancellationListener {", + "final class DaggerTestComponent implements TestComponent, ", + " CancellationListener {", + " private final DaggerTestComponent testComponent = this;", + "", " private DaggerTestComponent() {}", "", " public static Builder builder() {", diff --git a/javatests/dagger/internal/codegen/SubcomponentBuilderValidationTest.java b/javatests/dagger/internal/codegen/SubcomponentBuilderValidationTest.java index 0fd7e084d..2b3c49f51 100644 --- a/javatests/dagger/internal/codegen/SubcomponentBuilderValidationTest.java +++ b/javatests/dagger/internal/codegen/SubcomponentBuilderValidationTest.java @@ -18,7 +18,7 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.Compilers.daggerCompiler; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.SUBCOMPONENT_BUILDER; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.SUBCOMPONENT_BUILDER; import static dagger.internal.codegen.binding.ErrorMessages.creatorMessagesFor; import com.google.testing.compile.Compilation; diff --git a/javatests/dagger/internal/codegen/SubcomponentCreatorRequestFulfillmentTest.java b/javatests/dagger/internal/codegen/SubcomponentCreatorRequestFulfillmentTest.java index 32ae245dd..245b051c8 100644 --- a/javatests/dagger/internal/codegen/SubcomponentCreatorRequestFulfillmentTest.java +++ b/javatests/dagger/internal/codegen/SubcomponentCreatorRequestFulfillmentTest.java @@ -21,13 +21,13 @@ import static com.google.common.collect.Sets.immutableEnumSet; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.SUBCOMPONENT_BUILDER; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.SUBCOMPONENT_FACTORY; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.SUBCOMPONENT_BUILDER; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.SUBCOMPONENT_FACTORY; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.testing.compile.Compilation; -import dagger.internal.codegen.binding.ComponentCreatorAnnotation; +import dagger.internal.codegen.base.ComponentCreatorAnnotation; import java.util.Collection; import java.util.List; import java.util.Set; @@ -103,23 +103,25 @@ public class SubcomponentCreatorRequestFulfillmentTest extends ComponentCreatorT "final class DaggerC implements C {", " @Override", " public Sub.Builder sBuilder() {", - " return new SubBuilder();", + " return new SubBuilder(c);", " }", "", " @Override", " public UsesSubcomponent usesSubcomponent() {", - " return new UsesSubcomponent(new SubBuilder());", + " return new UsesSubcomponent(new SubBuilder(c));", " }", "", - " private final class SubBuilder implements Sub.Builder {", + " private static final class SubBuilder implements Sub.Builder {", " @Override", " public Sub build() {", - " return new SubImpl();", + " return new SubImpl(c);", " }", " }", "", - " private final class SubImpl implements Sub {", - " private SubImpl() {}", + " private static final class SubImpl implements Sub {", + " private SubImpl(DaggerC c) {", + " this.c = c;", + " }", " }", "}"); diff --git a/javatests/dagger/internal/codegen/SubcomponentCreatorValidationTest.java b/javatests/dagger/internal/codegen/SubcomponentCreatorValidationTest.java index 371253a81..f9b6e5895 100644 --- a/javatests/dagger/internal/codegen/SubcomponentCreatorValidationTest.java +++ b/javatests/dagger/internal/codegen/SubcomponentCreatorValidationTest.java @@ -19,18 +19,18 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; import static dagger.internal.codegen.TestUtils.message; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.SUBCOMPONENT_BUILDER; -import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.SUBCOMPONENT_FACTORY; -import static dagger.internal.codegen.binding.ComponentCreatorKind.BUILDER; -import static dagger.internal.codegen.binding.ComponentCreatorKind.FACTORY; -import static dagger.internal.codegen.binding.ComponentKind.SUBCOMPONENT; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.SUBCOMPONENT_BUILDER; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.SUBCOMPONENT_FACTORY; +import static dagger.internal.codegen.base.ComponentCreatorKind.BUILDER; +import static dagger.internal.codegen.base.ComponentCreatorKind.FACTORY; +import static dagger.internal.codegen.base.ComponentKind.SUBCOMPONENT; import static dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages.moreThanOneRefToSubcomponent; import static dagger.internal.codegen.binding.ErrorMessages.componentMessagesFor; import com.google.common.collect.ImmutableList; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; -import dagger.internal.codegen.binding.ComponentCreatorAnnotation; +import dagger.internal.codegen.base.ComponentCreatorAnnotation; import java.util.Collection; import javax.tools.JavaFileObject; import org.junit.Test; @@ -52,17 +52,19 @@ public class SubcomponentCreatorValidationTest extends ComponentCreatorTestHelpe @Test public void testRefSubcomponentAndSubCreatorFails() { - JavaFileObject componentFile = preprocessedJavaFile("test.ParentComponent", - "package test;", - "", - "import dagger.Component;", - "import javax.inject.Provider;", - "", - "@Component", - "interface ParentComponent {", - " ChildComponent child();", - " ChildComponent.Builder builder();", - "}"); + JavaFileObject componentFile = + preprocessedJavaFile( + "test.ParentComponent", + "package test;", + "", + "import dagger.Component;", + "import javax.inject.Provider;", + "", + "@Component", + "interface ParentComponent {", + " ChildComponent child();", + " ChildComponent.Builder childComponentBuilder();", + "}"); JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent", "package test;", "", @@ -82,7 +84,7 @@ public class SubcomponentCreatorValidationTest extends ComponentCreatorTestHelpe String.format( moreThanOneRefToSubcomponent(), "test.ChildComponent", - process("[child(), builder()]"))) + process("[child(), childComponentBuilder()]"))) .inFile(componentFile); } @@ -244,16 +246,18 @@ public class SubcomponentCreatorValidationTest extends ComponentCreatorTestHelpe @Test public void testCreatorMissingFactoryMethodFails() { - JavaFileObject componentFile = preprocessedJavaFile("test.ParentComponent", - "package test;", - "", - "import dagger.Component;", - "import javax.inject.Provider;", - "", - "@Component", - "interface ParentComponent {", - " ChildComponent.Builder builder();", - "}"); + JavaFileObject componentFile = + preprocessedJavaFile( + "test.ParentComponent", + "package test;", + "", + "import dagger.Component;", + "import javax.inject.Provider;", + "", + "@Component", + "interface ParentComponent {", + " ChildComponent.Builder childComponentBuilder();", + "}"); JavaFileObject childComponentFile = preprocessedJavaFile("test.ChildComponent", "package test;", "", @@ -726,8 +730,7 @@ public class SubcomponentCreatorValidationTest extends ComponentCreatorTestHelpe " interface Factory {", " ChildComponent create(String s, Integer i);", " }") - .addLines( // - "}") + .addLines("}") .build(); Compilation compilation = compile(componentFile, childComponentFile); assertThat(compilation).failed(); diff --git a/javatests/dagger/internal/codegen/SubcomponentValidationTest.java b/javatests/dagger/internal/codegen/SubcomponentValidationTest.java index c63522e88..0a24043c2 100644 --- a/javatests/dagger/internal/codegen/SubcomponentValidationTest.java +++ b/javatests/dagger/internal/codegen/SubcomponentValidationTest.java @@ -222,7 +222,7 @@ public class SubcomponentValidationTest { assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( - "A module may only occur once an an argument in a Subcomponent factory method, " + "A module may only occur once as an argument in a Subcomponent factory method, " + "but test.TestModule was already passed.") .inFile(componentFile) .onLine(7) @@ -456,7 +456,9 @@ public class SubcomponentValidationTest { "package test;", "", GeneratedLines.generatedAnnotations(), - "final class DaggerParentComponent implements ParentComponent {") + "final class DaggerParentComponent implements ParentComponent {", + " private Provider<Dep1> dep1Provider;", + " private Provider<Dep2> dep2Provider;") .addLinesIn( DEFAULT_MODE, " @SuppressWarnings(\"unchecked\")", @@ -465,54 +467,30 @@ public class SubcomponentValidationTest { " this.dep2Provider = DoubleCheck.provider(Dep2_Factory.create());", " }", "") - .addLines( - " @Override", // - " public Dep1 dep1() {") .addLinesIn( FAST_INIT_MODE, - " Object local = dep1;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = dep1;", - " if (local instanceof MemoizedSentinel) {", - " local = injectDep1(Dep1_Factory.newInstance());", - " dep1 = DoubleCheck.reentrantCheck(dep1, local);", - " }", - " }", - " }", - " return (Dep1) local;") - .addLinesIn( - DEFAULT_MODE, // - " return dep1Provider.get();") + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.dep1Provider = DoubleCheck.provider(", + " new SwitchingProvider<Dep1>(parentComponent, 0));", + " this.dep2Provider = DoubleCheck.provider(", + " new SwitchingProvider<Dep2>(parentComponent, 1));", + " }") .addLines( - " }", // + " @Override", + " public Dep1 dep1() {", + " return dep1Provider.get();", + " }", "", " @Override", - " public Dep2 dep2() {") - .addLinesIn( - FAST_INIT_MODE, - " Object local = dep2;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = dep2;", - " if (local instanceof MemoizedSentinel) {", - " local = injectDep2(Dep2_Factory.newInstance());", - " dep2 = DoubleCheck.reentrantCheck(dep2, local);", - " }", - " }", - " }", - " return (Dep2) local;") - .addLinesIn( - DEFAULT_MODE, // - " return dep2Provider.get();") - .addLines( + " public Dep2 dep2() {", + " return dep2Provider.get();", " }", "", " @Override", " public ChildComponent childComponent() {", - " return new ChildComponentImpl();", - " }", - "") + " return new ChildComponentImpl(parentComponent);", + " }") .addLinesIn( FAST_INIT_MODE, " @CanIgnoreReturnValue", @@ -527,38 +505,17 @@ public class SubcomponentValidationTest { " return instance;", " }") .addLines( - "", - " private final class ChildComponentImpl implements ChildComponent {", - " private final ChildModule childModule;", - "", - " private ChildComponentImpl() {", - " this.childModule = new ChildModule();", - " }", - "") - .addLinesIn( - DEFAULT_MODE, + " private static final class ChildComponentImpl implements ChildComponent {", " private NeedsDep1 needsDep1() {", - " return new NeedsDep1(DaggerParentComponent.this.dep1Provider.get());", - " }") - .addLinesIn( - FAST_INIT_MODE, - " private NeedsDep1 needsDep1() {", - " return new NeedsDep1(DaggerParentComponent.this.dep1());", - " }") - .addLines( + " return new NeedsDep1(parentComponent.dep1Provider.get());", + " }", + "", " private A a() {", " return injectA(", " A_Factory.newInstance(", - " needsDep1(),") - .addLinesIn( - DEFAULT_MODE, - " DaggerParentComponent.this.dep1Provider.get(),", - " DaggerParentComponent.this.dep2Provider.get()));") - .addLinesIn( - FAST_INIT_MODE, - " DaggerParentComponent.this.dep1(),", - " DaggerParentComponent.this.dep2()));") - .addLines( + " needsDep1(),", + " parentComponent.dep1Provider.get(),", + " parentComponent.dep2Provider.get()));", " }", "", " @Override", @@ -572,6 +529,21 @@ public class SubcomponentValidationTest { " A_MembersInjector.injectMethodA(instance);", " return instance;", " }", + " }") + .addLinesIn( + FAST_INIT_MODE, + " private static final class SwitchingProvider<T> implements Provider<T> {", + " @SuppressWarnings(\"unchecked\")", + " @Override", + " public T get() {", + " switch (id) {", + " case 0:", + " return (T) parentComponent.injectDep1(Dep1_Factory.newInstance());", + " case 1:", + " return (T) parentComponent.injectDep2(Dep2_Factory.newInstance());", + " default: throw new AssertionError(id);", + " }", + " }", " }", "}") .build(); @@ -658,12 +630,12 @@ public class SubcomponentValidationTest { "final class DaggerParentComponent implements ParentComponent {", " @Override", " public Foo.Sub newInstanceSubcomponent() {", - " return new F_SubImpl();", + " return new F_SubImpl(parentComponent);", " }", "", " @Override", " public NoConflict newNoConflictSubcomponent() {", - " return new NoConflictImpl();", + " return new NoConflictImpl(parentComponent);", " }", "", " static final class Builder {", @@ -672,23 +644,13 @@ public class SubcomponentValidationTest { " }", " }", "", - " private final class F_SubImpl implements Foo.Sub {", - " @Override", - " public Bar.Sub newBarSubcomponent() {", - " return new B_SubImpl();", - " }", + " private static final class ts_SubImpl implements Sub {}", "", - " private final class B_SubImpl implements Bar.Sub {", - " @Override", - " public Sub newSubcomponentInSubpackage() {", - " return new ts_SubI();", - " }", + " private static final class B_SubImpl implements Bar.Sub {}", "", - " private final class ts_SubI implements Sub {}", - " }", - " }", + " private static final class F_SubImpl implements Foo.Sub {}", "", - " private final class NoConflictImpl implements NoConflict {}", + " private static final class NoConflictImpl implements NoConflict {}", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()) @@ -740,7 +702,7 @@ public class SubcomponentValidationTest { "final class DaggerParentComponent implements ParentComponent {", " @Override", " public Sub newSubcomponent() {", - " return new t_SubImpl();", + " return new t_SubImpl(parentComponent);", " }", "", " static final class Builder {", @@ -749,17 +711,10 @@ public class SubcomponentValidationTest { " }", " }", "", - " private final class t_SubImpl implements Sub {", - " @Override", - " public test.deep.many.levels.that.match.test.Sub newDeepSubcomponent() {", - " return new tdmltmt_SubImpl();", - " }", + " private static final class tdmltmt_SubImpl", + " implements test.deep.many.levels.that.match.test.Sub {}", "", - " private final class tdmltmt_SubImpl implements ", - " test.deep.many.levels.that.match.test.Sub {", - " private tdmltmt_SubImpl() {}", - " }", - " }", + " private static final class t_SubImpl implements Sub {}", "}"); Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(parent, sub, deepSub); @@ -805,22 +760,13 @@ public class SubcomponentValidationTest { "final class DaggerParentComponent implements ParentComponent {", " @Override", " public Sub newSubcomponent() {", - " return new $_SubImpl();", + " return new $_SubImpl(parentComponent);", " }", "", - " private final class $_SubImpl implements Sub {", - " private $_SubImpl() {}", - "", - " @Override", - " public test.deep.many.levels.that.match.test.Sub newDeepSubcomponent() {", - " return new tdmltmt_SubImpl();", - " }", + " private static final class tdmltmt_SubImpl", + " implements test.deep.many.levels.that.match.test.Sub {}", "", - " private final class tdmltmt_SubImpl implements ", - " test.deep.many.levels.that.match.test.Sub {", - " private tdmltmt_SubImpl() {}", - " }", - " }", + " private static final class $_SubImpl implements Sub {}", "}", ""); @@ -882,19 +828,23 @@ public class SubcomponentValidationTest { "final class DaggerParentComponent implements ParentComponent {", " @Override", " public E.F.Sub top1() {", - " return new F_SubImpl();", + " return new F_SubImpl(parentComponent);", " }", "", " @Override", " public top2.a.b.c.d.E.F.Sub top2() {", - " return new F2_SubImpl();", + " return new F2_SubImpl(parentComponent);", " }", "", - " private final class F_SubImpl implements E.F.Sub {", - " private F_SubImpl() {}", + " private static final class F_SubImpl implements E.F.Sub {", + " private F_SubImpl(DaggerParentComponent parentComponent) {", + " this.parentComponent = parentComponent;", + " }", " }", - " private final class F2_SubImpl implements top2.a.b.c.d.E.F.Sub {", - " private F2_SubImpl() {}", + " private static final class F2_SubImpl implements top2.a.b.c.d.E.F.Sub {", + " private F2_SubImpl(DaggerParentComponent parentComponent) {", + " this.parentComponent = parentComponent;", + " }", " }", "}"); @@ -939,10 +889,10 @@ public class SubcomponentValidationTest { "final class DaggerC implements C {", " @Override", " public Foo.C newInstanceC() {", - " return new F_CImpl();", + " return new F_CImpl(c);", " }", "", - " private final class F_CImpl implements Foo.C {}", + " private static final class F_CImpl implements Foo.C {}", "}"); Compilation compilation = @@ -1000,36 +950,40 @@ public class SubcomponentValidationTest { "final class DaggerC implements C {", " @Override", " public C.Foo.Sub.Builder fooBuilder() {", - " return new F_SubBuilder();", + " return new F_SubBuilder(c);", " }", "", " @Override", " public C.Bar.Sub.Builder barBuilder() {", - " return new B_SubBuilder();", + " return new B_SubBuilder(c);", " }", "", // TODO(bcorso): Reverse the order of subcomponent and builder so that subcomponent // comes first. - " private final class F_SubBuilder implements C.Foo.Sub.Builder {", + " private static final class F_SubBuilder implements C.Foo.Sub.Builder {", " @Override", " public C.Foo.Sub build() {", - " return new F_SubImpl();", + " return new F_SubImpl(c);", " }", " }", "", - " private final class F_SubImpl implements C.Foo.Sub {", - " private F_SubImpl() {}", - " }", - "", - " private final class B_SubBuilder implements C.Bar.Sub.Builder {", + " private static final class B_SubBuilder implements C.Bar.Sub.Builder {", " @Override", " public C.Bar.Sub build() {", - " return new B_SubImpl();", + " return new B_SubImpl(c);", " }", " }", "", - " private final class B_SubImpl implements C.Bar.Sub {", - " private B_SubImpl() {}", + " private static final class F_SubImpl implements C.Foo.Sub {", + " private F_SubImpl(DaggerC c) {", + " this.c = c;", + " }", + " }", + "", + " private static final class B_SubImpl implements C.Bar.Sub {", + " private B_SubImpl(DaggerC c) {", + " this.c = c;", + " }", " }", "}"); Compilation compilation = diff --git a/javatests/dagger/internal/codegen/SwitchingProviderTest.java b/javatests/dagger/internal/codegen/SwitchingProviderTest.java index 615b09dbf..fb0f7e501 100644 --- a/javatests/dagger/internal/codegen/SwitchingProviderTest.java +++ b/javatests/dagger/internal/codegen/SwitchingProviderTest.java @@ -17,7 +17,7 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; -import static com.google.testing.compile.Compiler.javac; +import static dagger.internal.codegen.Compilers.compilerWithOptions; import com.google.common.collect.ImmutableList; import com.google.testing.compile.Compilation; @@ -69,7 +69,7 @@ public class SwitchingProviderTest { "package test;", GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", - " private final class SwitchingProvider<T> implements Provider<T> {", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " private T get0() {", " switch (id) {", @@ -249,36 +249,30 @@ public class SwitchingProviderTest { "", GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", - " private volatile Provider<String> sProvider;", + " private Provider<String> sProvider;", "", - " private Provider<String> stringProvider() {", - " Object local = sProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " sProvider = (Provider<String>) local;", - " }", - " return (Provider<String>) local;", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.sProvider = new SwitchingProvider<>(testComponent, 0);", " }", "", " @Override", " public Provider<Object> objectProvider() {", - " return (Provider) stringProvider();", + " return ((Provider) sProvider);", " }", "", " @Override", " public Provider<CharSequence> charSequenceProvider() {", - " return (Provider) stringProvider();", + " return ((Provider) sProvider);", " }", "", - " private final class SwitchingProvider<T> implements Provider<T> {", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", - " case 0:", - " return (T) TestModule_SFactory.s();", - " default:", - " throw new AssertionError(id);", + " case 0: return (T) TestModule_SFactory.s();", + " default: throw new AssertionError(id);", " }", " }", " }", @@ -334,47 +328,32 @@ public class SwitchingProviderTest { "", GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", - " private volatile Object charSequence = new MemoizedSentinel();", - " private volatile Provider<CharSequence> cProvider;", + " private Provider<String> sProvider;", + " private Provider<CharSequence> cProvider;", "", - " private CharSequence charSequence() {", - " Object local = charSequence;", - " if (local instanceof MemoizedSentinel) {", - " synchronized (local) {", - " local = charSequence;", - " if (local instanceof MemoizedSentinel) {", - " local = TestModule_SFactory.s();", - " charSequence = DoubleCheck.reentrantCheck(charSequence, local);", - " }", - " }", - " }", - " return (CharSequence) local;", + " @SuppressWarnings(\"unchecked\")", + " private void initialize() {", + " this.sProvider = new SwitchingProvider<>(testComponent, 0);", + " this.cProvider = DoubleCheck.provider((Provider) sProvider);", " }", "", " @Override", " public Provider<Object> objectProvider() {", - " return (Provider) charSequenceProvider();", + " return ((Provider) cProvider);", " }", "", " @Override", " public Provider<CharSequence> charSequenceProvider() {", - " Object local = cProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " cProvider = (Provider<CharSequence>) local;", - " }", - " return (Provider<CharSequence>) local;", + " return cProvider;", " }", "", - " private final class SwitchingProvider<T> implements Provider<T> {", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", - " case 0:", - " return (T) DaggerTestComponent.this.charSequence();", - " default:", - " throw new AssertionError(id);", + " case 0: return (T) TestModule_SFactory.s();", + " default: throw new AssertionError(id);", " }", " }", " }", @@ -544,24 +523,18 @@ public class SwitchingProviderTest { " @SuppressWarnings(\"rawtypes\")", " private static final Provider ABSENT_JDK_OPTIONAL_PROVIDER =", " InstanceFactory.create(Optional.empty());", - "", - " private volatile Provider<Optional<Present>> optionalOfPresentProvider;", - "", + " private Provider<Optional<Present>> optionalOfPresentProvider;", " private Provider<Optional<Absent>> optionalOfAbsentProvider;", "", " @SuppressWarnings(\"unchecked\")", " private void initialize() {", + " this.optionalOfPresentProvider = new SwitchingProvider<>(testComponent, 0);", " this.optionalOfAbsentProvider = absentJdkOptionalProvider();", " }", "", " @Override", " public Provider<Optional<Present>> providerOfOptionalOfPresent() {", - " Object local = optionalOfPresentProvider;", - " if (local == null) {", - " local = new SwitchingProvider<>(0);", - " optionalOfPresentProvider = (Provider<Optional<Present>>) local;", - " }", - " return (Provider<Optional<Present>>) local;", + " return optionalOfPresentProvider;", " }", "", " @Override", @@ -571,20 +544,18 @@ public class SwitchingProviderTest { "", " private static <T> Provider<Optional<T>> absentJdkOptionalProvider() {", " @SuppressWarnings(\"unchecked\")", - " Provider<Optional<T>> provider = ", - " (Provider<Optional<T>>) ABSENT_JDK_OPTIONAL_PROVIDER;", + " Provider<Optional<T>> provider =", + " (Provider<Optional<T>>) ABSENT_JDK_OPTIONAL_PROVIDER;", " return provider;", " }", "", - " private final class SwitchingProvider<T> implements Provider<T> {", + " private static final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", " @Override", " public T get() {", " switch (id) {", - " case 0: // java.util.Optional<test.Present>", - " return (T) Optional.of(TestModule_PFactory.p());", - " default:", - " throw new AssertionError(id);", + " case 0: return (T) Optional.of(TestModule_PFactory.p());", + " default: throw new AssertionError(id);", " }", " }", " }", @@ -592,8 +563,6 @@ public class SwitchingProviderTest { } private Compiler compilerWithAndroidMode() { - return javac() - .withProcessors(new ComponentProcessor()) - .withOptions(CompilerMode.FAST_INIT_MODE.javacopts()); + return compilerWithOptions(CompilerMode.FAST_INIT_MODE.javacopts()); } } diff --git a/javatests/dagger/internal/codegen/UnresolvableDependencyTest.java b/javatests/dagger/internal/codegen/UnresolvableDependencyTest.java new file mode 100644 index 000000000..4a2ff98d0 --- /dev/null +++ b/javatests/dagger/internal/codegen/UnresolvableDependencyTest.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2021 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.truth.Truth.assertThat; +import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.internal.codegen.Compilers.compilerWithOptions; +import static dagger.internal.codegen.Compilers.daggerCompiler; +import static java.util.stream.Collectors.joining; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class UnresolvableDependencyTest { + + @Test + public void referencesUnresolvableDependency() { + JavaFileObject fooComponent = + JavaFileObjects.forSourceLines( + "test.FooComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component", + "interface FooComponent {", + " Foo foo();", + "}"); + + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Foo {", + " @Inject", + " Foo(Bar bar) {}", + "}"); + + JavaFileObject bar = + JavaFileObjects.forSourceLines( + "test.Bar", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Bar {", + " @Inject", + " Bar(UnresolvableDependency dep) {}", + "}"); + + Compilation compilation = daggerCompiler().compile(fooComponent, foo, bar); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(3); + assertThat(compilation).hadErrorContaining( + "cannot find symbol" + + "\n symbol: class UnresolvableDependency" + + "\n location: class test.Bar"); + String trace = "\n " + + "\n Dependency trace:" + + "\n => element (CLASS): test.Bar" + + "\n => element (CONSTRUCTOR): Bar(UnresolvableDependency)" + + "\n => type (EXECUTABLE constructor): (UnresolvableDependency)void" + + "\n => type (ERROR parameter type): UnresolvableDependency"; + assertThat(compilation).hadErrorContaining( + "InjectProcessingStep was unable to process 'Bar(UnresolvableDependency)' because " + + "'UnresolvableDependency' could not be resolved." + trace); + assertThat(compilation).hadErrorContaining( + "ComponentProcessingStep was unable to process 'test.FooComponent' because " + + "'UnresolvableDependency' could not be resolved." + trace); + + // Only include a minimal portion of the stacktrace to minimize breaking tests due to refactors. + String stacktraceErrorMessage = + "dagger.internal.codegen.base" + + ".DaggerSuperficialValidation$ValidationException$KnownErrorType"; + + // Check that the stacktrace is not included in the error message by default. + assertThat( + compilation.errors().stream() + .map(error -> error.getMessage(null)) + .collect(joining("\n"))) + .doesNotContain(stacktraceErrorMessage); + + // Recompile with the option enabled and check that the stacktrace is now included + compilation = + compilerWithOptions("-Adagger.includeStacktraceWithDeferredErrorMessages=ENABLED") + .compile(fooComponent, foo, bar); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(3); + assertThat(compilation).hadErrorContaining(stacktraceErrorMessage); + } + + @Test + public void referencesUnresolvableAnnotationOnType() { + JavaFileObject fooComponent = + JavaFileObjects.forSourceLines( + "test.FooComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component", + "interface FooComponent {", + " Foo foo();", + "}"); + + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Foo {", + " @Inject", + " Foo(Bar bar) {}", + "}"); + + JavaFileObject bar = + JavaFileObjects.forSourceLines( + "test.Bar", + "package test;", + "", + "import javax.inject.Inject;", + "", + "@UnresolvableAnnotation", + "class Bar {", + " @Inject", + " Bar(String dep) {}", + "}"); + + Compilation compilation = daggerCompiler().compile(fooComponent, foo, bar); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(3); + assertThat(compilation).hadErrorContaining( + "cannot find symbol" + + "\n symbol: class UnresolvableAnnotation"); + String trace = "\n " + + "\n Dependency trace:" + + "\n => element (CLASS): test.Bar" + + "\n => annotation: @UnresolvableAnnotation" + + "\n => type (ERROR annotation type): UnresolvableAnnotation"; + assertThat(compilation).hadErrorContaining( + "InjectProcessingStep was unable to process 'Bar(java.lang.String)' because " + + "'UnresolvableAnnotation' could not be resolved." + trace); + assertThat(compilation).hadErrorContaining( + "ComponentProcessingStep was unable to process 'test.FooComponent' because " + + "'UnresolvableAnnotation' could not be resolved." + trace); + } + + @Test + public void referencesUnresolvableAnnotationOnTypeOnParameter() { + JavaFileObject fooComponent = + JavaFileObjects.forSourceLines( + "test.FooComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component", + "interface FooComponent {", + " Foo foo();", + "}"); + + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Foo {", + " @Inject", + " Foo(Bar bar) {}", + "}"); + + JavaFileObject bar = + JavaFileObjects.forSourceLines( + "test.Bar", + "package test;", + "", + "import javax.inject.Inject;", + "", + "class Bar {", + " @Inject", + " Bar(@UnresolvableAnnotation String dep) {}", + "}"); + + Compilation compilation = daggerCompiler().compile(fooComponent, foo, bar); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(3); + assertThat(compilation).hadErrorContaining( + "cannot find symbol" + + "\n symbol: class UnresolvableAnnotation" + + "\n location: class test.Bar"); + String trace = "\n " + + "\n Dependency trace:" + + "\n => element (CLASS): test.Bar" + + "\n => element (CONSTRUCTOR): Bar(java.lang.String)" + + "\n => element (PARAMETER): dep" + + "\n => annotation: @UnresolvableAnnotation" + + "\n => type (ERROR annotation type): UnresolvableAnnotation"; + assertThat(compilation).hadErrorContaining( + "InjectProcessingStep was unable to process 'Bar(java.lang.String)' because " + + "'UnresolvableAnnotation' could not be resolved." + trace); + assertThat(compilation).hadErrorContaining( + "ComponentProcessingStep was unable to process 'test.FooComponent' because " + + "'UnresolvableAnnotation' could not be resolved." + trace); + } +} diff --git a/javatests/dagger/internal/codegen/ValidationReportTest.java b/javatests/dagger/internal/codegen/ValidationReportTest.java index d1c3a2e3c..0e49d300b 100644 --- a/javatests/dagger/internal/codegen/ValidationReportTest.java +++ b/javatests/dagger/internal/codegen/ValidationReportTest.java @@ -18,15 +18,17 @@ package dagger.internal.codegen; import static com.google.common.truth.Truth.assertThat; import static com.google.testing.compile.CompilationSubject.assertThat; -import static com.google.testing.compile.Compiler.javac; +import static dagger.internal.codegen.Compilers.daggerCompiler; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableSet; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; import dagger.internal.codegen.validation.ValidationReport; -import dagger.internal.codegen.validation.ValidationReport.Builder; import java.util.Set; import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.TypeElement; import javax.tools.JavaFileObject; @@ -37,45 +39,42 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ValidationReportTest { private static final JavaFileObject TEST_CLASS_FILE = - JavaFileObjects.forSourceLines("test.TestClass", + JavaFileObjects.forSourceLines( + "test.TestClass", "package test;", "", "final class TestClass {}"); @Test public void basicReport() { - Compilation compilation = - javac() - .withProcessors( - new SimpleTestProcessor() { - @Override - void test() { - Builder<TypeElement> reportBuilder = - ValidationReport.about(getTypeElement("test.TestClass")); - reportBuilder.addError("simple error"); - reportBuilder.build().printMessagesTo(processingEnv.getMessager()); - } - }) - .compile(TEST_CLASS_FILE); + Processor processor = + new SimpleTestProcessor() { + @Override + void test() { + ValidationReport.about(getTypeElement("test.TestClass")) + .addError("simple error") + .build() + .printMessagesTo(processingEnv.getMessager()); + } + }; + Compilation compilation = daggerCompiler(processor).compile(TEST_CLASS_FILE); assertThat(compilation).failed(); assertThat(compilation).hadErrorContaining("simple error").inFile(TEST_CLASS_FILE).onLine(3); } @Test public void messageOnDifferentElement() { - Compilation compilation = - javac() - .withProcessors( - new SimpleTestProcessor() { - @Override - void test() { - Builder<TypeElement> reportBuilder = - ValidationReport.about(getTypeElement("test.TestClass")); - reportBuilder.addError("simple error", getTypeElement(String.class)); - reportBuilder.build().printMessagesTo(processingEnv.getMessager()); - } - }) - .compile(TEST_CLASS_FILE); + Processor processor = + new SimpleTestProcessor() { + @Override + void test() { + ValidationReport.about(getTypeElement("test.TestClass")) + .addError("simple error", getTypeElement(String.class)) + .build() + .printMessagesTo(processingEnv.getMessager()); + } + }; + Compilation compilation = daggerCompiler(processor).compile(TEST_CLASS_FILE); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining("[java.lang.String] simple error") @@ -85,29 +84,30 @@ public class ValidationReportTest { @Test public void subreport() { - Compilation compilation = - javac() - .withProcessors( - new SimpleTestProcessor() { - @Override - void test() { - Builder<TypeElement> reportBuilder = - ValidationReport.about(getTypeElement("test.TestClass")); - reportBuilder.addError("simple error"); - ValidationReport<TypeElement> parentReport = - ValidationReport.about(getTypeElement(String.class)) - .addSubreport(reportBuilder.build()) - .build(); - assertThat(parentReport.isClean()).isFalse(); - parentReport.printMessagesTo(processingEnv.getMessager()); - } - }) - .compile(TEST_CLASS_FILE); + Processor processor = + new SimpleTestProcessor() { + @Override + void test() { + ValidationReport parentReport = + ValidationReport.about(getTypeElement(String.class)) + .addSubreport( + ValidationReport.about(getTypeElement("test.TestClass")) + .addError("simple error") + .build()) + .build(); + assertThat(parentReport.isClean()).isFalse(); + parentReport.printMessagesTo(processingEnv.getMessager()); + } + }; + Compilation compilation = daggerCompiler(processor).compile(TEST_CLASS_FILE); assertThat(compilation).failed(); assertThat(compilation).hadErrorContaining("simple error").inFile(TEST_CLASS_FILE).onLine(3); } - private static abstract class SimpleTestProcessor extends AbstractProcessor { + private abstract static class SimpleTestProcessor extends AbstractProcessor { + @SuppressWarnings("HidingField") // Subclasses should always use the XProcessing version. + protected XProcessingEnv processingEnv; + @Override public Set<String> getSupportedAnnotationTypes() { return ImmutableSet.of("*"); @@ -115,16 +115,17 @@ public class ValidationReportTest { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + processingEnv = XProcessingEnv.create(super.processingEnv); test(); return false; } - protected final TypeElement getTypeElement(Class<?> clazz) { + protected final XTypeElement getTypeElement(Class<?> clazz) { return getTypeElement(clazz.getCanonicalName()); } - protected final TypeElement getTypeElement(String canonicalName) { - return processingEnv.getElementUtils().getTypeElement(canonicalName); + protected final XTypeElement getTypeElement(String canonicalName) { + return processingEnv.requireTypeElement(canonicalName); } abstract void test(); diff --git a/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD b/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD index 551e09139..9e7b1a424 100644 --- a/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD +++ b/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD @@ -28,10 +28,10 @@ GenJavaTests( deps = [ "//java/dagger/internal/codegen/bindinggraphvalidation", "//javatests/dagger/internal/codegen:compilers", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/compile_testing", + "//third_party/java/javapoet", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidationTest.java b/javatests/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidationTest.java new file mode 100644 index 000000000..bc8b0c6ad --- /dev/null +++ b/javatests/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidationTest.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2021 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.bindinggraphvalidation; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.internal.codegen.Compilers.daggerCompiler; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SetMultibindingValidationTest { + private static final JavaFileObject FOO = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "public interface Foo {}"); + + private static final JavaFileObject FOO_IMPL = + JavaFileObjects.forSourceLines( + "test.FooImpl", + "package test;", + "", + "import javax.inject.Inject;", + "", + "public final class FooImpl implements Foo {", + " @Inject FooImpl() {}", + "}"); + + @Test public void testMultipleSetBindingsToSameFoo() { + JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule", + "package test;", + "", + "import dagger.Binds;", + "import dagger.multibindings.IntoSet;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "interface TestModule {", + " @Binds @IntoSet Foo bindFoo(FooImpl impl);", + "", + " @Binds @IntoSet Foo bindFooAgain(FooImpl impl);", + "}"); + JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "import java.util.Set;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " Set<Foo> setOfFoo();", + "}"); + Compilation compilation = daggerCompiler().compile(FOO, FOO_IMPL, module, component); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "Multiple set contributions into Set<Foo> for the same contribution key: FooImpl"); + } + + @Test public void testMultipleSetBindingsToSameFooThroughMultipleBinds() { + JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule", + "package test;", + "", + "import dagger.Binds;", + "import dagger.multibindings.IntoSet;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "interface TestModule {", + " @Binds @IntoSet Object bindObject(FooImpl impl);", + "", + " @Binds @IntoSet Object bindObjectAgain(Foo impl);", + "", + " @Binds Foo bindFoo(FooImpl impl);", + "}"); + JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "import java.util.Set;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " Set<Object> setOfObject();", + "}"); + Compilation compilation = daggerCompiler().compile(FOO, FOO_IMPL, module, component); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "Multiple set contributions into Set<Object> for the same contribution key: FooImpl"); + } + + @Test public void testMultipleSetBindingsViaElementsIntoSet() { + JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule", + "package test;", + "", + "import dagger.Binds;", + "import dagger.Provides;", + "import dagger.multibindings.ElementsIntoSet;", + "import java.util.HashSet;", + "import java.util.Set;", + "import javax.inject.Inject;", + "import javax.inject.Qualifier;", + "", + "@dagger.Module", + "interface TestModule {", + "", + " @Qualifier", + " @interface Internal {}", + "", + " @Provides @Internal static Set<Foo> provideSet() { return new HashSet<>(); }", + "", + " @Binds @ElementsIntoSet Set<Foo> bindSet(@Internal Set<Foo> fooSet);", + "", + " @Binds @ElementsIntoSet Set<Foo> bindSetAgain(@Internal Set<Foo> fooSet);", + "}"); + JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "import java.util.Set;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " Set<Foo> setOfFoo();", + "}"); + Compilation compilation = daggerCompiler().compile(FOO, module, component); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "Multiple set contributions into Set<Foo> for the same contribution key: " + + "@TestModule.Internal Set<Foo>"); + } + + @Test public void testMultipleSetBindingsToSameFooSubcomponents() { + JavaFileObject parentModule = JavaFileObjects.forSourceLines("test.ParentModule", + "package test;", + "", + "import dagger.Binds;", + "import dagger.multibindings.IntoSet;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "interface ParentModule {", + " @Binds @IntoSet Foo bindFoo(FooImpl impl);", + "}"); + JavaFileObject childModule = JavaFileObjects.forSourceLines("test.ChildModule", + "package test;", + "", + "import dagger.Binds;", + "import dagger.multibindings.IntoSet;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "interface ChildModule {", + " @Binds @IntoSet Foo bindFoo(FooImpl impl);", + "}"); + JavaFileObject parentComponent = JavaFileObjects.forSourceLines("test.ParentComponent", + "package test;", + "", + "import dagger.Component;", + "import java.util.Set;", + "", + "@Component(modules = ParentModule.class)", + "interface ParentComponent {", + " Set<Foo> setOfFoo();", + " ChildComponent child();", + "}"); + JavaFileObject childComponent = JavaFileObjects.forSourceLines("test.ChildComponent", + "package test;", + "", + "import dagger.Subcomponent;", + "import java.util.Set;", + "", + "@Subcomponent(modules = ChildModule.class)", + "interface ChildComponent {", + " Set<Foo> setOfFoo();", + "}"); + Compilation compilation = daggerCompiler().compile( + FOO, FOO_IMPL, parentModule, childModule, parentComponent, childComponent); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "Multiple set contributions into Set<Foo> for the same contribution key: FooImpl"); + assertThat(compilation) + .hadErrorContaining( + "ParentComponent → ChildComponent"); + } + + @Test public void testMultipleSetBindingsToSameKeyButDifferentBindings() { + // Use an impl with local multibindings to create different bindings. We still want this to fail + // even though there are separate bindings because it is likely an unintentional error anyway. + JavaFileObject fooImplWithMult = JavaFileObjects.forSourceLines("test.FooImplWithMult", + "package test;", + "", + "import java.util.Set;", + "import javax.inject.Inject;", + "", + "public final class FooImplWithMult implements Foo {", + " @Inject FooImplWithMult(Set<Long> longSet) {}", + "}"); + // Scoping the @Binds is necessary to ensure it goes to different bindings + JavaFileObject parentModule = JavaFileObjects.forSourceLines("test.ParentModule", + "package test;", + "", + "import dagger.Binds;", + "import dagger.Provides;", + "import dagger.multibindings.IntoSet;", + "import javax.inject.Inject;", + "import javax.inject.Singleton;", + "", + "@dagger.Module", + "interface ParentModule {", + " @Singleton", + " @Binds @IntoSet Foo bindFoo(FooImplWithMult impl);", + "", + " @Provides @IntoSet static Long provideLong() {", + " return 0L;", + " }", + "}"); + JavaFileObject childModule = JavaFileObjects.forSourceLines("test.ChildModule", + "package test;", + "", + "import dagger.Binds;", + "import dagger.Provides;", + "import dagger.multibindings.IntoSet;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "interface ChildModule {", + " @Binds @IntoSet Foo bindFoo(FooImplWithMult impl);", + "", + " @Provides @IntoSet static Long provideLong() {", + " return 1L;", + " }", + "}"); + JavaFileObject parentComponent = JavaFileObjects.forSourceLines("test.ParentComponent", + "package test;", + "", + "import dagger.Component;", + "import java.util.Set;", + "import javax.inject.Singleton;", + "", + "@Singleton", + "@Component(modules = ParentModule.class)", + "interface ParentComponent {", + " Set<Foo> setOfFoo();", + " ChildComponent child();", + "}"); + JavaFileObject childComponent = JavaFileObjects.forSourceLines("test.ChildComponent", + "package test;", + "", + "import dagger.Subcomponent;", + "import java.util.Set;", + "", + "@Subcomponent(modules = ChildModule.class)", + "interface ChildComponent {", + " Set<Foo> setOfFoo();", + "}"); + Compilation compilation = daggerCompiler().compile( + FOO, fooImplWithMult, parentModule, childModule, parentComponent, childComponent); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "Multiple set contributions into Set<Foo> for the same contribution key: " + + "FooImplWithMult"); + assertThat(compilation) + .hadErrorContaining( + "ParentComponent → ChildComponent"); + } +} diff --git a/javatests/dagger/internal/codegen/javapoet/BUILD b/javatests/dagger/internal/codegen/javapoet/BUILD index 438d3779b..b108d6ee9 100644 --- a/javatests/dagger/internal/codegen/javapoet/BUILD +++ b/javatests/dagger/internal/codegen/javapoet/BUILD @@ -28,10 +28,10 @@ GenJavaTests( deps = [ "//java/dagger/internal/codegen/javapoet", "//java/dagger/internal/codegen/langmodel", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", - "@maven//:com_google_auto_auto_common", + "//third_party/java/auto:common", + "//third_party/java/compile_testing", + "//third_party/java/javapoet", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/internal/codegen/javapoet/ExpressionTest.java b/javatests/dagger/internal/codegen/javapoet/ExpressionTest.java index acfa46081..8360a1bf9 100644 --- a/javatests/dagger/internal/codegen/javapoet/ExpressionTest.java +++ b/javatests/dagger/internal/codegen/javapoet/ExpressionTest.java @@ -18,8 +18,9 @@ package dagger.internal.codegen.javapoet; import static com.google.common.truth.Truth.assertThat; -import com.google.auto.common.MoreTypes; import com.google.testing.compile.CompilationRule; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; import javax.lang.model.type.PrimitiveType; @@ -70,11 +71,10 @@ public class ExpressionTest { Expression boxedExpression = primitiveExpression.box(types); assertThat(boxedExpression.codeBlock().toString()).isEqualTo("(java.lang.Integer) 5"); - assertThat(MoreTypes.equivalence().equivalent(boxedExpression.type(), type(Integer.class))) - .isTrue(); + assertThat(TypeName.get(boxedExpression.type())).isEqualTo(TypeName.get(type(Integer.class))); } private TypeMirror type(Class<?> clazz) { - return elements.getTypeElement(clazz).asType(); + return elements.getTypeElement(ClassName.get(clazz)).asType(); } } diff --git a/javatests/dagger/internal/codegen/validation/BUILD b/javatests/dagger/internal/codegen/validation/BUILD index 03fd684d1..6271768cf 100644 --- a/javatests/dagger/internal/codegen/validation/BUILD +++ b/javatests/dagger/internal/codegen/validation/BUILD @@ -27,7 +27,7 @@ GenJavaTests( javacopts = DOCLINT_HTML_AND_SYNTAX, deps = [ "//java/dagger/internal/codegen/validation", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/lint/BUILD b/javatests/dagger/lint/BUILD index 268340d5e..a2026c91b 100644 --- a/javatests/dagger/lint/BUILD +++ b/javatests/dagger/lint/BUILD @@ -23,7 +23,7 @@ kt_jvm_test( name = "DaggerKotlinIssueDetectorTest", srcs = ["DaggerKotlinIssueDetectorTest.kt"], deps = [ - "@google_bazel_common//third_party/java/junit", + "//third_party/java/junit", "//java/dagger/lint:lint-artifact-lib", "@maven//:com_android_tools_lint_lint_checks", "@maven//:com_android_tools_lint_lint_tests", diff --git a/javatests/dagger/lint/DaggerKotlinIssueDetectorTest.kt b/javatests/dagger/lint/DaggerKotlinIssueDetectorTest.kt index f11239234..e1976998f 100644 --- a/javatests/dagger/lint/DaggerKotlinIssueDetectorTest.kt +++ b/javatests/dagger/lint/DaggerKotlinIssueDetectorTest.kt @@ -16,6 +16,7 @@ package dagger.lint import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestMode import com.android.tools.lint.detector.api.Detector import com.android.tools.lint.detector.api.Issue import org.junit.Test @@ -183,6 +184,8 @@ class DaggerKotlinIssueDetectorTest : LintDetectorTest() { ).indented() ) .allowCompilationErrors(false) + // Unlikely that @JvmStatic would be aliased, so skipping these modes. + .skipTestModes(TestMode.TYPE_ALIAS, TestMode.IMPORT_ALIAS) .run() .expect( """ diff --git a/javatests/dagger/producers/BUILD b/javatests/dagger/producers/BUILD index 9404d85ac..f8b098efe 100644 --- a/javatests/dagger/producers/BUILD +++ b/javatests/dagger/producers/BUILD @@ -31,12 +31,12 @@ GenJavaTests( functional = 0, javacopts = SOURCE_7_TARGET_7 + DOCLINT_REFERENCES + DOCLINT_HTML_AND_SYNTAX, deps = [ - "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:concurrent", "//java/dagger/producers", - "@google_bazel_common//third_party/java/guava:testlib", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/mockito", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/guava:testlib", + "//third_party/java/guava/collect", + "//third_party/java/guava/util/concurrent", + "//third_party/java/junit", + "//third_party/java/mockito", + "//third_party/java/truth", ], ) diff --git a/javatests/dagger/spi/BUILD b/javatests/dagger/spi/BUILD index 849037b6f..ef1dca040 100644 --- a/javatests/dagger/spi/BUILD +++ b/javatests/dagger/spi/BUILD @@ -32,13 +32,13 @@ GenJavaTests( deps = [ "//java/dagger:core", "//java/dagger/internal/codegen:processor", - "//java/dagger/internal/guava:base", - "//java/dagger/internal/guava:collect", "//java/dagger/spi", - "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/compile_testing", - "@google_bazel_common//third_party/java/jsr330_inject", - "@google_bazel_common//third_party/java/junit", - "@google_bazel_common//third_party/java/truth", + "//third_party/java/auto:service", + "//third_party/java/compile_testing", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/jsr330_inject", + "//third_party/java/junit", + "//third_party/java/truth", ], ) diff --git a/test_defs.bzl b/test_defs.bzl index 6f2308877..cdb9f51cf 100644 --- a/test_defs.bzl +++ b/test_defs.bzl @@ -19,7 +19,9 @@ load("@rules_java//java:defs.bzl", "java_library", "java_test") # Defines a set of build variants and the list of extra javacopts to build with. # The key will be appended to the generated test names to ensure uniqueness. BUILD_VARIANTS = { + "Shards": ["-Adagger.keysPerComponentShard=2"], "FastInit": ["-Adagger.fastInit=enabled"], + "FastInit_Shards": ["-Adagger.fastInit=enabled", "-Adagger.keysPerComponentShard=2"], } # TODO(ronshapiro): convert this to use bazel_common @@ -106,6 +108,7 @@ def _GenTests( if functional: for (variant_name, extra_javacopts) in BUILD_VARIANTS.items(): variant_javacopts = (javacopts or []) + extra_javacopts + _gen_tests( library_rule_type, test_rule_type, diff --git a/third_party/java/asm/BUILD b/third_party/java/asm/BUILD new file mode 100644 index 000000000..bebdb1216 --- /dev/null +++ b/third_party/java/asm/BUILD @@ -0,0 +1,32 @@ +# Copyright (C) 2021 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. + +# BUILD rules for http://asm.ow2.org/ + +package(default_visibility = ["//:src"]) + +alias( + name = "asm", + actual = "@maven//:org_ow2_asm_asm", +) + +alias( + name = "asm-tree", + actual = "@maven//:org_ow2_asm_asm_tree", +) + +alias( + name = "asm-commons", + actual = "@maven//:org_ow2_asm_asm_commons", +) diff --git a/third_party/java/auto/BUILD b/third_party/java/auto/BUILD new file mode 100644 index 000000000..7b90e287e --- /dev/null +++ b/third_party/java/auto/BUILD @@ -0,0 +1,113 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/auto + +load("@rules_java//java:defs.bzl", "java_library", "java_plugin") + +package(default_visibility = ["//:src"]) + +alias( + name = "common", + actual = "@maven//:com_google_auto_auto_common", +) + +java_plugin( + name = "auto_value_processor", + processor_class = "com.google.auto.value.processor.AutoValueProcessor", + visibility = ["//visibility:private"], + deps = [ + ":common", + ":service", + "//third_party/java/guava", + "@maven//:com_google_auto_value_auto_value", + ], +) + +java_plugin( + name = "auto_annotation_processor", + processor_class = "com.google.auto.value.processor.AutoAnnotationProcessor", + visibility = ["//visibility:private"], + deps = [ + ":common", + ":service", + "//third_party/java/guava", + "@maven//:com_google_auto_value_auto_value", + ], +) + +java_plugin( + name = "auto_oneof_processor", + processor_class = "com.google.auto.value.processor.AutoOneOfProcessor", + visibility = ["//visibility:private"], + deps = [ + ":common", + ":service", + "//third_party/java/guava", + "@maven//:com_google_auto_value_auto_value", + ], +) + +java_library( + name = "value", + exported_plugins = [ + ":auto_annotation_processor", + ":auto_oneof_processor", + ":auto_value_processor", + ], + tags = ["maven:compile_only"], + exports = [ + "@maven//:com_google_auto_value_auto_value_annotations", + ], +) + +java_plugin( + name = "auto_factory_processor", + generates_api = 1, + processor_class = "com.google.auto.factory.processor.AutoFactoryProcessor", + visibility = ["//visibility:private"], + deps = [ + ":common", + ":service", + "//third_party/java/google_java_format", + "//third_party/java/guava", + "//third_party/java/javapoet", + "@maven//:com_google_auto_factory_auto_factory", + ], +) + +java_library( + name = "factory", + exported_plugins = [":auto_factory_processor"], + exports = ["@maven//:com_google_auto_factory_auto_factory"], +) + +java_plugin( + name = "auto_service_processor", + processor_class = "com.google.auto.service.processor.AutoServiceProcessor", + visibility = ["//visibility:private"], + deps = [ + ":common", + "//third_party/java/guava", + "@maven//:com_google_auto_service_auto_service", + "@maven//:com_google_auto_service_auto_service_annotations", + ], +) + +java_library( + name = "service", + exported_plugins = [":auto_service_processor"], + tags = ["maven:compile_only"], + exports = ["@maven//:com_google_auto_service_auto_service_annotations"], +) diff --git a/third_party/java/byte_buddy/BUILD b/third_party/java/byte_buddy/BUILD new file mode 100644 index 000000000..062e3cae0 --- /dev/null +++ b/third_party/java/byte_buddy/BUILD @@ -0,0 +1,23 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/mockito/mockito + +package(default_visibility = ["//:src"]) + +alias( + name = "byte_buddy", + testonly = 1, + actual = "@maven//:net_bytebuddy_byte_buddy", +) diff --git a/third_party/java/byte_buddy_agent/BUILD b/third_party/java/byte_buddy_agent/BUILD new file mode 100644 index 000000000..942078ccd --- /dev/null +++ b/third_party/java/byte_buddy_agent/BUILD @@ -0,0 +1,23 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/mockito/mockito + +package(default_visibility = ["//:src"]) + +alias( + name = "byte_buddy_agent", + testonly = 1, + actual = "@maven//:net_bytebuddy_byte_buddy_agent", +) diff --git a/third_party/java/checker_framework/BUILD b/third_party/java/checker_framework/BUILD new file mode 100644 index 000000000..01f3d7044 --- /dev/null +++ b/third_party/java/checker_framework/BUILD @@ -0,0 +1,28 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://checkerframework.org/ + +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//:src"]) + +java_library( + name = "dataflow", + exports = ["@maven//:org_checkerframework_dataflow"], + runtime_deps = [ + "@maven//:org_checkerframework_checker_qual", + "@maven//:org_checkerframework_javacutil", + ], +) diff --git a/third_party/java/checker_framework_annotations/BUILD b/third_party/java/checker_framework_annotations/BUILD new file mode 100644 index 000000000..5fd74bbb0 --- /dev/null +++ b/third_party/java/checker_framework_annotations/BUILD @@ -0,0 +1,22 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://checkerframework.org/ + +package(default_visibility = ["//:src"]) + +alias( + name = "checker_framework_annotations", + actual = "@maven//:org_checkerframework_checker_compat_qual", +) diff --git a/third_party/java/compile_testing/BUILD b/third_party/java/compile_testing/BUILD new file mode 100644 index 000000000..8aff4a474 --- /dev/null +++ b/third_party/java/compile_testing/BUILD @@ -0,0 +1,34 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/compile-testing + +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//:src"]) + +java_library( + name = "compile_testing", + testonly = 1, + exports = ["@maven//:com_google_testing_compile_compile_testing"], + runtime_deps = [ + "//third_party/java/auto:value", + "//third_party/java/error_prone:annotations", + "//third_party/java/guava", + "//third_party/java/jsr305_annotations", + "//third_party/java/junit", + "//third_party/java/truth", + "@local_jdk//:lib/tools.jar", + ], +) diff --git a/third_party/java/diffutils/BUILD b/third_party/java/diffutils/BUILD new file mode 100644 index 000000000..711ff69ac --- /dev/null +++ b/third_party/java/diffutils/BUILD @@ -0,0 +1,22 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/truth + +package(default_visibility = ["//:src"]) + +alias( + name = "diffutils", + actual = "@maven//:com_googlecode_java_diff_utils_diffutils", +) diff --git a/third_party/java/error_prone/BUILD b/third_party/java/error_prone/BUILD new file mode 100644 index 000000000..26a9682e3 --- /dev/null +++ b/third_party/java/error_prone/BUILD @@ -0,0 +1,47 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/error-prone. Note that Bazel already +# applies the Error Prone compiler to all java compilations - this package exports +# dependencies for Error Prone's libraries + +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//:src"]) + +java_library( + name = "annotations", + tags = ["maven:compile_only"], + exports = ["@maven//:com_google_errorprone_error_prone_annotations"], +) + +alias( + name = "error_prone_javac", + actual = "@maven//:com_google_errorprone_javac_shaded", +) + +java_library( + name = "check_api", + exports = [ + "@maven//:com_google_errorprone_error_prone_annotation", + "@maven//:com_google_errorprone_error_prone_check_api", + ], + runtime_deps = [ + ":annotations", + ":error_prone_javac", + "//third_party/java/checker_framework:dataflow", + "//third_party/java/diffutils", + "//third_party/java/jsr305_annotations", + ], +) diff --git a/third_party/java/google_java_format/BUILD b/third_party/java/google_java_format/BUILD new file mode 100644 index 000000000..c2313d905 --- /dev/null +++ b/third_party/java/google_java_format/BUILD @@ -0,0 +1,30 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/google-java-format + +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//:src"]) + +java_library( + name = "google_java_format", + exports = [ + "@maven//:com_google_googlejavaformat_google_java_format", + ], + runtime_deps = [ + "//third_party/java/error_prone:error_prone_javac", + "//third_party/java/guava", + ], +) diff --git a/third_party/java/grpc/BUILD b/third_party/java/grpc/BUILD new file mode 100644 index 000000000..e58da69db --- /dev/null +++ b/third_party/java/grpc/BUILD @@ -0,0 +1,37 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/grpc/grpc-java + +package(default_visibility = ["//:src"]) + +alias( + name = "core", + actual = "@maven//:io_grpc_grpc_core", +) + +alias( + name = "netty", + actual = "@maven//:io_grpc_grpc_netty", +) + +alias( + name = "context", + actual = "@maven//:io_grpc_grpc_context", +) + +alias( + name = "protobuf", + actual = "@maven//:io_grpc_grpc_protobuf", +) diff --git a/third_party/java/guava/BUILD b/third_party/java/guava/BUILD new file mode 100644 index 000000000..bc3364b69 --- /dev/null +++ b/third_party/java/guava/BUILD @@ -0,0 +1,41 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/guava + +load("@rules_java//java:defs.bzl", "java_library", "java_plugin") + +package(default_visibility = ["//:src"]) + +java_library( + name = "guava", + exported_plugins = [":beta-checker"], + exports = [ + "@maven//:com_google_guava_failureaccess", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "testlib", + testonly = 1, + exports = ["@maven//:com_google_guava_guava_testlib"], + runtime_deps = [":guava"], +) + +java_plugin( + name = "beta-checker", + visibility = ["//visibility:private"], + deps = ["@maven//:com_google_guava_guava_beta_checker"], +) diff --git a/third_party/java/guava/base/BUILD b/third_party/java/guava/base/BUILD new file mode 100644 index 000000000..8ec4e6a6e --- /dev/null +++ b/third_party/java/guava/base/BUILD @@ -0,0 +1,22 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/guava + +package(default_visibility = ["//:src"]) + +alias( + name = "base", + actual = "//third_party/java/guava", +) diff --git a/third_party/java/guava/cache/BUILD b/third_party/java/guava/cache/BUILD new file mode 100644 index 000000000..8da3dfb27 --- /dev/null +++ b/third_party/java/guava/cache/BUILD @@ -0,0 +1,22 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/guava + +package(default_visibility = ["//:src"]) + +alias( + name = "cache", + actual = "//third_party/java/guava", +) diff --git a/third_party/java/guava/collect/BUILD b/third_party/java/guava/collect/BUILD new file mode 100644 index 000000000..a90aceb30 --- /dev/null +++ b/third_party/java/guava/collect/BUILD @@ -0,0 +1,22 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/guava + +package(default_visibility = ["//:src"]) + +alias( + name = "collect", + actual = "//third_party/java/guava", +) diff --git a/third_party/java/guava/graph/BUILD b/third_party/java/guava/graph/BUILD new file mode 100644 index 000000000..c77ca68c6 --- /dev/null +++ b/third_party/java/guava/graph/BUILD @@ -0,0 +1,22 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/guava + +package(default_visibility = ["//:src"]) + +alias( + name = "graph", + actual = "//third_party/java/guava", +) diff --git a/third_party/java/guava/io/BUILD b/third_party/java/guava/io/BUILD new file mode 100644 index 000000000..ad6c7a25d --- /dev/null +++ b/third_party/java/guava/io/BUILD @@ -0,0 +1,22 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/guava + +package(default_visibility = ["//:src"]) + +alias( + name = "io", + actual = "//third_party/java/guava", +) diff --git a/third_party/java/guava/util/concurrent/BUILD b/third_party/java/guava/util/concurrent/BUILD new file mode 100644 index 000000000..00dd5ad34 --- /dev/null +++ b/third_party/java/guava/util/concurrent/BUILD @@ -0,0 +1,22 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/guava + +package(default_visibility = ["//:src"]) + +alias( + name = "concurrent", + actual = "//third_party/java/guava", +) diff --git a/third_party/java/hamcrest/BUILD b/third_party/java/hamcrest/BUILD new file mode 100644 index 000000000..3ce9487f3 --- /dev/null +++ b/third_party/java/hamcrest/BUILD @@ -0,0 +1,23 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/hamcrest/JavaHamcrest + +package(default_visibility = ["//:src"]) + +alias( + name = "hamcrest", + testonly = 1, + actual = "@maven//:org_hamcrest_hamcrest_core", +) diff --git a/third_party/java/incap/BUILD b/third_party/java/incap/BUILD new file mode 100644 index 000000000..7958754b3 --- /dev/null +++ b/third_party/java/incap/BUILD @@ -0,0 +1,35 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/tbroyer/gradle-incap-helper + +load("@rules_java//java:defs.bzl", "java_library", "java_plugin") + +package(default_visibility = ["//:src"]) + +java_library( + name = "incap", + exported_plugins = [":processor"], + exports = ["@maven//:net_ltgt_gradle_incap_incap"], +) + +java_plugin( + name = "processor", + processor_class = "net.ltgt.gradle.incap.processor.IncrementalAnnotationProcessorProcessor", + visibility = ["//visibility:private"], + deps = [ + "@maven//:net_ltgt_gradle_incap_incap", + "@maven//:net_ltgt_gradle_incap_incap_processor", + ], +) diff --git a/third_party/java/javapoet/BUILD b/third_party/java/javapoet/BUILD new file mode 100644 index 000000000..7d056d76f --- /dev/null +++ b/third_party/java/javapoet/BUILD @@ -0,0 +1,22 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/square/javapoet + +package(default_visibility = ["//:src"]) + +alias( + name = "javapoet", + actual = "@maven//:com_squareup_javapoet", +) diff --git a/third_party/java/jsr250_annotations/BUILD b/third_party/java/jsr250_annotations/BUILD new file mode 100644 index 000000000..effdea892 --- /dev/null +++ b/third_party/java/jsr250_annotations/BUILD @@ -0,0 +1,22 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://en.wikipedia.org/wiki/JSR_250 + +package(default_visibility = ["//:src"]) + +alias( + name = "jsr250_annotations", + actual = "@maven//:javax_annotation_jsr250_api", +) diff --git a/third_party/java/jsr305_annotations/BUILD b/third_party/java/jsr305_annotations/BUILD new file mode 100644 index 000000000..3ae1825b0 --- /dev/null +++ b/third_party/java/jsr305_annotations/BUILD @@ -0,0 +1,22 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://jcp.org/en/jsr/detail?id=305 + +package(default_visibility = ["//:src"]) + +alias( + name = "jsr305_annotations", + actual = "@maven//:com_google_code_findbugs_jsr305", +) diff --git a/third_party/java/jsr330_inject/BUILD b/third_party/java/jsr330_inject/BUILD new file mode 100644 index 000000000..c8603b7d1 --- /dev/null +++ b/third_party/java/jsr330_inject/BUILD @@ -0,0 +1,27 @@ +# Copyright (C) 2021 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. + +# BUILD rules for javax.inject (https://www.jcp.org/en/jsr/detail?id=330) + +package(default_visibility = ["//:src"]) + +alias( + name = "jsr330_inject", + actual = "@maven//:javax_inject_javax_inject", +) + +alias( + name = "tck", + actual = "@maven//:javax_inject_javax_inject_tck", +) diff --git a/third_party/java/junit/BUILD b/third_party/java/junit/BUILD new file mode 100644 index 000000000..e40e2f74d --- /dev/null +++ b/third_party/java/junit/BUILD @@ -0,0 +1,26 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/junit-team + +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//:src"]) + +java_library( + name = "junit", + testonly = 1, + exports = ["@maven//:junit_junit"], + runtime_deps = ["//third_party/java/hamcrest"], +) diff --git a/third_party/java/mockito/BUILD b/third_party/java/mockito/BUILD new file mode 100644 index 000000000..d940443e5 --- /dev/null +++ b/third_party/java/mockito/BUILD @@ -0,0 +1,31 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/mockito/mockito + +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//:src"]) + +java_library( + name = "mockito", + testonly = 1, + exports = ["@maven//:org_mockito_mockito_core"], + runtime_deps = [ + "//third_party/java/byte_buddy", + "//third_party/java/byte_buddy_agent", + "//third_party/java/hamcrest", + "//third_party/java/objenesis", + ], +) diff --git a/third_party/java/objenesis/BUILD b/third_party/java/objenesis/BUILD new file mode 100644 index 000000000..68ea07d99 --- /dev/null +++ b/third_party/java/objenesis/BUILD @@ -0,0 +1,23 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/mockito/mockito + +package(default_visibility = ["//:src"]) + +alias( + name = "objenesis", + testonly = 1, + actual = "@maven//:org_objenesis_objenesis", +) diff --git a/third_party/java/protobuf/BUILD b/third_party/java/protobuf/BUILD new file mode 100644 index 000000000..b06ee12ef --- /dev/null +++ b/third_party/java/protobuf/BUILD @@ -0,0 +1,22 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/protobuf/tree/master/java + +package(default_visibility = ["//:src"]) + +alias( + name = "protobuf", + actual = "@maven//:com_google_protobuf_protobuf_java", +) diff --git a/third_party/java/truth/BUILD b/third_party/java/truth/BUILD new file mode 100644 index 000000000..73eb05f3c --- /dev/null +++ b/third_party/java/truth/BUILD @@ -0,0 +1,33 @@ +# Copyright (C) 2021 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. + +# BUILD rules for https://github.com/google/truth + +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//:src"]) + +java_library( + name = "truth", + testonly = 1, + exports = [ + "@maven//:com_google_truth_extensions_truth_java8_extension", + "@maven//:com_google_truth_truth", + ], + # TODO(cpovirk): This would make more sense in deps, but Bazel won't allow + # that unless we add dummy srcs. + runtime_deps = [ + "//third_party/java/asm", + ], +) diff --git a/tools/bazel_compat.bzl b/tools/bazel_compat.bzl new file mode 100644 index 000000000..e9354da65 --- /dev/null +++ b/tools/bazel_compat.bzl @@ -0,0 +1,65 @@ +# Copyright (C) 202 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. + +"""Macros for building with Bazel. +""" + +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library") + +def compat_kt_android_library(name, **kwargs): + bazel_kt_android_library(name, kwargs) + +def bazel_kt_android_library(name, kwargs): + """A macro that wraps Bazel's kt_android_library. + + This macro wraps Bazel's kt_android_library to output the jars files + in the expected locations (b/203519416). It also adds a dependency on + kotlin_stdlib if there are kotlin sources. + + Args: + name: the name of the library. + kwargs: Additional arguments of the library. + """ + + # If there are any kotlin sources, add the kotlin_stdlib, otherwise + # java-only projects may be missing a required runtime dependency on it. + if any([src.endswith(".kt") for src in kwargs.get("srcs", [])]): + # Add the kotlin_stdlib, otherwise it will be missing from java-only projects. + # We use deps rather than exports because exports isn't picked up by the pom file. + # See https://github.com/google/dagger/issues/3119 + required_deps = ["@maven//:org_jetbrains_kotlin_kotlin_stdlib"] + kwargs["deps"] = kwargs.get("deps", []) + required_deps + + # TODO(b/203519416): Bazel's kt_android_library outputs its jars under a target + # suffixed with "_kt". Thus, we have to do a bit of name aliasing to ensure that + # the jars exist at the expected targets. + kt_android_library( + name = "{}_internal".format(name), + **kwargs + ) + + native.alias( + name = name, + actual = ":{}_internal_kt".format(name), + ) + + native.alias( + name = "lib{}.jar".format(name), + actual = ":{}_internal_kt.jar".format(name), + ) + + native.alias( + name = "lib{}-src.jar".format(name), + actual = ":{}_internal_kt-sources.jar".format(name), + ) diff --git a/tools/maven.bzl b/tools/maven.bzl index 2c648a218..f842c88ac 100644 --- a/tools/maven.bzl +++ b/tools/maven.bzl @@ -74,7 +74,6 @@ def gen_maven_artifact( javadoc_exclude_packages, javadoc_android_api_level, shaded_deps, - shaded_rules, manifest, lint_deps, proguard_specs @@ -96,7 +95,6 @@ def _gen_maven_artifact( javadoc_exclude_packages, javadoc_android_api_level, shaded_deps, - shaded_rules, manifest, lint_deps, proguard_specs): @@ -131,7 +129,6 @@ def _gen_maven_artifact( javadoc_exclude_packages: The packages to exclude from the javadocs. javadoc_android_api_level: The android api level for the javadocs. shaded_deps: The shaded deps for the jarjar. - shaded_rules: The shaded rules for the jarjar. manifest: The AndroidManifest.xml to bundle in when packaing an 'aar'. lint_deps: The lint targets to be bundled in when packaging an 'aar'. proguard_specs: The proguard spec files to be bundled in when packaging an 'aar' @@ -148,7 +145,6 @@ def _gen_maven_artifact( ) shaded_deps = shaded_deps or [] - shaded_rules = shaded_rules or [] artifact_targets = [artifact_target] + (artifact_target_libs or []) lint_deps = lint_deps or [] @@ -172,7 +168,6 @@ def _gen_maven_artifact( name = name + "-classes", testonly = testonly, jars = artifact_targets + shaded_deps, - rules = shaded_rules, merge_meta_inf_files = merge_meta_inf_files, ) if lint_deps: @@ -217,7 +212,6 @@ def _gen_maven_artifact( name = name, testonly = testonly, jars = artifact_targets + shaded_deps, - rules = shaded_rules, merge_meta_inf_files = merge_meta_inf_files, ) diff --git a/tools/shader/build.gradle b/tools/shader/build.gradle new file mode 100644 index 000000000..2d1bc18d0 --- /dev/null +++ b/tools/shader/build.gradle @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 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. + */ + +import java.util.jar.Manifest; +import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer +import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext +import shadow.org.apache.tools.zip.ZipOutputStream +import shadow.org.apache.tools.zip.ZipEntry + +plugins { + id 'java-library' + // Use shadow version >= 7.1.1 to get log4j vulnerability patches: + // https://github.com/johnrengelman/shadow/releases/tag/7.1.1 + id 'com.github.johnrengelman.shadow' version '7.1.1' +} + +repositories { + mavenCentral() + mavenLocal() +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 +} + +// This transformation makes sure the input jar's MANIFEST.MF properties, +// such as "Created-by:" get merged into the final artifact jar (although it's +// not clear how necessary this is). +/** A transform that merges in the MANIFEST.MF files from the inputJar. */ +class ManifestMerger implements Transformer { + private static final String MANIFEST_MF = "META-INF/MANIFEST.MF"; + + private Manifest manifest; + + @Override + boolean canTransformResource(FileTreeElement element) { + return MANIFEST_MF.equalsIgnoreCase(element.relativePath.pathString); + } + + @Override + void transform(TransformerContext context) { + if (manifest == null) { + manifest = new Manifest(context.is); + } else { + Manifest toMerge = new Manifest(context.is); + manifest.getMainAttributes() + .putAll(toMerge.getMainAttributes().entrySet()); + manifest.getEntries().putAll(toMerge.getEntries().entrySet()); + } + } + + @Override + boolean hasTransformedResource() { true } + + @Override + void modifyOutputStream( + ZipOutputStream os, boolean preserveFileTimestamps) { + os.putNextEntry(new ZipEntry(MANIFEST_MF)); + if (manifest != null) { + ByteArrayOutputStream content = new ByteArrayOutputStream(); + manifest.write(content); + os.write(content.toByteArray()); + } + } +} + +configurations { + shaded +} + +shadowJar { + archiveClassifier = "" // postfix for output jar + configurations = [project.configurations.shaded] + transform(ManifestMerger.class) + + // Add a 'relocate' declaration for each shaded rule. + // Format: "key1,value1;key2,value2;key3,value3" + def rules = project.getProperty("shadedRules").split(";") + for (def i = 0; i < rules.size(); i++) { + def rule = rules[i].split(",") + relocate "${rule[0]}", "${rule[1]}" + } +} + +dependencies { + shaded files(project.getProperty("inputJar")) +} diff --git a/java/dagger/hilt/android/example/gradle/simple/gradle/wrapper/gradle-wrapper.jar b/tools/shader/gradle/wrapper/gradle-wrapper.jar Binary files differindex 5c2d1cf01..5c2d1cf01 100644 --- a/java/dagger/hilt/android/example/gradle/simple/gradle/wrapper/gradle-wrapper.jar +++ b/tools/shader/gradle/wrapper/gradle-wrapper.jar diff --git a/java/dagger/hilt/android/example/gradle/simple/gradle/wrapper/gradle-wrapper.properties b/tools/shader/gradle/wrapper/gradle-wrapper.properties index 4d9ca1649..0f80bbf51 100644 --- a/java/dagger/hilt/android/example/gradle/simple/gradle/wrapper/gradle-wrapper.properties +++ b/tools/shader/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/dagger/hilt/android/example/gradle/simple/gradlew b/tools/shader/gradlew index b0d6d0ab5..b0d6d0ab5 100755 --- a/java/dagger/hilt/android/example/gradle/simple/gradlew +++ b/tools/shader/gradlew diff --git a/util/deploy-all.sh b/util/deploy-all.sh new file mode 100755 index 000000000..a3febb8d0 --- /dev/null +++ b/util/deploy-all.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -eu + +bash $(dirname $0)/deploy-dagger.sh "$@" + +bash $(dirname $0)/deploy-hilt.sh "$@" + +bash $(dirname $0)/deploy-hilt-gradle-plugin.sh "$@"
\ No newline at end of file diff --git a/util/deploy-dagger.sh b/util/deploy-dagger.sh index ddf492659..2806047a3 100755 --- a/util/deploy-dagger.sh +++ b/util/deploy-dagger.sh @@ -14,114 +14,145 @@ readonly EXTRA_MAVEN_ARGS=("$@") # parameter, if provided then javadoc must also be provided. # @param {string} javadoc the java doc jar of the library. This is an optional # parameter, if provided then srcjar must also be provided. +# @param {string} module_name the JPMS module name to include in the jar. This +# is an optional parameter and can only be used with jar files. _deploy() { - local library=$1 - local pomfile=$2 - local srcjar=$3 - local javadoc=$4 + local shaded_rules=$1 + local library=$2 + local pomfile=$3 + local srcjar=$4 + local javadoc=$5 + local module_name=$6 bash $(dirname $0)/deploy-library.sh \ + "$shaded_rules" \ "$library" \ "$pomfile" \ "$srcjar" \ "$javadoc" \ + "$module_name" \ "$MVN_GOAL" \ "$VERSION_NAME" \ "${EXTRA_MAVEN_ARGS[@]:+${EXTRA_MAVEN_ARGS[@]}}" } _deploy \ + "" \ java/dagger/libcore.jar \ java/dagger/pom.xml \ java/dagger/libcore-src.jar \ - java/dagger/core-javadoc.jar + java/dagger/core-javadoc.jar \ + "dagger" _deploy \ + "" \ gwt/libgwt.jar \ gwt/pom.xml \ gwt/libgwt.jar \ - gwt/libgwt.jar + gwt/libgwt.jar \ + "" +# This artifact uses the shaded classes from dagger-spi, so we use the same +# shading rules so that our references to those classes are shaded the same way. _deploy \ + "com.google.auto.common,dagger.spi.shaded.auto.common;androidx.room.compiler.processing,dagger.spi.shaded.androidx.room.compiler.processing" \ java/dagger/internal/codegen/artifact.jar \ java/dagger/internal/codegen/pom.xml \ java/dagger/internal/codegen/artifact-src.jar \ - java/dagger/internal/codegen/artifact-javadoc.jar + java/dagger/internal/codegen/artifact-javadoc.jar \ + "" _deploy \ + "" \ java/dagger/producers/artifact.jar \ java/dagger/producers/pom.xml \ java/dagger/producers/artifact-src.jar \ - java/dagger/producers/artifact-javadoc.jar + java/dagger/producers/artifact-javadoc.jar \ + "" _deploy \ + "com.google.auto.common,dagger.spi.shaded.auto.common;androidx.room.compiler.processing,dagger.spi.shaded.androidx.room.compiler.processing" \ java/dagger/spi/artifact.jar \ java/dagger/spi/pom.xml \ java/dagger/spi/artifact-src.jar \ - java/dagger/spi/artifact-javadoc.jar + java/dagger/spi/artifact-javadoc.jar \ + "" _deploy \ + "" \ java/dagger/android/android.aar \ java/dagger/android/pom.xml \ java/dagger/android/libandroid-src.jar \ - java/dagger/android/android-javadoc.jar + java/dagger/android/android-javadoc.jar \ + "" _deploy \ + "" \ java/dagger/android/android-legacy.aar \ java/dagger/android/legacy-pom.xml \ "" \ + "" \ "" -# b/37741866 and https://github.com/google/dagger/issues/715 -_deploy \ - java/dagger/android/libandroid.jar \ - java/dagger/android/jarimpl-pom.xml \ - java/dagger/android/libandroid-src.jar \ - java/dagger/android/android-javadoc.jar - _deploy \ + "" \ java/dagger/android/support/support.aar \ java/dagger/android/support/pom.xml \ java/dagger/android/support/libsupport-src.jar \ - java/dagger/android/support/support-javadoc.jar + java/dagger/android/support/support-javadoc.jar \ + "" _deploy \ + "" \ java/dagger/android/support/support-legacy.aar \ java/dagger/android/support/legacy-pom.xml \ "" \ + "" \ "" _deploy \ + "" \ shaded_android_processor.jar \ java/dagger/android/processor/pom.xml \ java/dagger/android/processor/libprocessor-src.jar \ - java/dagger/android/processor/processor-javadoc.jar + java/dagger/android/processor/processor-javadoc.jar \ + "" _deploy \ + "" \ java/dagger/grpc/server/libserver.jar \ java/dagger/grpc/server/server-pom.xml \ java/dagger/grpc/server/libserver-src.jar \ - java/dagger/grpc/server/javadoc.jar + java/dagger/grpc/server/javadoc.jar \ + "" _deploy \ + "" \ java/dagger/grpc/server/libannotations.jar \ java/dagger/grpc/server/annotations-pom.xml \ java/dagger/grpc/server/libannotations-src.jar \ - java/dagger/grpc/server/javadoc.jar + java/dagger/grpc/server/javadoc.jar \ + "" _deploy \ + "" \ shaded_grpc_server_processor.jar \ java/dagger/grpc/server/processor/pom.xml \ java/dagger/grpc/server/processor/libprocessor-src.jar \ - java/dagger/grpc/server/processor/javadoc.jar + java/dagger/grpc/server/processor/javadoc.jar \ + "" _deploy \ + "" \ java/dagger/lint/lint-artifact.jar \ java/dagger/lint/lint-pom.xml \ java/dagger/lint/lint-artifact-src.jar \ - java/dagger/lint/lint-artifact-javadoc.jar + java/dagger/lint/lint-artifact-javadoc.jar \ + "" _deploy \ + "" \ java/dagger/lint/lint-android-artifact.aar \ java/dagger/lint/lint-android-pom.xml \ "" \ + "" \ "" diff --git a/util/deploy-hilt-gradle-plugin.sh b/util/deploy-hilt-gradle-plugin.sh new file mode 100755 index 000000000..203049434 --- /dev/null +++ b/util/deploy-hilt-gradle-plugin.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -eu + +readonly MVN_GOAL="$1" +readonly VERSION_NAME="$2" +shift 2 +readonly EXTRA_MAVEN_ARGS=("$@") + +# Builds and deploy the Gradle plugin. +_deploy_plugin() { + local plugindir=java/dagger/hilt/android/plugin + ./$plugindir/gradlew -p $plugindir --no-daemon clean \ + publishAllPublicationsToMavenRepository -PPublishVersion="$VERSION_NAME" + local outdir=$plugindir/build/repo/com/google/dagger/hilt-android-gradle-plugin/$VERSION_NAME + local markerOutDir=$plugindir/build/repo/com/google/dagger/hilt/android/com.google.dagger.hilt.android.gradle.plugin/$VERSION_NAME + # When building '-SNAPSHOT' versions in gradle, the filenames replaces + # '-SNAPSHOT' with timestamps, so we need to disambiguate by finding each file + # to deploy. See: https://stackoverflow.com/questions/54182823/ + local suffix + if [[ "$VERSION_NAME" == *"-SNAPSHOT" ]]; then + # Gets the timestamp part out of the name to be used as suffix. + # Timestamp format is ########.######-#. + suffix=$(find $outdir -name "*.pom" | grep -Eo '[0-9]{8}\.[0-9]{6}-[0-9]{1}') + else + suffix=$VERSION_NAME + fi + mvn "$MVN_GOAL" \ + -Dfile="$(find $outdir -name "*-$suffix.jar")" \ + -DpomFile="$(find $outdir -name "*-$suffix.pom")" \ + -Dsources="$(find $outdir -name "*-$suffix-sources.jar")" \ + -Djavadoc="$(find $outdir -name "*-$suffix-javadoc.jar")" \ + "${EXTRA_MAVEN_ARGS[@]:+${EXTRA_MAVEN_ARGS[@]}}" + mvn "$MVN_GOAL" \ + -Dfile="$(find $markerOutDir -name "*-$suffix.pom")" \ + -DpomFile="$(find $markerOutDir -name "*-$suffix.pom")" \ + "${EXTRA_MAVEN_ARGS[@]:+${EXTRA_MAVEN_ARGS[@]}}" +} + +# Gradle Plugin is built with Gradle, but still deployed via Maven (mvn) +_deploy_plugin
\ No newline at end of file diff --git a/util/deploy-hilt.sh b/util/deploy-hilt.sh index 3c25c10b7..325ef13f2 100755 --- a/util/deploy-hilt.sh +++ b/util/deploy-hilt.sh @@ -14,75 +14,63 @@ readonly EXTRA_MAVEN_ARGS=("$@") # parameter, if provided then javadoc must also be provided. # @param {string} javadoc the java doc jar of the library. This is an optional # parameter, if provided then srcjar must also be provided. +# @param {string} module_name the JPMS module name to include in the jar. This +# is an optional parameter and can only be used with jar files. _deploy() { - local library=$1 - local pomfile=$2 - local srcjar=$3 - local javadoc=$4 + local shaded_rules=$1 + local library=$2 + local pomfile=$3 + local srcjar=$4 + local javadoc=$5 + local module_name=$6 bash $(dirname $0)/deploy-library.sh \ + "$shaded_rules" \ "$library" \ "$pomfile" \ "$srcjar" \ "$javadoc" \ + "$module_name" \ "$MVN_GOAL" \ "$VERSION_NAME" \ "${EXTRA_MAVEN_ARGS[@]:+${EXTRA_MAVEN_ARGS[@]}}" } _deploy \ + "" \ java/dagger/hilt/android/artifact.aar \ java/dagger/hilt/android/pom.xml \ java/dagger/hilt/android/artifact-src.jar \ - java/dagger/hilt/android/artifact-javadoc.jar + java/dagger/hilt/android/artifact-javadoc.jar \ + "" _deploy \ + "" \ java/dagger/hilt/android/testing/artifact.aar \ java/dagger/hilt/android/testing/pom.xml \ java/dagger/hilt/android/testing/artifact-src.jar \ - java/dagger/hilt/android/testing/artifact-javadoc.jar + java/dagger/hilt/android/testing/artifact-javadoc.jar \ + "" _deploy \ + "com.google.auto.common,dagger.hilt.android.shaded.auto.common" \ java/dagger/hilt/processor/artifact.jar \ java/dagger/hilt/processor/pom.xml \ java/dagger/hilt/processor/artifact-src.jar \ - java/dagger/hilt/processor/artifact-javadoc.jar + java/dagger/hilt/processor/artifact-javadoc.jar \ + "" _deploy \ + "com.google.auto.common,dagger.hilt.android.shaded.auto.common" \ java/dagger/hilt/android/processor/artifact.jar \ java/dagger/hilt/android/processor/pom.xml \ java/dagger/hilt/android/processor/artifact-src.jar \ - java/dagger/hilt/android/processor/artifact-javadoc.jar + java/dagger/hilt/android/processor/artifact-javadoc.jar \ + "" _deploy \ + "" \ java/dagger/hilt/artifact-core.jar \ java/dagger/hilt/pom.xml \ java/dagger/hilt/artifact-core-src.jar \ - java/dagger/hilt/artifact-core-javadoc.jar - -# Builds and deploy the Gradle plugin. -_deploy_plugin() { - local plugindir=java/dagger/hilt/android/plugin - ./$plugindir/gradlew -p $plugindir --no-daemon clean \ - publishAllPublicationsToMavenRepository -PPublishVersion="$VERSION_NAME" - local outdir=$plugindir/build/repo/com/google/dagger/hilt-android-gradle-plugin/$VERSION_NAME - # When building '-SNAPSHOT' versions in gradle, the filenames replaces - # '-SNAPSHOT' with timestamps, so we need to disambiguate by finding each file - # to deploy. See: https://stackoverflow.com/questions/54182823/ - local suffix - if [[ "$VERSION_NAME" == *"-SNAPSHOT" ]]; then - # Gets the timestamp part out of the name to be used as suffix. - # Timestamp format is ########.######-#. - suffix=$(find $outdir -name "*.pom" | grep -Eo '[0-9]{8}\.[0-9]{6}-[0-9]{1}') - else - suffix=$VERSION_NAME - fi - mvn "$MVN_GOAL" \ - -Dfile="$(find $outdir -name "*-$suffix.jar")" \ - -DpomFile="$(find $outdir -name "*-$suffix.pom")" \ - -Dsources="$(find $outdir -name "*-$suffix-sources.jar")" \ - -Djavadoc="$(find $outdir -name "*-$suffix-javadoc.jar")" \ - "${EXTRA_MAVEN_ARGS[@]:+${EXTRA_MAVEN_ARGS[@]}}" -} - -# Gradle Plugin is built with Gradle, but still deployed via Maven (mvn) -_deploy_plugin + java/dagger/hilt/artifact-core-javadoc.jar \ + "" diff --git a/util/deploy-library.sh b/util/deploy-library.sh index a744402ba..02962946d 100755 --- a/util/deploy-library.sh +++ b/util/deploy-library.sh @@ -9,18 +9,29 @@ set -eu # parameter, if provided then javadoc must also be provided. # @param {string} javadoc the java doc jar of the library. This is an optional # parameter, if provided then srcjar must also be provided. +# @param {string} module_name the JPMS module name to include in the jar. This +# is an optional parameter and can only be used with jar files. deploy_library() { - local library=$1 - local pomfile=$2 - local srcjar=$3 - local javadoc=$4 - local mvn_goal=$5 - local version_name=$6 - shift 6 + local shaded_rules=$1 + local library=$2 + local pomfile=$3 + local srcjar=$4 + local javadoc=$5 + local module_name=$6 + local mvn_goal=$7 + local version_name=$8 + shift 8 local extra_maven_args=("$@") - bazel build --define=pom_version="$version_name" \ - $library $pomfile + bazel build --define=pom_version="$version_name" $library $pomfile + + # Shade the library if shaded_rules exist + if [[ ! -z "$shaded_rules" ]]; then + bash $(dirname $0)/shade-library.sh \ + $(bazel_output_file $library) $shaded_rules + # The output jar name is the same as the input library appended with -shaded + library="${library%.*}-shaded.${library##*.}" + fi # TODO(bcorso): Consider moving this into the "gen_maven_artifact" macro, this # requires having the version checked-in for the build system. @@ -29,6 +40,12 @@ deploy_library() { $(bazel_output_file $pomfile) \ $version_name + # TODO(bcorso): Consider moving this into the "gen_maven_artifact" macro once + # all our targets are using gen_maven_artifact + add_automatic_module_name_manifest_entry \ + $(bazel_output_file $library) \ + "${module_name}" + if [ -n "$srcjar" ] && [ -n "$javadoc" ] ; then bazel build --define=pom_version="$version_name" \ $srcjar $javadoc @@ -81,6 +98,22 @@ add_tracking_version() { fi } +add_automatic_module_name_manifest_entry() { + local library=$1 + local module_name=$2 + if [ -n "$module_name" ] ; then + if [[ $library =~ \.jar$ ]]; then + local temp_dir=$(mktemp -d) + echo "Automatic-Module-Name: ${module_name}" > $temp_dir/module_name_file + # The "m" flag is specifically for adding manifest entries. + jar ufm $library $temp_dir/module_name_file + else + echo "Could not add module name to $library" + exit 1 + fi + fi +} + find_pom_value() { local pomfile=$1 local attribute=$2 diff --git a/util/deploy-to-maven-central.sh b/util/deploy-to-maven-central.sh index 1a71f5fed..0bf4b64cc 100755 --- a/util/deploy-to-maven-central.sh +++ b/util/deploy-to-maven-central.sh @@ -10,26 +10,22 @@ readonly KEY=$1 readonly VERSION_NAME=$2 shift 2 -if [[ ! "$VERSION_NAME" =~ ^2\. ]]; then - echo 'Version name must begin with "2."' - exit 2 +$(dirname $0)/validate-dagger-version.sh "$VERSION_NAME" + +BAZEL_VERSION=$(bazel --version) +if [[ $BAZEL_VERSION != *"4.2.1"* ]]; then + echo "Must use Bazel version 4.2.1" + exit 4 fi -if [[ "$VERSION_NAME" =~ " " ]]; then - echo "Version name must not have any spaces" - exit 3 +if [[ -z "${ANDROID_HOME}" ]]; then + echo "ANDROID_HOME environment variable must be set" + exit 5 fi bash $(dirname $0)/run-local-tests.sh -bash $(dirname $0)/deploy-dagger.sh \ - "gpg:sign-and-deploy-file" \ - "$VERSION_NAME" \ - "-DrepositoryId=sonatype-nexus-staging" \ - "-Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/" \ - "-Dgpg.keyname=${KEY}" - -bash $(dirname $0)/deploy-hilt.sh \ +bash $(dirname $0)/deploy-all.sh \ "gpg:sign-and-deploy-file" \ "$VERSION_NAME" \ "-DrepositoryId=sonatype-nexus-staging" \ @@ -39,30 +35,8 @@ bash $(dirname $0)/deploy-hilt.sh \ # Note: we detach from head before making any sed changes to avoid commiting # a particular version to master. git checkout --detach - -# Set the version string that is used as a tag in all of our libraries. If -# another repo depends on a versioned tag of Dagger, their java_library.tags -# should match the versioned release. -sed -i s/'${project.version}'/"${VERSION_NAME}"/g build_defs.bzl - -# Note: We avoid commiting until after deploying in case deploying fails and -# we need to run the script again. -git commit -m "${VERSION_NAME} release" build_defs.bzl -git tag -a -m "Dagger ${VERSION_NAME}" dagger-"${VERSION_NAME}" -git push origin tag dagger-"${VERSION_NAME}" - +bash $(dirname $0)/publish-tagged-release.sh $VERSION_NAME # Switch back to the original HEAD git checkout - -# Publish javadocs to gh-pages -bazel build //:user-docs.jar -git clone --quiet --branch gh-pages \ - https://github.com/google/dagger gh-pages > /dev/null -cd gh-pages -unzip ../bazel-bin/user-docs.jar -d api/$VERSION_NAME -rm -rf api/$VERSION_NAME/META-INF/ -git add api/$VERSION_NAME -git commit -m "$VERSION_NAME docs" -git push origin gh-pages -cd .. -rm -rf gh-pages +bash $(dirname $0)/publish-tagged-docs.sh $VERSION_NAME diff --git a/util/install-local-snapshot.sh b/util/install-local-snapshot.sh index be6030c74..2225e51d6 100755 --- a/util/install-local-snapshot.sh +++ b/util/install-local-snapshot.sh @@ -4,11 +4,7 @@ set -eu echo -e "Installing maven snapshot locally...\n" -bash $(dirname $0)/deploy-dagger.sh \ - "install:install-file" \ - "LOCAL-SNAPSHOT" - -bash $(dirname $0)/deploy-hilt.sh \ +bash $(dirname $0)/deploy-all.sh \ "install:install-file" \ "LOCAL-SNAPSHOT" diff --git a/util/latest-dagger-version.sh b/util/latest-dagger-version.sh new file mode 100755 index 000000000..55b3efde8 --- /dev/null +++ b/util/latest-dagger-version.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +set -eu + +function github-rest-api { + local GITHUB_REST_API=$1 + + local GITHUB_API_HEADER_ACCEPT="Accept: application/vnd.github.v3+json" + + curl -s $GITHUB_REST_API -H $GITHUB_API_HEADER_ACCEPT +} + +function github-latest-release-tag { + local REPO_NAME=$1 + + # Grab the last two latest releases: + # (We skip the latest release if we haven't set release notes yet). + local RELEASE_API="https://api.github.com/repos/$REPO_NAME/releases?per_page=2" + + # This gets the latest release info (as json) from github. + local RELEASE_JSON=$(github-rest-api $RELEASE_API) + + # This pulls out the "body" from the json (i.e. the release notes) + local RELEASE_NOTES=$(echo $RELEASE_JSON | jq '.[0].body') + + if [ "$RELEASE_NOTES" ] + then + # Return the latest release tag + echo $RELEASE_JSON | jq '.[0].tag_name' + else + # If there are no release notes in the latest release then we use the + # 2nd most latest version since we don't want to update the version until + # the release notes are set. + echo "Ignoring the latest release since the release notes have not been set." + echo "Using the previous release's version as latest." + + # Return the 2nd most recent release tag + echo $RELEASE_JSON | jq '.[1].tag_name' + fi +} + +function dagger-latest-release { + # Get the latest Dagger release tag, e.g. "dagger-2.31.2" or "dagger-2.32" + local DAGGER_RELEASE_TAG=$(github-latest-release-tag "google/dagger") + + # Converts the "tag_name" to a version, e.g. "dagger-2.32" => "2.32" + echo $DAGGER_RELEASE_TAG | grep -oP "(?<=dagger-)\d+\.\d+(\.\d+)?" +} + +type jq >/dev/null 2>&1 || { + echo >&2 "jq is not installed. Try 'sudo apt-get install jq'."; + exit 1; +} + +dagger-latest-release diff --git a/util/publish-snapshot-on-commit.sh b/util/publish-snapshot-on-commit.sh deleted file mode 100755 index 63cc3eaa1..000000000 --- a/util/publish-snapshot-on-commit.sh +++ /dev/null @@ -1,27 +0,0 @@ -# see https://coderwall.com/p/9b_lfq - -set -eux - -if [ "$GITHUB_REPOSITORY" == "google/dagger" ] && \ - [ "$GITHUB_EVENT_NAME" == "push" ] && \ - [ "$GITHUB_REF" == "refs/heads/master" ]; then - echo -e "Publishing maven snapshot...\n" - - bash $(dirname $0)/deploy-dagger.sh \ - "deploy:deploy-file" \ - "HEAD-SNAPSHOT" \ - "-DrepositoryId=sonatype-nexus-snapshots" \ - "-Durl=https://oss.sonatype.org/content/repositories/snapshots" \ - "--settings=$(dirname $0)/settings.xml" - - bash $(dirname $0)/deploy-hilt.sh \ - "deploy:deploy-file" \ - "HEAD-SNAPSHOT" \ - "-DrepositoryId=sonatype-nexus-snapshots" \ - "-Durl=https://oss.sonatype.org/content/repositories/snapshots" \ - "--settings=$(dirname $0)/settings.xml" - - echo -e "Published maven snapshot" -else - echo -e "Not publishing snapshot for branch=${$GITHUB_REF}" -fi diff --git a/util/publish-tagged-docs.sh b/util/publish-tagged-docs.sh new file mode 100755 index 000000000..8c5cddfa0 --- /dev/null +++ b/util/publish-tagged-docs.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# TODO(bcorso): Consider sharing this script with utils/generate-latest-docs.sh + +set -eux + +if [ $# -lt 1 ]; then + echo "usage $0 <version-name>" + exit 1; +fi +readonly VERSION_NAME=$1 +shift 1 + +$(dirname $0)/validate-dagger-version.sh "$VERSION_NAME" + +# Publish javadocs to gh-pages +bazel build //:user-docs.jar + +# If a token exists, then use the token to clone the repo. This allows our +# automated workflows to commit without manually authenticating. +if [[ ! -z "$GH_TOKEN" ]]; then + git clone --quiet --branch=gh-pages https://x-access-token:${GH_TOKEN}@github.com/google/dagger gh-pages > /dev/null +else + git clone --quiet --branch=gh-pages https://github.com/google/dagger gh-pages > /dev/null +fi + +cd gh-pages +unzip ../bazel-bin/user-docs.jar -d api/$VERSION_NAME +rm -rf api/$VERSION_NAME/META-INF/ +git add api/$VERSION_NAME +git commit -m "$VERSION_NAME docs" +git push origin gh-pages +cd .. +rm -rf gh-pages diff --git a/util/publish-tagged-release.sh b/util/publish-tagged-release.sh new file mode 100755 index 000000000..11a131f50 --- /dev/null +++ b/util/publish-tagged-release.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -eux + +if [ $# -lt 1 ]; then + echo "usage $0 <version-name>" + exit 1; +fi +readonly VERSION_NAME=$1 +shift 1 + +$(dirname $0)/validate-dagger-version.sh "$VERSION_NAME" + +# Set the version string that is used as a tag in all of our libraries. If +# another repo depends on a versioned tag of Dagger, their java_library.tags +# should match the versioned release. +sed -i s/'${project.version}'/"${VERSION_NAME}"/g build_defs.bzl + +# Note: We avoid commiting until after deploying in case deploying fails and +# we need to run the script again. +git commit -m "${VERSION_NAME} release" build_defs.bzl +git tag -a -m "Dagger ${VERSION_NAME}" dagger-"${VERSION_NAME}" +git push origin tag dagger-"${VERSION_NAME}" diff --git a/util/run-local-emulator-tests.sh b/util/run-local-emulator-tests.sh index 347a632af..30b31bedb 100755 --- a/util/run-local-emulator-tests.sh +++ b/util/run-local-emulator-tests.sh @@ -11,14 +11,8 @@ readonly GRADLE_PROJECTS=( ) for project in "${GRADLE_PROJECTS[@]}"; do echo "Running gradle Android emulator tests for $project" - ./$project/gradlew -p $project connectedAndroidTest --continue --no-daemon --stacktrace + ./$project/gradlew -p $project connectedAndroidTest --continue --no-daemon --stacktrace --configuration-cache done -# Run emulator tests in a project with configuration cache enabled -# TODO(danysantiago): Once AGP 4.2.0 is stable, remove these project and enable -# config cache in the other test projects. -readonly CONFIG_CACHE_PROJECT="javatests/artifacts/hilt-android/gradleConfigCache" -./$CONFIG_CACHE_PROJECT/gradlew -p $CONFIG_CACHE_PROJECT connectedAndroidTest --continue --no-daemon --stacktrace --configuration-cache - # Close logcat if [ -n "$LOGCAT_PID" ] ; then kill $LOGCAT_PID; fi diff --git a/util/run-local-gradle-android-tests.sh b/util/run-local-gradle-android-tests.sh index a9418a589..6ff9220a1 100755 --- a/util/run-local-gradle-android-tests.sh +++ b/util/run-local-gradle-android-tests.sh @@ -4,19 +4,20 @@ set -ex readonly AGP_VERSION_INPUT=$1 readonly ANDROID_GRADLE_PROJECTS=( - "java/dagger/example/gradle/android/simple" "javatests/artifacts/dagger-android/simple" "javatests/artifacts/hilt-android/simple" "javatests/artifacts/hilt-android/simpleKotlin" ) for project in "${ANDROID_GRADLE_PROJECTS[@]}"; do echo "Running gradle tests for $project with AGP $AGP_VERSION_INPUT" - AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project buildDebug --no-daemon --stacktrace - AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project testDebug --continue --no-daemon --stacktrace + # Enable config cache if AGP is 4.2.0 or greater. + # Note that this is a lexicographical comparison. + if [[ "$AGP_VERSION_INPUT" > "4.1.0" ]] + then + CONFIG_CACHE_ARG="--configuration-cache" + else + CONFIG_CACHE_ARG="" + fi + AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project buildDebug --no-daemon --stacktrace $CONFIG_CACHE_ARG + AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project testDebug --continue --no-daemon --stacktrace $CONFIG_CACHE_ARG done - -# Run gradle tests in a project with configuration cache enabled -# TODO(danysantiago): Once AGP 4.2.0 is stable, remove these project and enable -# config cache in the other test projects. -readonly CONFIG_CACHE_PROJECT="javatests/artifacts/hilt-android/gradleConfigCache" -./$CONFIG_CACHE_PROJECT/gradlew -p $CONFIG_CACHE_PROJECT assembleDebug --no-daemon --stacktrace --configuration-cache diff --git a/util/run-local-gradle-tests.sh b/util/run-local-gradle-tests.sh index 479158d8f..ecbbd352b 100755 --- a/util/run-local-gradle-tests.sh +++ b/util/run-local-gradle-tests.sh @@ -3,9 +3,8 @@ set -ex readonly GRADLE_PROJECTS=( - "java/dagger/example/gradle/simple" - "java/dagger/hilt/android/plugin" - "javatests/artifacts/dagger/simple" + "javatests/artifacts/dagger" + "javatests/artifacts/hilt-android/pluginMarker" ) for project in "${GRADLE_PROJECTS[@]}"; do echo "Running gradle tests for $project" diff --git a/util/run-local-tests.sh b/util/run-local-tests.sh index ea78671fe..3d7ba90ea 100755 --- a/util/run-local-tests.sh +++ b/util/run-local-tests.sh @@ -17,5 +17,7 @@ pushd examples/maven && mvn compile && popd # Run local gradle tests util/run-local-gradle-tests.sh util/run-local-gradle-android-tests.sh "4.1.0" -util/run-local-gradle-android-tests.sh "4.2.0-beta04" +util/run-local-gradle-android-tests.sh "4.2.0" +util/run-local-gradle-android-tests.sh "7.0.0" + diff --git a/util/settings.xml b/util/settings.xml deleted file mode 100644 index 91f444b22..000000000 --- a/util/settings.xml +++ /dev/null @@ -1,9 +0,0 @@ -<settings> - <servers> - <server> - <id>sonatype-nexus-snapshots</id> - <username>${env.CI_DEPLOY_USERNAME}</username> - <password>${env.CI_DEPLOY_PASSWORD}</password> - </server> - </servers> -</settings> diff --git a/util/shade-library.sh b/util/shade-library.sh new file mode 100644 index 000000000..73767d63a --- /dev/null +++ b/util/shade-library.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -eux + +readonly INPUT_JAR=$1 +readonly SHADE_RULES=$2 + +_shade_libary() { + local shader=$(dirname $0)/../tools/shader + local output="${INPUT_JAR%.*}-shaded.${INPUT_JAR##*.}" + + ./$shader/gradlew -p $shader shadowJar \ + -PinputJar="../../$INPUT_JAR" \ + -PshadedRules=$SHADE_RULES + + # Copy the shaded jar to the specified output + cp $shader/build/libs/shader.jar $output +} + +_shade_libary diff --git a/util/shasum.sh b/util/shasum.sh new file mode 100755 index 000000000..a71c29878 --- /dev/null +++ b/util/shasum.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -eu + +if [ $# -lt 1 ]; then + echo "usage $0 <version-name>" + exit 1; +fi +readonly VERSION_NAME=$1 +shift 1 + +$(dirname $0)/validate-dagger-version.sh "$VERSION_NAME" + +pushd $(mktemp -d) +wget https://github.com/google/dagger/archive/dagger-$VERSION_NAME.zip -P . +OUTPUT=$(shasum -a 256 dagger-$VERSION_NAME.zip) +echo "SHA sum: $OUTPUT" +popd diff --git a/util/validate-dagger-version.sh b/util/validate-dagger-version.sh new file mode 100755 index 000000000..a595aba2a --- /dev/null +++ b/util/validate-dagger-version.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -eu + +if [ $# -lt 1 ]; then + echo "usage $0 <version-name>" + exit 1; +fi +readonly VERSION_NAME=$1 +shift 1 + +if [[ ! "$VERSION_NAME" =~ ^2\. ]]; then + echo 'Version name must begin with "2."' + exit 2 +fi + +if [[ "$VERSION_NAME" =~ " " ]]; then + echo "Version name must not have any spaces" + exit 3 +fi diff --git a/workspace_defs.bzl b/workspace_defs.bzl index 2a63e433f..922978201 100644 --- a/workspace_defs.bzl +++ b/workspace_defs.bzl @@ -16,36 +16,43 @@ load("//:build_defs.bzl", "POM_VERSION") -_DAGGER_VERSION = POM_VERSION -_HILT_VERSION = POM_VERSION +# For tagged releases, the POM_VERSION will be set to the version of the release. +# However, for CI testing the POM_VERSION will not be set, so we use the +# HEAD-SNAPSHOT artifacts instead. +# TODO(bcorso): Ideally, we would use the LOCAL-SNAPSHOT artifacts for CI testing; +# however, maven_install doesn't work with local maven repositories +# (See issue: https://github.com/bazelbuild/rules_jvm_external/issues/305). +_VERSION = POM_VERSION if POM_VERSION != "${project.version}" else "HEAD-SNAPSHOT" DAGGER_ARTIFACTS = [ - "com.google.dagger:dagger:" + _DAGGER_VERSION, - "com.google.dagger:dagger-compiler:" + _DAGGER_VERSION, - "com.google.dagger:dagger-producers:" + _DAGGER_VERSION, - "com.google.dagger:dagger-spi:" + _DAGGER_VERSION, + "com.google.dagger:dagger:" + _VERSION, + "com.google.dagger:dagger-compiler:" + _VERSION, + "com.google.dagger:dagger-producers:" + _VERSION, + "com.google.dagger:dagger-spi:" + _VERSION, ] DAGGER_ANDROID_ARTIFACTS = [ - "com.google.dagger:dagger-android-processor:" + _DAGGER_VERSION, - "com.google.dagger:dagger-android-support:" + _DAGGER_VERSION, - "com.google.dagger:dagger-android:" + _DAGGER_VERSION, + "com.google.dagger:dagger-android-processor:" + _VERSION, + "com.google.dagger:dagger-android-support:" + _VERSION, + "com.google.dagger:dagger-android:" + _VERSION, ] HILT_ANDROID_ARTIFACTS = [ "androidx.test:core:1.1.0", # Export for ApplicationProvider "javax.annotation:jsr250-api:1.0", # Export for @Generated "androidx.annotation:annotation:1.1.0", # Export for @CallSuper/@Nullable - "com.google.dagger:dagger:" + _DAGGER_VERSION, - "com.google.dagger:dagger-compiler:" + _DAGGER_VERSION, - "com.google.dagger:hilt-android:" + _HILT_VERSION, - "com.google.dagger:hilt-android-testing:" + _HILT_VERSION, - "com.google.dagger:hilt-android-compiler:" + _HILT_VERSION, + "com.google.dagger:dagger:" + _VERSION, + "com.google.dagger:dagger-compiler:" + _VERSION, + "com.google.dagger:hilt-android:" + _VERSION, + "com.google.dagger:hilt-android-testing:" + _VERSION, + "com.google.dagger:hilt-android-compiler:" + _VERSION, + "com.google.dagger:hilt-core:" + _VERSION, ] DAGGER_REPOSITORIES = [ "https://maven.google.com", "https://repo1.maven.org/maven2", + "https://oss.sonatype.org/content/repositories/snapshots", ] DAGGER_ANDROID_REPOSITORIES = DAGGER_REPOSITORIES @@ -162,9 +169,11 @@ def hilt_android_rules(repo_name = "@maven"): ":hilt_aggregated_deps_processor", ":hilt_alias_of_processor", ":hilt_define_component_processor", + ":hilt_early_entry_points_processor", ":hilt_generates_root_input_processor", ":hilt_originating_element_processor", ":hilt_root_processor", + ":hilt_component_tree_deps_processor", ":hilt_view_model_processor", ], visibility = ["//visibility:public"], @@ -173,6 +182,7 @@ def hilt_android_rules(repo_name = "@maven"): "%s//:javax_inject_javax_inject" % repo_name, # For @Inject "%s//:androidx_annotation_annotation" % repo_name, # For @CallSuper "%s//:com_google_dagger_hilt_android" % repo_name, + "%s//:com_google_dagger_hilt_core" % repo_name, "%s//:javax_annotation_jsr250_api" % repo_name, # For @Generated ], ) @@ -217,6 +227,13 @@ def hilt_android_rules(repo_name = "@maven"): ) native.java_plugin( + name = "hilt_early_entry_points_processor", + generates_api = 1, + processor_class = "dagger.hilt.processor.internal.earlyentrypoint.EarlyEntryPointProcessor", + deps = ["%s//:com_google_dagger_hilt_android_compiler" % repo_name], + ) + + native.java_plugin( name = "hilt_generates_root_input_processor", generates_api = 1, processor_class = "dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputProcessor", @@ -238,6 +255,13 @@ def hilt_android_rules(repo_name = "@maven"): ) native.java_plugin( + name = "hilt_component_tree_deps_processor", + generates_api = 1, + processor_class = "dagger.hilt.processor.internal.root.ComponentTreeDepsProcessor", + deps = ["%s//:com_google_dagger_hilt_android_compiler" % repo_name], + ) + + native.java_plugin( name = "hilt_view_model_processor", generates_api = 1, processor_class = "dagger.hilt.android.processor.internal.viewmodel.ViewModelProcessor", @@ -252,7 +276,6 @@ def hilt_android_rules(repo_name = "@maven"): exported_plugins = [ ":hilt_bind_value_processor", ":hilt_custom_test_application_processor", - ":hilt_testroot_processor", ":hilt_uninstall_modules_processor", ], visibility = ["//visibility:public"], @@ -278,15 +301,8 @@ def hilt_android_rules(repo_name = "@maven"): ) native.java_plugin( - name = "hilt_testroot_processor", - generates_api = 1, - processor_class = "dagger.hilt.android.processor.internal.testroot.TestRootProcessor", - deps = ["%s//:com_google_dagger_hilt_android_compiler" % repo_name], - ) - - native.java_plugin( name = "hilt_uninstall_modules_processor", generates_api = 1, - processor_class = "dagger.hilt.android.processor.internal.uninstallmodules.UninstallModulesProcessor", + processor_class = "dagger.hilt.processor.internal.uninstallmodules.UninstallModulesProcessor", deps = ["%s//:com_google_dagger_hilt_android_compiler" % repo_name], ) |