aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-11 05:13:56 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-11 05:13:56 +0000
commitef0f2ca72e351217f1422d85850b6010764750d0 (patch)
treeb7c0ba640fd38e96068089eb744b59b100cfe192
parent8a8c1f2648124e7e7612fdc620ee25b5e8e3e902 (diff)
parent75c0b4f87df6bec9cdd5fbab2b29acf33128926c (diff)
downloadturbine-android13-mainline-networking-release.tar.gz
Change-Id: I05003cd1bb11a6b89bea9b94e5bb6903dc03ccd6
-rw-r--r--.gitattributes1
-rw-r--r--.github/dependabot.yml6
-rw-r--r--.github/workflows/ci.yml69
-rw-r--r--.mvn/jvm.config10
-rw-r--r--.travis.yml27
-rwxr-xr-xandroid-annotation-stubs/gen_annotations.sh4
-rw-r--r--android-annotation-stubs/src/org/jspecify/nullness/NullMarked.java (renamed from android-annotation-stubs/src/org/checkerframework/checker/nullness/compatqual/NullableDecl.java)4
-rw-r--r--android-annotation-stubs/src/org/jspecify/nullness/Nullable.java (renamed from android-annotation-stubs/src/org/checkerframework/checker/nullness/qual/Nullable.java)2
-rw-r--r--appveyor.yml31
-rw-r--r--java/com/google/common/escape/SourceCodeEscapers.java8
-rw-r--r--java/com/google/common/escape/package-info.java19
-rw-r--r--java/com/google/turbine/binder/Binder.java88
-rw-r--r--java/com/google/turbine/binder/CanonicalTypeBinder.java83
-rw-r--r--java/com/google/turbine/binder/ClassPath.java2
-rw-r--r--java/com/google/turbine/binder/ClassPathBinder.java9
-rw-r--r--java/com/google/turbine/binder/CompUnitPreprocessor.java13
-rw-r--r--java/com/google/turbine/binder/ConstBinder.java50
-rw-r--r--java/com/google/turbine/binder/ConstEvaluator.java758
-rw-r--r--java/com/google/turbine/binder/CtSymClassBinder.java42
-rw-r--r--java/com/google/turbine/binder/DisambiguateTypeAnnotations.java49
-rw-r--r--java/com/google/turbine/binder/FileManagerClassBinder.java235
-rw-r--r--java/com/google/turbine/binder/HierarchyBinder.java15
-rw-r--r--java/com/google/turbine/binder/JimageClassBinder.java24
-rw-r--r--java/com/google/turbine/binder/ModuleBinder.java17
-rw-r--r--java/com/google/turbine/binder/Processing.java234
-rw-r--r--java/com/google/turbine/binder/Resolve.java55
-rw-r--r--java/com/google/turbine/binder/TypeBinder.java260
-rw-r--r--java/com/google/turbine/binder/bound/AnnotationMetadata.java13
-rw-r--r--java/com/google/turbine/binder/bound/BoundClass.java2
-rw-r--r--java/com/google/turbine/binder/bound/EnumConstantValue.java3
-rw-r--r--java/com/google/turbine/binder/bound/HeaderBoundClass.java4
-rw-r--r--java/com/google/turbine/binder/bound/ModuleInfo.java13
-rw-r--r--java/com/google/turbine/binder/bound/PackageSourceBoundClass.java3
-rw-r--r--java/com/google/turbine/binder/bound/SourceBoundClass.java7
-rw-r--r--java/com/google/turbine/binder/bound/SourceHeaderBoundClass.java9
-rw-r--r--java/com/google/turbine/binder/bound/SourceModuleInfo.java2
-rw-r--r--java/com/google/turbine/binder/bound/SourceTypeBoundClass.java40
-rw-r--r--java/com/google/turbine/binder/bound/TurbineAnnotationValue.java3
-rw-r--r--java/com/google/turbine/binder/bound/TurbineClassValue.java3
-rw-r--r--java/com/google/turbine/binder/bound/TypeBoundClass.java93
-rw-r--r--java/com/google/turbine/binder/bound/package-info.java19
-rw-r--r--java/com/google/turbine/binder/bytecode/BytecodeBinder.java22
-rw-r--r--java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java117
-rw-r--r--java/com/google/turbine/binder/bytecode/package-info.java18
-rw-r--r--java/com/google/turbine/binder/env/CompoundEnv.java6
-rw-r--r--java/com/google/turbine/binder/env/Env.java16
-rw-r--r--java/com/google/turbine/binder/env/LazyEnv.java18
-rw-r--r--java/com/google/turbine/binder/env/SimpleEnv.java8
-rw-r--r--java/com/google/turbine/binder/env/package-info.java19
-rw-r--r--java/com/google/turbine/binder/lookup/CanonicalSymbolResolver.java2
-rw-r--r--java/com/google/turbine/binder/lookup/CompoundScope.java8
-rw-r--r--java/com/google/turbine/binder/lookup/CompoundTopLevelIndex.java16
-rw-r--r--java/com/google/turbine/binder/lookup/ImportIndex.java21
-rw-r--r--java/com/google/turbine/binder/lookup/ImportScope.java9
-rw-r--r--java/com/google/turbine/binder/lookup/MemberImportIndex.java22
-rw-r--r--java/com/google/turbine/binder/lookup/PackageScope.java2
-rw-r--r--java/com/google/turbine/binder/lookup/Scope.java2
-rw-r--r--java/com/google/turbine/binder/lookup/SimpleTopLevelIndex.java25
-rw-r--r--java/com/google/turbine/binder/lookup/TopLevelIndex.java2
-rw-r--r--java/com/google/turbine/binder/lookup/WildImportIndex.java23
-rw-r--r--java/com/google/turbine/binder/lookup/package-info.java19
-rw-r--r--java/com/google/turbine/binder/package-info.java19
-rw-r--r--java/com/google/turbine/binder/sym/ClassSymbol.java4
-rw-r--r--java/com/google/turbine/binder/sym/FieldSymbol.java3
-rw-r--r--java/com/google/turbine/binder/sym/MethodSymbol.java3
-rw-r--r--java/com/google/turbine/binder/sym/ModuleSymbol.java3
-rw-r--r--java/com/google/turbine/binder/sym/PackageSymbol.java3
-rw-r--r--java/com/google/turbine/binder/sym/ParamSymbol.java3
-rw-r--r--java/com/google/turbine/binder/sym/RecordComponentSymbol.java67
-rw-r--r--java/com/google/turbine/binder/sym/Symbol.java1
-rw-r--r--java/com/google/turbine/binder/sym/TyVarSymbol.java3
-rw-r--r--java/com/google/turbine/binder/sym/package-info.java19
-rw-r--r--java/com/google/turbine/bytecode/AnnotationWriter.java23
-rw-r--r--java/com/google/turbine/bytecode/Attribute.java118
-rw-r--r--java/com/google/turbine/bytecode/AttributeWriter.java135
-rw-r--r--java/com/google/turbine/bytecode/ByteReader.java2
-rw-r--r--java/com/google/turbine/bytecode/ClassFile.java161
-rw-r--r--java/com/google/turbine/bytecode/ClassReader.java43
-rw-r--r--java/com/google/turbine/bytecode/ClassWriter.java12
-rw-r--r--java/com/google/turbine/bytecode/ConstantPoolReader.java2
-rw-r--r--java/com/google/turbine/bytecode/LowerAttributes.java34
-rw-r--r--java/com/google/turbine/bytecode/package-info.java19
-rw-r--r--java/com/google/turbine/bytecode/sig/Sig.java14
-rw-r--r--java/com/google/turbine/bytecode/sig/SigParser.java2
-rw-r--r--java/com/google/turbine/bytecode/sig/package-info.java19
-rw-r--r--java/com/google/turbine/deps/Dependencies.java6
-rw-r--r--java/com/google/turbine/deps/Transitive.java26
-rw-r--r--java/com/google/turbine/deps/package-info.java18
-rw-r--r--java/com/google/turbine/diag/LineMap.java10
-rw-r--r--java/com/google/turbine/diag/SourceFile.java3
-rw-r--r--java/com/google/turbine/diag/TurbineDiagnostic.java13
-rw-r--r--java/com/google/turbine/diag/TurbineError.java9
-rw-r--r--java/com/google/turbine/diag/TurbineLog.java26
-rw-r--r--java/com/google/turbine/diag/package-info.java18
-rw-r--r--java/com/google/turbine/lower/Lower.java154
-rw-r--r--java/com/google/turbine/lower/LowerSignature.java35
-rw-r--r--java/com/google/turbine/lower/package-info.java18
-rw-r--r--java/com/google/turbine/main/Main.java54
-rw-r--r--java/com/google/turbine/main/package-info.java18
-rw-r--r--java/com/google/turbine/model/Const.java354
-rw-r--r--java/com/google/turbine/model/TurbineElementType.java3
-rw-r--r--java/com/google/turbine/model/TurbineFlag.java10
-rw-r--r--java/com/google/turbine/model/TurbineTyKind.java3
-rw-r--r--java/com/google/turbine/model/package-info.java18
-rw-r--r--java/com/google/turbine/options/LanguageVersion.java136
-rw-r--r--java/com/google/turbine/options/TurbineOptions.java75
-rw-r--r--java/com/google/turbine/options/TurbineOptionsParser.java95
-rw-r--r--java/com/google/turbine/options/package-info.java18
-rw-r--r--java/com/google/turbine/parse/ConstExpressionParser.java132
-rw-r--r--java/com/google/turbine/parse/Parser.java339
-rw-r--r--java/com/google/turbine/parse/StreamLexer.java41
-rw-r--r--java/com/google/turbine/parse/Token.java4
-rw-r--r--java/com/google/turbine/parse/UnicodeEscapePreprocessor.java20
-rw-r--r--java/com/google/turbine/parse/VariableInitializerParser.java10
-rw-r--r--java/com/google/turbine/parse/package-info.java18
-rw-r--r--java/com/google/turbine/processing/ModelFactory.java24
-rw-r--r--java/com/google/turbine/processing/TurbineAnnotationMirror.java17
-rw-r--r--java/com/google/turbine/processing/TurbineAnnotationProxy.java6
-rw-r--r--java/com/google/turbine/processing/TurbineElement.java179
-rw-r--r--java/com/google/turbine/processing/TurbineElements.java93
-rw-r--r--java/com/google/turbine/processing/TurbineFiler.java3
-rw-r--r--java/com/google/turbine/processing/TurbineMessager.java9
-rw-r--r--java/com/google/turbine/processing/TurbineName.java3
-rw-r--r--java/com/google/turbine/processing/TurbineProcessingEnvironment.java4
-rw-r--r--java/com/google/turbine/processing/TurbineTypeMirror.java17
-rw-r--r--java/com/google/turbine/processing/TurbineTypes.java89
-rw-r--r--java/com/google/turbine/processing/package-info.java18
-rw-r--r--java/com/google/turbine/tree/Pretty.java113
-rw-r--r--java/com/google/turbine/tree/Tree.java176
-rw-r--r--java/com/google/turbine/tree/TurbineModifier.java7
-rw-r--r--java/com/google/turbine/tree/package-info.java19
-rw-r--r--java/com/google/turbine/type/AnnoInfo.java3
-rw-r--r--java/com/google/turbine/type/Type.java18
-rw-r--r--java/com/google/turbine/type/package-info.java18
-rw-r--r--java/com/google/turbine/types/Canonicalize.java15
-rw-r--r--java/com/google/turbine/types/Deannotate.java88
-rw-r--r--java/com/google/turbine/types/Erasure.java15
-rw-r--r--java/com/google/turbine/types/package-info.java18
-rw-r--r--java/com/google/turbine/zip/Zip.java4
-rw-r--r--java/com/google/turbine/zip/package-info.java18
-rw-r--r--javatests/com/google/turbine/binder/BinderErrorTest.java318
-rw-r--r--javatests/com/google/turbine/binder/BinderTest.java84
-rw-r--r--javatests/com/google/turbine/binder/ClassPathBinderTest.java145
-rw-r--r--javatests/com/google/turbine/binder/CtSymClassBinderTest.java49
-rw-r--r--javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java22
-rw-r--r--javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java13
-rw-r--r--javatests/com/google/turbine/bytecode/ClassReaderTest.java42
-rw-r--r--javatests/com/google/turbine/bytecode/ClassWriterTest.java113
-rw-r--r--javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java7
-rw-r--r--javatests/com/google/turbine/deps/AbstractTransitiveTest.java263
-rw-r--r--javatests/com/google/turbine/deps/DependenciesTest.java8
-rw-r--r--javatests/com/google/turbine/deps/TransitiveTest.java267
-rw-r--r--javatests/com/google/turbine/lower/IntegrationTestSupport.java91
-rw-r--r--javatests/com/google/turbine/lower/LongStringIntegrationTest.java105
-rw-r--r--javatests/com/google/turbine/lower/LowerIntegrationTest.java367
-rw-r--r--javatests/com/google/turbine/lower/LowerTest.java173
-rw-r--r--javatests/com/google/turbine/lower/MissingJavaBaseModule.java105
-rw-r--r--javatests/com/google/turbine/lower/ModuleIntegrationTest.java13
-rw-r--r--javatests/com/google/turbine/lower/testdata/ambiguous_identifier.test14
-rw-r--r--javatests/com/google/turbine/lower/testdata/annotation_clinit.test18
-rw-r--r--javatests/com/google/turbine/lower/testdata/array_class_literal.test3
-rw-r--r--javatests/com/google/turbine/lower/testdata/const_operation_order.test13
-rw-r--r--javatests/com/google/turbine/lower/testdata/empty_package_info.test3
-rw-r--r--javatests/com/google/turbine/lower/testdata/record.test46
-rw-r--r--javatests/com/google/turbine/lower/testdata/record2.test27
-rw-r--r--javatests/com/google/turbine/lower/testdata/sealed.test11
-rw-r--r--javatests/com/google/turbine/lower/testdata/string_const.test7
-rw-r--r--javatests/com/google/turbine/lower/testdata/unicode_pkg.test4
-rw-r--r--javatests/com/google/turbine/main/MainTest.java135
-rw-r--r--javatests/com/google/turbine/main/ReducedClasspathTest.java31
-rw-r--r--javatests/com/google/turbine/options/LanguageVersionTest.java147
-rw-r--r--javatests/com/google/turbine/options/TurbineOptionsTest.java90
-rw-r--r--javatests/com/google/turbine/parse/ExpressionParserTest.java16
-rw-r--r--javatests/com/google/turbine/parse/JavacLexer.java4
-rw-r--r--javatests/com/google/turbine/parse/LexerTest.java5
-rw-r--r--javatests/com/google/turbine/parse/ParseErrorTest.java370
-rw-r--r--javatests/com/google/turbine/parse/ParserIntegrationTest.java3
-rw-r--r--javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java31
-rw-r--r--javatests/com/google/turbine/parse/testdata/anno2.input3
-rw-r--r--javatests/com/google/turbine/parse/testdata/record.input53
-rw-r--r--javatests/com/google/turbine/parse/testdata/sealed.input15
-rw-r--r--javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java35
-rw-r--r--javatests/com/google/turbine/processing/ProcessingIntegrationTest.java655
-rw-r--r--javatests/com/google/turbine/processing/TurbineAnnotationMirrorTest.java7
-rw-r--r--javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java21
-rw-r--r--javatests/com/google/turbine/processing/TurbineElementsHidesTest.java318
-rw-r--r--javatests/com/google/turbine/processing/TurbineElementsTest.java8
-rw-r--r--javatests/com/google/turbine/processing/TurbineFilerTest.java35
-rw-r--r--javatests/com/google/turbine/processing/TurbineMessagerTest.java60
-rw-r--r--javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java14
-rw-r--r--javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java37
-rw-r--r--javatests/com/google/turbine/testing/AsmUtils.java10
-rw-r--r--javatests/com/google/turbine/testing/TestClassPaths.java19
-rw-r--r--javatests/com/google/turbine/testing/TestResources.java42
-rw-r--r--javatests/com/google/turbine/zip/ZipTest.java12
-rw-r--r--pom.xml211
196 files changed, 7816 insertions, 2976 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..6313b56
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto eol=lf
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..daec318
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "daily"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..e12698c
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,69 @@
+# Copyright 2020 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ test:
+ name: "JDK ${{ matrix.java }} on ${{ matrix.os }}"
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ ubuntu-latest ]
+ java: [ 17, 11 ]
+ experimental: [ false ]
+ include:
+ # Only test on macos and windows with a single recent JDK to avoid a
+ # combinatorial explosion of test configurations.
+ - os: macos-latest
+ java: 17
+ experimental: false
+ - os: windows-latest
+ java: 17
+ experimental: false
+ - os: ubuntu-latest
+ java: 18-ea
+ experimental: true
+ runs-on: ${{ matrix.os }}
+ continue-on-error: ${{ matrix.experimental }}
+ steps:
+ - name: Cancel previous
+ uses: styfle/cancel-workflow-action@0.9.1
+ with:
+ access_token: ${{ github.token }}
+ - name: 'Check out repository'
+ uses: actions/checkout@v2
+ - name: 'Set up JDK ${{ matrix.java }}'
+ uses: actions/setup-java@v2
+ with:
+ java-version: ${{ matrix.java }}
+ distribution: 'zulu'
+ cache: 'maven'
+ - name: 'Install'
+ shell: bash
+ run: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
+ - name: 'Test'
+ shell: bash
+ run: mvn test -B
+ - name: 'Javadoc'
+ shell: bash
+ run: mvn javadoc:aggregate
diff --git a/.mvn/jvm.config b/.mvn/jvm.config
new file mode 100644
index 0000000..504456f
--- /dev/null
+++ b/.mvn/jvm.config
@@ -0,0 +1,10 @@
+--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 8113f5f..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-language: java
-
-jdk:
- - openjdk8
- - openjdk9
- - openjdk10
- - openjdk11
- - openjdk-ea
-
-matrix:
- allow_failures:
- - jdk: openjdk-ea
-
-# see https://github.com/travis-ci/travis-ci/issues/8408
-before_install:
-- unset _JAVA_OPTIONS
-
-# use travis-ci docker based infrastructure
-sudo: false
-
-cache:
- directories:
- - $HOME/.m2
-
-script:
-- mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
-- mvn test -B
diff --git a/android-annotation-stubs/gen_annotations.sh b/android-annotation-stubs/gen_annotations.sh
index 1965324..b2bb170 100755
--- a/android-annotation-stubs/gen_annotations.sh
+++ b/android-annotation-stubs/gen_annotations.sh
@@ -5,8 +5,8 @@ declare -A PARAMETER
declare -A IMPORT
ANNOTATIONS=(
- org.checkerframework.checker.nullness.qual.Nullable
- org.checkerframework.checker.nullness.compatqual.NullableDecl
+ org.jspecify.nullness.Nullable
+ org.jspecify.nullness.NullMarked
)
for a in ${ANNOTATIONS[@]}; do
diff --git a/android-annotation-stubs/src/org/checkerframework/checker/nullness/compatqual/NullableDecl.java b/android-annotation-stubs/src/org/jspecify/nullness/NullMarked.java
index 2d39b0f..ef4b562 100644
--- a/android-annotation-stubs/src/org/checkerframework/checker/nullness/compatqual/NullableDecl.java
+++ b/android-annotation-stubs/src/org/jspecify/nullness/NullMarked.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.checkerframework.checker.nullness.compatqual;
+package org.jspecify.nullness;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -37,4 +37,4 @@ import java.lang.annotation.Target;
ElementType.TYPE_USE
})
@Retention(RetentionPolicy.SOURCE)
-public @interface NullableDecl {}
+public @interface NullMarked {}
diff --git a/android-annotation-stubs/src/org/checkerframework/checker/nullness/qual/Nullable.java b/android-annotation-stubs/src/org/jspecify/nullness/Nullable.java
index 276d64c..7156d51 100644
--- a/android-annotation-stubs/src/org/checkerframework/checker/nullness/qual/Nullable.java
+++ b/android-annotation-stubs/src/org/jspecify/nullness/Nullable.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.checkerframework.checker.nullness.qual;
+package org.jspecify.nullness;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 44ff736..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-os: Visual Studio 2015
-
-environment:
- matrix:
- - JAVA_HOME: C:\Program Files\Java\jdk9
- - JAVA_HOME: C:\Program Files\Java\jdk10
-
-install:
- - ps: |
- Add-Type -AssemblyName System.IO.Compression.FileSystem
- if (!(Test-Path -Path "C:\maven" )) {
- (new-object System.Net.WebClient).DownloadFile(
- 'http://www.us.apache.org/dist/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.zip',
- 'C:\maven-bin.zip'
- )
- [System.IO.Compression.ZipFile]::ExtractToDirectory("C:\maven-bin.zip", "C:\maven")
- }
- - cmd: SET PATH=C:\maven\apache-maven-3.3.9\bin;%JAVA_HOME%\bin;%PATH%
- - cmd: SET MAVEN_OPTS=-XX:MaxPermSize=2g -Xmx4g
- - cmd: SET JAVA_OPTS=-XX:MaxPermSize=2g -Xmx4g
-
-build_script:
- - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
-
-test_script:
- - mvn test -B
-
-cache:
- - C:\maven\
- - C:\Users\appveyor\.m2
-
diff --git a/java/com/google/common/escape/SourceCodeEscapers.java b/java/com/google/common/escape/SourceCodeEscapers.java
index 4a1aa99..c0f9d6b 100644
--- a/java/com/google/common/escape/SourceCodeEscapers.java
+++ b/java/com/google/common/escape/SourceCodeEscapers.java
@@ -22,7 +22,7 @@ import java.util.Map;
/**
* A factory for Escaper instances used to escape strings for safe use in Java.
*
- * <p>This is a subset of source code escapers that are in the process of being open-sources as part
+ * <p>This is a subset of source code escapers that are in the process of being open-sourced as part
* of guava, see: https://github.com/google/guava/issues/1620
*/
// TODO(cushon): migrate to the guava version once it is open-sourced, and delete this
@@ -43,8 +43,8 @@ public final class SourceCodeEscapers {
* safely be included in either a Java character literal or string literal. This is the preferred
* way to escape Java characters for use in String or character literals.
*
- * <p>See: <a href= "http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#101089"
- * >The Java Language Specification</a> for more details.
+ * <p>See: <a href="https://docs.oracle.com/javase/specs/jls/se14/html/jls-3.html#jls-3.10.6" >The
+ * Java Language Specification</a> for more details.
*/
public static CharEscaper javaCharEscaper() {
return JAVA_CHAR_ESCAPER;
@@ -66,7 +66,7 @@ public final class SourceCodeEscapers {
}
// This escaper does not produce octal escape sequences. See:
- // http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#101089
+ // https://docs.oracle.com/javase/specs/jls/se14/html/jls-3.html#jls-3.10.6
// "Octal escapes are provided for compatibility with C, but can express
// only Unicode values \u0000 through \u00FF, so Unicode escapes are
// usually preferred."
diff --git a/java/com/google/common/escape/package-info.java b/java/com/google/common/escape/package-info.java
new file mode 100644
index 0000000..b69b34e
--- /dev/null
+++ b/java/com/google/common/escape/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+@org.jspecify.nullness.NullMarked
+package com.google.common.escape;
diff --git a/java/com/google/turbine/binder/Binder.java b/java/com/google/turbine/binder/Binder.java
index 0e3f41f..d2ce948 100644
--- a/java/com/google/turbine/binder/Binder.java
+++ b/java/com/google/turbine/binder/Binder.java
@@ -16,6 +16,8 @@
package com.google.turbine.binder;
+import static java.util.Objects.requireNonNull;
+
import com.google.auto.value.AutoValue;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
@@ -54,6 +56,7 @@ import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.binder.sym.ModuleSymbol;
import com.google.turbine.diag.SourceFile;
+import com.google.turbine.diag.TurbineDiagnostic;
import com.google.turbine.diag.TurbineError;
import com.google.turbine.diag.TurbineError.ErrorKind;
import com.google.turbine.diag.TurbineLog;
@@ -66,12 +69,13 @@ import com.google.turbine.type.Type;
import java.time.Duration;
import java.util.Optional;
import javax.annotation.processing.Processor;
+import org.jspecify.nullness.Nullable;
/** The entry point for analysis. */
-public class Binder {
+public final class Binder {
/** Binds symbols and types to the given compilation units. */
- public static BindingResult bind(
+ public static @Nullable BindingResult bind(
ImmutableList<CompUnit> units,
ClassPath classpath,
ClassPath bootclasspath,
@@ -80,26 +84,35 @@ public class Binder {
}
/** Binds symbols and types to the given compilation units. */
- public static BindingResult bind(
+ public static @Nullable BindingResult bind(
ImmutableList<CompUnit> units,
ClassPath classpath,
ProcessorInfo processorInfo,
ClassPath bootclasspath,
Optional<String> moduleVersion) {
TurbineLog log = new TurbineLog();
- BindingResult br =
- bind(
- log,
- units,
- /* generatedSources= */ ImmutableMap.of(),
- /* generatedClasses= */ ImmutableMap.of(),
- classpath,
- bootclasspath,
- moduleVersion);
- if (!processorInfo.processors().isEmpty() && !units.isEmpty()) {
+ BindingResult br;
+ try {
br =
- Processing.process(
- log, units, classpath, processorInfo, bootclasspath, br, moduleVersion);
+ bind(
+ log,
+ units,
+ /* generatedSources= */ ImmutableMap.of(),
+ /* generatedClasses= */ ImmutableMap.of(),
+ classpath,
+ bootclasspath,
+ moduleVersion);
+ if (!processorInfo.processors().isEmpty() && !units.isEmpty()) {
+ br =
+ Processing.process(
+ log, units, classpath, processorInfo, bootclasspath, br, moduleVersion);
+ }
+ } catch (TurbineError turbineError) {
+ throw new TurbineError(
+ ImmutableList.<TurbineDiagnostic>builder()
+ .addAll(log.diagnostics())
+ .addAll(turbineError.diagnostics())
+ .build());
}
log.maybeThrow();
return br;
@@ -169,11 +182,11 @@ public class Binder {
ImmutableMap.Builder<ClassSymbol, SourceTypeBoundClass> result = ImmutableMap.builder();
for (ClassSymbol sym : syms) {
- result.put(sym, tenv.get(sym));
+ result.put(sym, tenv.getNonNull(sym));
}
return new BindingResult(
- result.build(),
+ result.buildOrThrow(),
boundModules,
classPathEnv,
tli,
@@ -240,11 +253,12 @@ public class Binder {
ImportScope wildImportScope = WildImportIndex.create(importResolver, tli, unit.imports());
MemberImportIndex memberImports =
new MemberImportIndex(unit.source(), importResolver, tli, unit.imports());
- ImportScope scope =
- ImportScope.fromScope(topLevel)
- .append(wildImportScope)
- .append(ImportScope.fromScope(packageScope))
- .append(importScope);
+ ImportScope scope = ImportScope.fromScope(topLevel).append(wildImportScope);
+ // Can be null if we're compiling a package-info.java for an empty package
+ if (packageScope != null) {
+ scope = scope.append(ImportScope.fromScope(packageScope));
+ }
+ scope = scope.append(importScope);
if (unit.module().isPresent()) {
ModDecl module = unit.module().get();
modules.put(
@@ -274,12 +288,12 @@ public class Binder {
@Override
public SourceHeaderBoundClass complete(
Env<ClassSymbol, HeaderBoundClass> henv, ClassSymbol sym) {
- PackageSourceBoundClass base = psenv.get(sym);
+ PackageSourceBoundClass base = psenv.getNonNull(sym);
return HierarchyBinder.bind(log.withSource(base.source()), sym, base, henv);
}
});
}
- return new LazyEnv<>(completers.build(), classPathEnv);
+ return new LazyEnv<>(completers.buildOrThrow(), classPathEnv);
}
private static Env<ClassSymbol, SourceTypeBoundClass> bindTypes(
@@ -289,7 +303,7 @@ public class Binder {
Env<ClassSymbol, HeaderBoundClass> henv) {
SimpleEnv.Builder<ClassSymbol, SourceTypeBoundClass> builder = SimpleEnv.builder();
for (ClassSymbol sym : syms) {
- SourceHeaderBoundClass base = shenv.get(sym);
+ SourceHeaderBoundClass base = shenv.getNonNull(sym);
builder.put(sym, TypeBinder.bind(log.withSource(base.source()), henv, sym, base));
}
return builder.build();
@@ -301,7 +315,8 @@ public class Binder {
Env<ClassSymbol, TypeBoundClass> tenv) {
SimpleEnv.Builder<ClassSymbol, SourceTypeBoundClass> builder = SimpleEnv.builder();
for (ClassSymbol sym : syms) {
- builder.put(sym, CanonicalTypeBinder.bind(sym, stenv.get(sym), tenv));
+ SourceTypeBoundClass base = stenv.getNonNull(sym);
+ builder.put(sym, CanonicalTypeBinder.bind(sym, base, tenv));
}
return builder.build();
}
@@ -318,7 +333,7 @@ public class Binder {
moduleEnv.append(
new Env<ModuleSymbol, ModuleInfo>() {
@Override
- public ModuleInfo get(ModuleSymbol sym) {
+ public @Nullable ModuleInfo get(ModuleSymbol sym) {
PackageSourceBoundModule info = modules.get(sym);
if (info != null) {
return new ModuleInfo(
@@ -356,7 +371,7 @@ public class Binder {
ImmutableMap.Builder<FieldSymbol, LazyEnv.Completer<FieldSymbol, Const.Value, Const.Value>>
completers = ImmutableMap.builder();
for (ClassSymbol sym : syms) {
- SourceTypeBoundClass info = env.get(sym);
+ SourceTypeBoundClass info = env.getNonNull(sym);
for (FieldInfo field : info.fields()) {
if (!isConst(field)) {
continue;
@@ -365,7 +380,8 @@ public class Binder {
field.sym(),
new LazyEnv.Completer<FieldSymbol, Const.Value, Const.Value>() {
@Override
- public Const.Value complete(Env<FieldSymbol, Const.Value> env1, FieldSymbol k) {
+ public Const.@Nullable Value complete(
+ Env<FieldSymbol, Const.Value> env1, FieldSymbol k) {
try {
return new ConstEvaluator(
sym,
@@ -376,7 +392,9 @@ public class Binder {
env1,
baseEnv,
log.withSource(info.source()))
- .evalFieldInitializer(field.decl().init().get(), field.type());
+ .evalFieldInitializer(
+ // we're processing fields bound from sources in the compilation
+ requireNonNull(field.decl()).init().get(), field.type());
} catch (LazyEnv.LazyBindingError e) {
// fields initializers are allowed to reference the field being initialized,
// but if they do they aren't constants
@@ -391,11 +409,12 @@ public class Binder {
// lazily evaluated fields in the current compilation unit with
// constant fields in the classpath (which don't require evaluation).
Env<FieldSymbol, Const.Value> constenv =
- new LazyEnv<>(completers.build(), SimpleEnv.<FieldSymbol, Const.Value>builder().build());
+ new LazyEnv<>(
+ completers.buildOrThrow(), SimpleEnv.<FieldSymbol, Const.Value>builder().build());
SimpleEnv.Builder<ClassSymbol, SourceTypeBoundClass> builder = SimpleEnv.builder();
for (ClassSymbol sym : syms) {
- SourceTypeBoundClass base = env.get(sym);
+ SourceTypeBoundClass base = env.getNonNull(sym);
builder.put(
sym, new ConstBinder(constenv, sym, baseEnv, base, log.withSource(base.source())).bind());
}
@@ -437,7 +456,8 @@ public class Binder {
Env<ClassSymbol, TypeBoundClass> tenv) {
SimpleEnv.Builder<ClassSymbol, SourceTypeBoundClass> builder = SimpleEnv.builder();
for (ClassSymbol sym : syms) {
- builder.put(sym, DisambiguateTypeAnnotations.bind(stenv.get(sym), tenv));
+ SourceTypeBoundClass base = stenv.getNonNull(sym);
+ builder.put(sym, DisambiguateTypeAnnotations.bind(base, tenv));
}
return builder.build();
}
@@ -540,4 +560,6 @@ public class Binder {
units, modules, classPathEnv, tli, generatedSources, generatedClasses, statistics);
}
}
+
+ private Binder() {}
}
diff --git a/java/com/google/turbine/binder/CanonicalTypeBinder.java b/java/com/google/turbine/binder/CanonicalTypeBinder.java
index a2f045a..5ff17bb 100644
--- a/java/com/google/turbine/binder/CanonicalTypeBinder.java
+++ b/java/com/google/turbine/binder/CanonicalTypeBinder.java
@@ -16,6 +16,8 @@
package com.google.turbine.binder;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.turbine.binder.bound.SourceTypeBoundClass;
@@ -23,6 +25,7 @@ import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo;
import com.google.turbine.binder.bound.TypeBoundClass.ParamInfo;
+import com.google.turbine.binder.bound.TypeBoundClass.RecordComponentInfo;
import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo;
import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.sym.ClassSymbol;
@@ -38,39 +41,37 @@ import java.util.Map;
/**
* Canonicalizes all qualified types in a {@link SourceTypeBoundClass} using {@link Canonicalize}.
*/
-public class CanonicalTypeBinder {
+public final class CanonicalTypeBinder {
static SourceTypeBoundClass bind(
ClassSymbol sym, SourceTypeBoundClass base, Env<ClassSymbol, TypeBoundClass> env) {
- ClassTy superClassType = null;
- if (base.superClassType() != null && base.superClassType().tyKind() == TyKind.CLASS_TY) {
+ Type superClassType = base.superClassType();
+ int pos = base.decl().position();
+ if (superClassType != null && superClassType.tyKind() == TyKind.CLASS_TY) {
superClassType =
Canonicalize.canonicalizeClassTy(
- base.source(),
- base.decl().position(),
- env,
- base.owner(),
- (ClassTy) base.superClassType());
+ base.source(), pos, env, base.owner(), (ClassTy) superClassType);
}
ImmutableList.Builder<Type> interfaceTypes = ImmutableList.builder();
for (Type i : base.interfaceTypes()) {
if (i.tyKind() == TyKind.CLASS_TY) {
- i =
- Canonicalize.canonicalizeClassTy(
- base.source(), base.decl().position(), env, base.owner(), (ClassTy) i);
+ i = Canonicalize.canonicalizeClassTy(base.source(), pos, env, base.owner(), (ClassTy) i);
}
interfaceTypes.add(i);
}
ImmutableMap<TyVarSymbol, TyVarInfo> typParamTypes =
- typeParameters(base.source(), base.decl().position(), env, sym, base.typeParameterTypes());
- ImmutableList<MethodInfo> methods =
- methods(base.source(), base.decl().position(), env, sym, base.methods());
+ typeParameters(base.source(), pos, env, sym, base.typeParameterTypes());
+ ImmutableList<RecordComponentInfo> components =
+ components(base.source(), env, sym, pos, base.components());
+ ImmutableList<MethodInfo> methods = methods(base.source(), pos, env, sym, base.methods());
ImmutableList<FieldInfo> fields = fields(base.source(), env, sym, base.fields());
return new SourceTypeBoundClass(
interfaceTypes.build(),
+ base.permits(),
superClassType,
typParamTypes,
base.access(),
+ components,
methods,
fields,
base.owner(),
@@ -96,7 +97,13 @@ public class CanonicalTypeBinder {
result.add(
new FieldInfo(
base.sym(),
- Canonicalize.canonicalize(source, base.decl().position(), env, sym, base.type()),
+ Canonicalize.canonicalize(
+ source,
+ // we're processing fields bound from sources in the compilation
+ requireNonNull(base.decl()).position(),
+ env,
+ sym,
+ base.type()),
base.access(),
base.annotations(),
base.decl(),
@@ -117,25 +124,33 @@ public class CanonicalTypeBinder {
ImmutableMap<TyVarSymbol, TyVarInfo> tps =
typeParameters(source, pos, env, sym, base.tyParams());
Type ret = Canonicalize.canonicalize(source, pos, env, sym, base.returnType());
- ImmutableList.Builder<ParamInfo> parameters = ImmutableList.builder();
- for (ParamInfo parameter : base.parameters()) {
- parameters.add(param(source, pos, env, sym, parameter));
- }
+ ImmutableList<ParamInfo> parameters = parameters(source, env, sym, pos, base.parameters());
ImmutableList<Type> exceptions = canonicalizeList(source, pos, env, sym, base.exceptions());
result.add(
new MethodInfo(
base.sym(),
tps,
ret,
- parameters.build(),
+ parameters,
exceptions,
base.access(),
base.defaultValue(),
base.decl(),
base.annotations(),
- base.receiver() != null
- ? param(source, base.decl().position(), env, sym, base.receiver())
- : null));
+ base.receiver() != null ? param(source, pos, env, sym, base.receiver()) : null));
+ }
+ return result.build();
+ }
+
+ private static ImmutableList<ParamInfo> parameters(
+ SourceFile source,
+ Env<ClassSymbol, TypeBoundClass> env,
+ ClassSymbol sym,
+ int pos,
+ ImmutableList<ParamInfo> parameters) {
+ ImmutableList.Builder<ParamInfo> result = ImmutableList.builder();
+ for (ParamInfo parameter : parameters) {
+ result.add(param(source, pos, env, sym, parameter));
}
return result.build();
}
@@ -153,6 +168,24 @@ public class CanonicalTypeBinder {
base.access());
}
+ private static ImmutableList<RecordComponentInfo> components(
+ SourceFile source,
+ Env<ClassSymbol, TypeBoundClass> env,
+ ClassSymbol sym,
+ int pos,
+ ImmutableList<RecordComponentInfo> components) {
+ ImmutableList.Builder<RecordComponentInfo> result = ImmutableList.builder();
+ for (RecordComponentInfo component : components) {
+ result.add(
+ new RecordComponentInfo(
+ component.sym(),
+ Canonicalize.canonicalize(source, pos, env, sym, component.type()),
+ component.annotations(),
+ component.access()));
+ }
+ return result.build();
+ }
+
private static ImmutableMap<TyVarSymbol, TyVarInfo> typeParameters(
SourceFile source,
int position,
@@ -166,7 +199,7 @@ public class CanonicalTypeBinder {
(IntersectionTy) Canonicalize.canonicalize(source, position, env, sym, info.upperBound());
result.put(e.getKey(), new TyVarInfo(upperBound, /* lowerBound= */ null, info.annotations()));
}
- return result.build();
+ return result.buildOrThrow();
}
private static ImmutableList<Type> canonicalizeList(
@@ -181,4 +214,6 @@ public class CanonicalTypeBinder {
}
return result.build();
}
+
+ private CanonicalTypeBinder() {}
}
diff --git a/java/com/google/turbine/binder/ClassPath.java b/java/com/google/turbine/binder/ClassPath.java
index eeea7c5..eb78099 100644
--- a/java/com/google/turbine/binder/ClassPath.java
+++ b/java/com/google/turbine/binder/ClassPath.java
@@ -23,6 +23,7 @@ import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.lookup.TopLevelIndex;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.ModuleSymbol;
+import org.jspecify.nullness.Nullable;
/**
* A compilation classpath, e.g. the user or platform class path. May be backed by a search path of
@@ -38,5 +39,6 @@ public interface ClassPath {
/** The classpath's top level index. */
TopLevelIndex index();
+ @Nullable
Supplier<byte[]> resource(String path);
}
diff --git a/java/com/google/turbine/binder/ClassPathBinder.java b/java/com/google/turbine/binder/ClassPathBinder.java
index 8aead80..1c41e96 100644
--- a/java/com/google/turbine/binder/ClassPathBinder.java
+++ b/java/com/google/turbine/binder/ClassPathBinder.java
@@ -36,9 +36,10 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
+import org.jspecify.nullness.Nullable;
/** Sets up an environment for symbols on the classpath. */
-public class ClassPathBinder {
+public final class ClassPathBinder {
/**
* The prefix for repackaged transitive dependencies; see {@link
@@ -57,7 +58,7 @@ public class ClassPathBinder {
Env<ClassSymbol, BytecodeBoundClass> benv =
new Env<ClassSymbol, BytecodeBoundClass>() {
@Override
- public BytecodeBoundClass get(ClassSymbol sym) {
+ public @Nullable BytecodeBoundClass get(ClassSymbol sym) {
return map.get(sym);
}
};
@@ -92,7 +93,7 @@ public class ClassPathBinder {
}
@Override
- public Supplier<byte[]> resource(String path) {
+ public @Nullable Supplier<byte[]> resource(String path) {
return resources.get(path);
}
};
@@ -148,4 +149,6 @@ public class ClassPathBinder {
}
});
}
+
+ private ClassPathBinder() {}
}
diff --git a/java/com/google/turbine/binder/CompUnitPreprocessor.java b/java/com/google/turbine/binder/CompUnitPreprocessor.java
index ed70e88..970dc4b 100644
--- a/java/com/google/turbine/binder/CompUnitPreprocessor.java
+++ b/java/com/google/turbine/binder/CompUnitPreprocessor.java
@@ -45,7 +45,7 @@ import java.util.Set;
* Processes compilation units before binding, creating symbols for type declarations and desugaring
* access modifiers.
*/
-public class CompUnitPreprocessor {
+public final class CompUnitPreprocessor {
/** A pre-processed compilation unit. */
public static class PreprocessedCompUnit {
@@ -149,7 +149,7 @@ public class CompUnitPreprocessor {
types.add(new SourceBoundClass(sym, owner, children, access, decl));
}
}
- return result.build();
+ return result.buildOrThrow();
}
/** Desugars access flags for a class. */
@@ -175,6 +175,9 @@ public class CompUnitPreprocessor {
case ANNOTATION:
access |= TurbineFlag.ACC_ABSTRACT | TurbineFlag.ACC_INTERFACE | TurbineFlag.ACC_ANNOTATION;
break;
+ case RECORD:
+ access |= TurbineFlag.ACC_SUPER | TurbineFlag.ACC_FINAL;
+ break;
}
return access;
}
@@ -195,12 +198,14 @@ public class CompUnitPreprocessor {
case INTERFACE:
case ENUM:
case ANNOTATION:
+ case RECORD:
access |= TurbineFlag.ACC_STATIC;
break;
case CLASS:
if ((enclosing & (TurbineFlag.ACC_INTERFACE | TurbineFlag.ACC_ANNOTATION)) != 0) {
access |= TurbineFlag.ACC_STATIC;
}
+ break;
}
// propagate strictfp to nested types
@@ -219,7 +224,11 @@ public class CompUnitPreprocessor {
Optional.empty(),
ImmutableList.of(),
ImmutableList.of(),
+ ImmutableList.of(),
+ ImmutableList.of(),
TurbineTyKind.INTERFACE,
/* javadoc= */ null);
}
+
+ private CompUnitPreprocessor() {}
}
diff --git a/java/com/google/turbine/binder/ConstBinder.java b/java/com/google/turbine/binder/ConstBinder.java
index 3a41e94..29ae710 100644
--- a/java/com/google/turbine/binder/ConstBinder.java
+++ b/java/com/google/turbine/binder/ConstBinder.java
@@ -16,6 +16,8 @@
package com.google.turbine.binder;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -27,6 +29,7 @@ import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo;
import com.google.turbine.binder.bound.TypeBoundClass.ParamInfo;
+import com.google.turbine.binder.bound.TypeBoundClass.RecordComponentInfo;
import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo;
import com.google.turbine.binder.env.CompoundEnv;
import com.google.turbine.binder.env.Env;
@@ -55,6 +58,7 @@ import com.google.turbine.type.Type.WildUnboundedTy;
import com.google.turbine.type.Type.WildUpperBoundedTy;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
+import org.jspecify.nullness.Nullable;
/** Binding pass to evaluate constant expressions. */
public class ConstBinder {
@@ -101,13 +105,16 @@ public class ConstBinder {
env,
log)
.evaluateAnnotations(base.annotations());
+ ImmutableList<RecordComponentInfo> components = bindRecordComponents(base.components());
ImmutableList<TypeBoundClass.FieldInfo> fields = fields(base.fields());
ImmutableList<MethodInfo> methods = bindMethods(base.methods());
return new SourceTypeBoundClass(
bindTypes(base.interfaceTypes()),
+ base.permits(),
base.superClassType() != null ? bindType(base.superClassType()) : null,
bindTypeParameters(base.typeParameterTypes()),
base.access(),
+ components,
methods,
fields,
base.owner(),
@@ -164,7 +171,17 @@ public class ConstBinder {
return new ParamInfo(base.sym(), bindType(base.type()), annos, base.access());
}
- static AnnotationMetadata bindAnnotationMetadata(
+ private ImmutableList<RecordComponentInfo> bindRecordComponents(
+ ImmutableList<RecordComponentInfo> components) {
+ ImmutableList.Builder<RecordComponentInfo> result = ImmutableList.builder();
+ for (RecordComponentInfo base : components) {
+ ImmutableList<AnnoInfo> annos = constEvaluator.evaluateAnnotations(base.annotations());
+ result.add(new RecordComponentInfo(base.sym(), bindType(base.type()), annos, base.access()));
+ }
+ return result.build();
+ }
+
+ static @Nullable AnnotationMetadata bindAnnotationMetadata(
TurbineTyKind kind, Iterable<AnnoInfo> annotations) {
if (kind != TurbineTyKind.ANNOTATION) {
return null;
@@ -194,8 +211,11 @@ public class ConstBinder {
return new AnnotationMetadata(retention, target, repeatable);
}
- private static RetentionPolicy bindRetention(AnnoInfo annotation) {
+ private static @Nullable RetentionPolicy bindRetention(AnnoInfo annotation) {
Const value = annotation.values().get("value");
+ if (value == null) {
+ return null;
+ }
if (value.kind() != Kind.ENUM_CONSTANT) {
return null;
}
@@ -208,7 +228,8 @@ public class ConstBinder {
private static ImmutableSet<TurbineElementType> bindTarget(AnnoInfo annotation) {
ImmutableSet.Builder<TurbineElementType> result = ImmutableSet.builder();
- Const val = annotation.values().get("value");
+ // requireNonNull is safe because java.lang.annotation.Target declares `value`.
+ Const val = requireNonNull(annotation.values().get("value"));
switch (val.kind()) {
case ARRAY:
for (Const element : ((ArrayInitValue) val).elements()) {
@@ -226,8 +247,9 @@ public class ConstBinder {
return result.build();
}
- private static ClassSymbol bindRepeatable(AnnoInfo annotation) {
- Const value = annotation.values().get("value");
+ private static @Nullable ClassSymbol bindRepeatable(AnnoInfo annotation) {
+ // requireNonNull is safe because java.lang.annotation.Repeatable declares `value`.
+ Const value = requireNonNull(annotation.values().get("value"));
if (value.kind() != Kind.CLASS_LITERAL) {
return null;
}
@@ -261,18 +283,19 @@ public class ConstBinder {
return result.build();
}
- private Value fieldValue(TypeBoundClass.FieldInfo base) {
+ private @Nullable Value fieldValue(TypeBoundClass.FieldInfo base) {
if (base.decl() == null || !base.decl().init().isPresent()) {
return null;
}
if ((base.access() & TurbineFlag.ACC_FINAL) == 0) {
return null;
}
- switch (base.type().tyKind()) {
+ Type type = base.type();
+ switch (type.tyKind()) {
case PRIM_TY:
break;
case CLASS_TY:
- if (((Type.ClassTy) base.type()).sym().equals(ClassSymbol.STRING)) {
+ if (((Type.ClassTy) type).sym().equals(ClassSymbol.STRING)) {
break;
}
// falls through
@@ -280,8 +303,13 @@ public class ConstBinder {
return null;
}
Value value = constantEnv.get(base.sym());
- if (value != null) {
- value = (Value) ConstEvaluator.cast(base.type(), value);
+ if (value == null) {
+ return null;
+ }
+ if (type.tyKind().equals(TyKind.PRIM_TY)) {
+ value =
+ constEvaluator.coerce(
+ base.decl().init().get().position(), value, ((Type.PrimTy) type).primkind());
}
return value;
}
@@ -306,7 +334,7 @@ public class ConstBinder {
/* lowerBound= */ null,
constEvaluator.evaluateAnnotations(info.annotations())));
}
- return result.build();
+ return result.buildOrThrow();
}
private Type bindType(Type type) {
diff --git a/java/com/google/turbine/binder/ConstEvaluator.java b/java/com/google/turbine/binder/ConstEvaluator.java
index 9d5f042..771e87f 100644
--- a/java/com/google/turbine/binder/ConstEvaluator.java
+++ b/java/com/google/turbine/binder/ConstEvaluator.java
@@ -17,6 +17,7 @@
package com.google.turbine.binder;
import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.Objects.requireNonNull;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
@@ -43,10 +44,16 @@ import com.google.turbine.diag.TurbineError;
import com.google.turbine.diag.TurbineError.ErrorKind;
import com.google.turbine.diag.TurbineLog.TurbineLogWithSource;
import com.google.turbine.model.Const;
+import com.google.turbine.model.Const.ArrayInitValue;
+import com.google.turbine.model.Const.CharValue;
import com.google.turbine.model.Const.ConstCastError;
+import com.google.turbine.model.Const.DoubleValue;
+import com.google.turbine.model.Const.FloatValue;
+import com.google.turbine.model.Const.StringValue;
import com.google.turbine.model.Const.Value;
import com.google.turbine.model.TurbineConstantTypeKind;
import com.google.turbine.model.TurbineFlag;
+import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.tree.Tree;
import com.google.turbine.tree.Tree.ArrayInit;
import com.google.turbine.tree.Tree.Binary;
@@ -56,15 +63,18 @@ import com.google.turbine.tree.Tree.Conditional;
import com.google.turbine.tree.Tree.ConstVarName;
import com.google.turbine.tree.Tree.Expression;
import com.google.turbine.tree.Tree.Ident;
+import com.google.turbine.tree.Tree.Paren;
import com.google.turbine.tree.Tree.PrimTy;
import com.google.turbine.tree.Tree.TypeCast;
import com.google.turbine.tree.Tree.Unary;
+import com.google.turbine.tree.TurbineOperatorKind;
import com.google.turbine.type.AnnoInfo;
import com.google.turbine.type.Type;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
+import org.jspecify.nullness.Nullable;
/**
* Constant expression evaluation.
@@ -74,10 +84,10 @@ import java.util.Map;
public strictfp class ConstEvaluator {
/** The symbol of the originating class, for visibility checks. */
- private final ClassSymbol origin;
+ private final @Nullable ClassSymbol origin;
/** The symbol of the enclosing class, for lexical field lookups. */
- private final ClassSymbol owner;
+ private final @Nullable ClassSymbol owner;
/** Member imports of the enclosing compilation unit. */
private final MemberImportIndex memberImports;
@@ -86,7 +96,7 @@ public strictfp class ConstEvaluator {
private final SourceFile source;
/** The constant variable environment. */
- private final Env<FieldSymbol, Const.Value> values;
+ private final Env<FieldSymbol, Value> values;
/** The class environment. */
private final CompoundEnv<ClassSymbol, TypeBoundClass> env;
@@ -96,8 +106,8 @@ public strictfp class ConstEvaluator {
private final TurbineLogWithSource log;
public ConstEvaluator(
- ClassSymbol origin,
- ClassSymbol owner,
+ @Nullable ClassSymbol origin,
+ @Nullable ClassSymbol owner,
MemberImportIndex memberImports,
SourceFile source,
Scope scope,
@@ -116,32 +126,23 @@ public strictfp class ConstEvaluator {
}
/** Evaluates the given expression's value. */
- public Const eval(Tree t) {
+ public @Nullable Const eval(Tree t) {
switch (t.kind()) {
case LITERAL:
{
- Const.Value a = (Const.Value) ((Tree.Literal) t).value();
+ Value a = (Value) ((Tree.Literal) t).value();
if (a == null) {
return null;
}
switch (a.constantTypeKind()) {
case CHAR:
- return new Const.CharValue(((com.google.turbine.model.Const.CharValue) a).value());
case INT:
- return new Const.IntValue(((com.google.turbine.model.Const.IntValue) a).value());
case LONG:
- return new Const.LongValue(((com.google.turbine.model.Const.LongValue) a).value());
case FLOAT:
- return new Const.FloatValue(((com.google.turbine.model.Const.FloatValue) a).value());
case DOUBLE:
- return new Const.DoubleValue(
- ((com.google.turbine.model.Const.DoubleValue) a).value());
case BOOLEAN:
- return new Const.BooleanValue(
- ((com.google.turbine.model.Const.BooleanValue) a).value());
case STRING:
- return new Const.StringValue(
- ((com.google.turbine.model.Const.StringValue) a).value());
+ return a;
case SHORT:
case BYTE:
case NULL:
@@ -156,6 +157,8 @@ public strictfp class ConstEvaluator {
return evalClassLiteral((ClassLiteral) t);
case BINARY:
return evalBinary((Binary) t);
+ case PAREN:
+ return eval(((Paren) t).expr());
case TYPE_CAST:
return evalCast((TypeCast) t);
case UNARY:
@@ -208,11 +211,11 @@ public strictfp class ConstEvaluator {
}
LookupResult result = scope.lookup(new LookupKey(ImmutableList.copyOf(flat)));
if (result == null) {
- log.error(classTy.position(), ErrorKind.CANNOT_RESOLVE, flat.peekFirst());
+ log.error(classTy.position(), ErrorKind.CANNOT_RESOLVE, flat.getFirst());
return Type.ErrorTy.create(flat);
}
if (result.sym().symKind() != Symbol.Kind.CLASS) {
- throw error(classTy.position(), ErrorKind.UNEXPECTED_TYPE_PARAMETER, flat.peekFirst());
+ throw error(classTy.position(), ErrorKind.UNEXPECTED_TYPE_PARAMETER, flat.getFirst());
}
ClassSymbol classSym = (ClassSymbol) result.sym();
for (Ident bit : result.remaining()) {
@@ -231,6 +234,7 @@ public strictfp class ConstEvaluator {
}
/** Evaluates a reference to another constant variable. */
+ @Nullable
Const evalConstVar(ConstVarName t) {
FieldInfo field = resolveField(t);
if (field == null) {
@@ -281,7 +285,7 @@ public strictfp class ConstEvaluator {
String.format("field %s", Iterables.getLast(t.name())));
}
- private FieldInfo resolveQualifiedField(ConstVarName t) {
+ private @Nullable FieldInfo resolveQualifiedField(ConstVarName t) {
if (t.name().size() <= 1) {
return null;
}
@@ -304,10 +308,10 @@ public strictfp class ConstEvaluator {
}
/** Search for constant variables in lexically enclosing scopes. */
- private FieldInfo lexicalField(
- Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym, Ident name) {
+ private @Nullable FieldInfo lexicalField(
+ Env<ClassSymbol, TypeBoundClass> env, @Nullable ClassSymbol sym, Ident name) {
while (sym != null) {
- TypeBoundClass info = env.get(sym);
+ TypeBoundClass info = env.getNonNull(sym);
FieldInfo field = Resolve.resolveField(env, origin, sym, name);
if (field != null) {
return field;
@@ -318,61 +322,321 @@ public strictfp class ConstEvaluator {
}
/** Casts the value to the given type. */
- static Const cast(Type ty, Const value) {
+ private Const cast(int position, Type ty, Const value) {
checkNotNull(value);
switch (ty.tyKind()) {
case CLASS_TY:
case TY_VAR:
return value;
case PRIM_TY:
- return coerce((Const.Value) value, ((Type.PrimTy) ty).primkind());
+ if (!value.kind().equals(Const.Kind.PRIMITIVE)) {
+ throw error(position, ErrorKind.EXPRESSION_ERROR);
+ }
+ return coerce(position, (Value) value, ((Type.PrimTy) ty).primkind());
default:
throw new AssertionError(ty.tyKind());
}
}
- private static Const.Value coerce(Const.Value value, TurbineConstantTypeKind kind) {
+ /** Casts the constant value to the given type. */
+ Value coerce(int position, Value value, TurbineConstantTypeKind kind) {
switch (kind) {
+ case BYTE:
+ return asByte(position, value);
+ case SHORT:
+ return asShort(position, value);
+ case INT:
+ return asInt(position, value);
+ case LONG:
+ return asLong(position, value);
+ case FLOAT:
+ return asFloat(position, value);
+ case DOUBLE:
+ return asDouble(position, value);
+ case CHAR:
+ return asChar(position, value);
case BOOLEAN:
- return value.asBoolean();
case STRING:
- return value.asString();
+ case NULL:
+ if (!value.constantTypeKind().equals(kind)) {
+ throw typeError(position, value, kind);
+ }
+ return value;
+ }
+ throw new AssertionError(kind);
+ }
+
+ private Const.BooleanValue asBoolean(int position, Value value) {
+ if (!value.constantTypeKind().equals(TurbineConstantTypeKind.BOOLEAN)) {
+ throw typeError(position, value, TurbineConstantTypeKind.BOOLEAN);
+ }
+ return (Const.BooleanValue) value;
+ }
+
+ private Const.StringValue asString(int position, Value value) {
+ if (!value.constantTypeKind().equals(TurbineConstantTypeKind.STRING)) {
+ throw typeError(position, value, TurbineConstantTypeKind.STRING);
+ }
+ return (Const.StringValue) value;
+ }
+
+ private Const.StringValue toString(int position, Value value) {
+ String result;
+ switch (value.constantTypeKind()) {
+ case CHAR:
+ result = String.valueOf(((Const.CharValue) value).value());
+ break;
+ case SHORT:
+ result = String.valueOf(((Const.ShortValue) value).value());
+ break;
+ case INT:
+ result = String.valueOf(((Const.IntValue) value).value());
+ break;
case LONG:
- return value.asLong();
+ result = String.valueOf(((Const.LongValue) value).value());
+ break;
+ case FLOAT:
+ result = String.valueOf(((Const.FloatValue) value).value());
+ break;
+ case DOUBLE:
+ result = String.valueOf(((Const.DoubleValue) value).value());
+ break;
+ case BOOLEAN:
+ result = String.valueOf(((Const.BooleanValue) value).value());
+ break;
+ case BYTE:
+ result = String.valueOf(((Const.ByteValue) value).value());
+ break;
+ case STRING:
+ return (StringValue) value;
+ default:
+ throw typeError(position, value, TurbineConstantTypeKind.STRING);
+ }
+ return new Const.StringValue(result);
+ }
+
+ private Const.CharValue asChar(int position, Value value) {
+ char result;
+ switch (value.constantTypeKind()) {
+ case CHAR:
+ return (Const.CharValue) value;
+ case BYTE:
+ result = (char) ((Const.ByteValue) value).value();
+ break;
+ case SHORT:
+ result = (char) ((Const.ShortValue) value).value();
+ break;
+ case INT:
+ result = (char) ((Const.IntValue) value).value();
+ break;
+ case LONG:
+ result = (char) ((Const.LongValue) value).value();
+ break;
+ case FLOAT:
+ result = (char) ((Const.FloatValue) value).value();
+ break;
+ case DOUBLE:
+ result = (char) ((Const.DoubleValue) value).value();
+ break;
+ default:
+ throw typeError(position, value, TurbineConstantTypeKind.CHAR);
+ }
+ return new Const.CharValue(result);
+ }
+
+ private Const.ByteValue asByte(int position, Value value) {
+ byte result;
+ switch (value.constantTypeKind()) {
+ case CHAR:
+ result = (byte) ((Const.CharValue) value).value();
+ break;
+ case BYTE:
+ return (Const.ByteValue) value;
+ case SHORT:
+ result = (byte) ((Const.ShortValue) value).value();
+ break;
+ case INT:
+ result = (byte) ((Const.IntValue) value).value();
+ break;
+ case LONG:
+ result = (byte) ((Const.LongValue) value).value();
+ break;
+ case FLOAT:
+ result = (byte) ((Const.FloatValue) value).value();
+ break;
+ case DOUBLE:
+ result = (byte) ((Const.DoubleValue) value).value();
+ break;
+ default:
+ throw typeError(position, value, TurbineConstantTypeKind.BYTE);
+ }
+ return new Const.ByteValue(result);
+ }
+
+ private Const.ShortValue asShort(int position, Value value) {
+ short result;
+ switch (value.constantTypeKind()) {
+ case CHAR:
+ result = (short) ((Const.CharValue) value).value();
+ break;
+ case BYTE:
+ result = ((Const.ByteValue) value).value();
+ break;
+ case SHORT:
+ return (Const.ShortValue) value;
+ case INT:
+ result = (short) ((Const.IntValue) value).value();
+ break;
+ case LONG:
+ result = (short) ((Const.LongValue) value).value();
+ break;
+ case FLOAT:
+ result = (short) ((Const.FloatValue) value).value();
+ break;
+ case DOUBLE:
+ result = (short) ((Const.DoubleValue) value).value();
+ break;
+ default:
+ throw typeError(position, value, TurbineConstantTypeKind.SHORT);
+ }
+ return new Const.ShortValue(result);
+ }
+
+ private Const.IntValue asInt(int position, Value value) {
+ int result;
+ switch (value.constantTypeKind()) {
+ case CHAR:
+ result = ((CharValue) value).value();
+ break;
+ case BYTE:
+ result = ((Const.ByteValue) value).value();
+ break;
+ case SHORT:
+ result = ((Const.ShortValue) value).value();
+ break;
case INT:
- return value.asInteger();
+ return (Const.IntValue) value;
+ case LONG:
+ result = (int) ((Const.LongValue) value).value();
+ break;
+ case FLOAT:
+ result = (int) ((Const.FloatValue) value).value();
+ break;
+ case DOUBLE:
+ result = (int) ((Const.DoubleValue) value).value();
+ break;
+ default:
+ throw typeError(position, value, TurbineConstantTypeKind.INT);
+ }
+ return new Const.IntValue(result);
+ }
+
+ private Const.LongValue asLong(int position, Value value) {
+ long result;
+ switch (value.constantTypeKind()) {
+ case CHAR:
+ result = ((CharValue) value).value();
+ break;
case BYTE:
- return value.asByte();
+ result = ((Const.ByteValue) value).value();
+ break;
+ case SHORT:
+ result = ((Const.ShortValue) value).value();
+ break;
+ case INT:
+ result = ((Const.IntValue) value).value();
+ break;
+ case LONG:
+ return (Const.LongValue) value;
+ case FLOAT:
+ result = (long) ((Const.FloatValue) value).value();
+ break;
+ case DOUBLE:
+ result = (long) ((Const.DoubleValue) value).value();
+ break;
+ default:
+ throw typeError(position, value, TurbineConstantTypeKind.LONG);
+ }
+ return new Const.LongValue(result);
+ }
+
+ private Const.FloatValue asFloat(int position, Value value) {
+ float result;
+ switch (value.constantTypeKind()) {
case CHAR:
- return value.asChar();
+ result = ((CharValue) value).value();
+ break;
+ case BYTE:
+ result = ((Const.ByteValue) value).value();
+ break;
case SHORT:
- return value.asShort();
+ result = ((Const.ShortValue) value).value();
+ break;
+ case INT:
+ result = (float) ((Const.IntValue) value).value();
+ break;
+ case LONG:
+ result = (float) ((Const.LongValue) value).value();
+ break;
+ case FLOAT:
+ return (FloatValue) value;
case DOUBLE:
- return value.asDouble();
+ result = (float) ((Const.DoubleValue) value).value();
+ break;
+ default:
+ throw typeError(position, value, TurbineConstantTypeKind.FLOAT);
+ }
+ return new Const.FloatValue(result);
+ }
+
+ private Const.DoubleValue asDouble(int position, Value value) {
+ double result;
+ switch (value.constantTypeKind()) {
+ case CHAR:
+ result = ((CharValue) value).value();
+ break;
+ case BYTE:
+ result = ((Const.ByteValue) value).value();
+ break;
+ case SHORT:
+ result = ((Const.ShortValue) value).value();
+ break;
+ case INT:
+ result = ((Const.IntValue) value).value();
+ break;
+ case LONG:
+ result = (double) ((Const.LongValue) value).value();
+ break;
case FLOAT:
- return value.asFloat();
+ result = ((Const.FloatValue) value).value();
+ break;
+ case DOUBLE:
+ return (DoubleValue) value;
default:
- throw new AssertionError(kind);
+ throw typeError(position, value, TurbineConstantTypeKind.DOUBLE);
}
+ return new Const.DoubleValue(result);
}
- private Const.Value evalValue(Expression tree) {
+ private @Nullable Value evalValue(Expression tree) {
Const result = eval(tree);
// TODO(cushon): consider distinguishing between constant field and annotation values,
// and only allowing class literals / enum constants in the latter
- return (result instanceof Const.Value) ? (Const.Value) result : null;
+ return (result instanceof Value) ? (Value) result : null;
}
- private Const.Value evalConditional(Conditional t) {
- Const.Value condition = evalValue(t.cond());
+ private @Nullable Value evalConditional(Conditional t) {
+ Value condition = evalValue(t.cond());
if (condition == null) {
return null;
}
- return condition.asBoolean().value() ? evalValue(t.iftrue()) : evalValue(t.iffalse());
+ return asBoolean(t.position(), condition).value()
+ ? evalValue(t.iftrue())
+ : evalValue(t.iffalse());
}
- private Const.Value evalUnary(Unary t) {
- Const.Value expr = evalValue(t.expr());
+ private @Nullable Value evalUnary(Unary t) {
+ Value expr = evalValue(t.expr());
if (expr == null) {
return null;
}
@@ -390,67 +654,67 @@ public strictfp class ConstEvaluator {
}
}
- private Value unaryNegate(int position, Value expr) {
+ private @Nullable Value unaryNegate(int position, Value expr) {
switch (expr.constantTypeKind()) {
case BOOLEAN:
- return new Const.BooleanValue(!expr.asBoolean().value());
+ return new Const.BooleanValue(!asBoolean(position, expr).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, expr.constantTypeKind());
}
}
- private Value bitwiseComp(int position, Value expr) {
+ private @Nullable Value bitwiseComp(int position, Value expr) {
expr = promoteUnary(position, expr);
switch (expr.constantTypeKind()) {
case INT:
- return new Const.IntValue(~expr.asInteger().value());
+ return new Const.IntValue(~asInt(position, expr).value());
case LONG:
- return new Const.LongValue(~expr.asLong().value());
+ return new Const.LongValue(~asLong(position, expr).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, expr.constantTypeKind());
}
}
- private Value unaryPlus(int position, Value expr) {
+ private @Nullable Value unaryPlus(int position, Value expr) {
expr = promoteUnary(position, expr);
switch (expr.constantTypeKind()) {
case INT:
- return new Const.IntValue(+expr.asInteger().value());
+ return new Const.IntValue(+asInt(position, expr).value());
case LONG:
- return new Const.LongValue(+expr.asLong().value());
+ return new Const.LongValue(+asLong(position, expr).value());
case FLOAT:
- return new Const.FloatValue(+expr.asFloat().value());
+ return new Const.FloatValue(+asFloat(position, expr).value());
case DOUBLE:
- return new Const.DoubleValue(+expr.asDouble().value());
+ return new Const.DoubleValue(+asDouble(position, expr).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, expr.constantTypeKind());
}
}
- private Value unaryMinus(int position, Value expr) {
+ private @Nullable Value unaryMinus(int position, Value expr) {
expr = promoteUnary(position, expr);
switch (expr.constantTypeKind()) {
case INT:
- return new Const.IntValue(-expr.asInteger().value());
+ return new Const.IntValue(-asInt(position, expr).value());
case LONG:
- return new Const.LongValue(-expr.asLong().value());
+ return new Const.LongValue(-asLong(position, expr).value());
case FLOAT:
- return new Const.FloatValue(-expr.asFloat().value());
+ return new Const.FloatValue(-asFloat(position, expr).value());
case DOUBLE:
- return new Const.DoubleValue(-expr.asDouble().value());
+ return new Const.DoubleValue(-asDouble(position, expr).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, expr.constantTypeKind());
}
}
- private Const.Value evalCast(TypeCast t) {
- Const.Value expr = evalValue(t.expr());
+ private @Nullable Value evalCast(TypeCast t) {
+ Value expr = evalValue(t.expr());
if (expr == null) {
return null;
}
switch (t.ty().kind()) {
case PRIM_TY:
- return coerce(expr, ((Tree.PrimTy) t.ty()).tykind());
+ return coerce(t.expr().position(), expr, ((Tree.PrimTy) t.ty()).tykind());
case CLASS_TY:
{
ClassTy classTy = (ClassTy) t.ty();
@@ -459,102 +723,102 @@ public strictfp class ConstEvaluator {
// Explicit boxing cases (e.g. `(Boolean) false`) are legal, but not const exprs.
return null;
}
- return expr.asString();
+ return toString(t.expr().position(), expr);
}
default:
throw new AssertionError(t.ty().kind());
}
}
- private Const.Value add(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value add(int position, Value a, Value b) {
if (a.constantTypeKind() == TurbineConstantTypeKind.STRING
|| b.constantTypeKind() == TurbineConstantTypeKind.STRING) {
- return new Const.StringValue(a.asString().value() + b.asString().value());
+ return new Const.StringValue(toString(position, a).value() + toString(position, b).value());
}
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.IntValue(a.asInteger().value() + b.asInteger().value());
+ return new Const.IntValue(asInt(position, a).value() + asInt(position, b).value());
case LONG:
- return new Const.LongValue(a.asLong().value() + b.asLong().value());
+ return new Const.LongValue(asLong(position, a).value() + asLong(position, b).value());
case FLOAT:
- return new Const.FloatValue(a.asFloat().value() + b.asFloat().value());
+ return new Const.FloatValue(asFloat(position, a).value() + asFloat(position, b).value());
case DOUBLE:
- return new Const.DoubleValue(a.asDouble().value() + b.asDouble().value());
+ return new Const.DoubleValue(asDouble(position, a).value() + asDouble(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value subtract(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value subtract(int position, Value a, Value b) {
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.IntValue(a.asInteger().value() - b.asInteger().value());
+ return new Const.IntValue(asInt(position, a).value() - asInt(position, b).value());
case LONG:
- return new Const.LongValue(a.asLong().value() - b.asLong().value());
+ return new Const.LongValue(asLong(position, a).value() - asLong(position, b).value());
case FLOAT:
- return new Const.FloatValue(a.asFloat().value() - b.asFloat().value());
+ return new Const.FloatValue(asFloat(position, a).value() - asFloat(position, b).value());
case DOUBLE:
- return new Const.DoubleValue(a.asDouble().value() - b.asDouble().value());
+ return new Const.DoubleValue(asDouble(position, a).value() - asDouble(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value mult(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value mult(int position, Value a, Value b) {
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.IntValue(a.asInteger().value() * b.asInteger().value());
+ return new Const.IntValue(asInt(position, a).value() * asInt(position, b).value());
case LONG:
- return new Const.LongValue(a.asLong().value() * b.asLong().value());
+ return new Const.LongValue(asLong(position, a).value() * asLong(position, b).value());
case FLOAT:
- return new Const.FloatValue(a.asFloat().value() * b.asFloat().value());
+ return new Const.FloatValue(asFloat(position, a).value() * asFloat(position, b).value());
case DOUBLE:
- return new Const.DoubleValue(a.asDouble().value() * b.asDouble().value());
+ return new Const.DoubleValue(asDouble(position, a).value() * asDouble(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value divide(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value divide(int position, Value a, Value b) {
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.IntValue(a.asInteger().value() / b.asInteger().value());
+ return new Const.IntValue(asInt(position, a).value() / asInt(position, b).value());
case LONG:
- return new Const.LongValue(a.asLong().value() / b.asLong().value());
+ return new Const.LongValue(asLong(position, a).value() / asLong(position, b).value());
case FLOAT:
- return new Const.FloatValue(a.asFloat().value() / b.asFloat().value());
+ return new Const.FloatValue(asFloat(position, a).value() / asFloat(position, b).value());
case DOUBLE:
- return new Const.DoubleValue(a.asDouble().value() / b.asDouble().value());
+ return new Const.DoubleValue(asDouble(position, a).value() / asDouble(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value mod(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value mod(int position, Value a, Value b) {
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.IntValue(a.asInteger().value() % b.asInteger().value());
+ return new Const.IntValue(asInt(position, a).value() % asInt(position, b).value());
case LONG:
- return new Const.LongValue(a.asLong().value() % b.asLong().value());
+ return new Const.LongValue(asLong(position, a).value() % asLong(position, b).value());
case FLOAT:
- return new Const.FloatValue(a.asFloat().value() % b.asFloat().value());
+ return new Const.FloatValue(asFloat(position, a).value() % asFloat(position, b).value());
case DOUBLE:
- return new Const.DoubleValue(a.asDouble().value() % b.asDouble().value());
+ return new Const.DoubleValue(asDouble(position, a).value() % asDouble(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
@@ -564,289 +828,319 @@ public strictfp class ConstEvaluator {
private static final int LONG_SHIFT_MASK = 0b111111;
- private Const.Value shiftLeft(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value shiftLeft(int position, Value a, Value b) {
a = promoteUnary(position, a);
b = promoteUnary(position, b);
switch (a.constantTypeKind()) {
case INT:
return new Const.IntValue(
- a.asInteger().value() << (b.asInteger().value() & INT_SHIFT_MASK));
+ asInt(position, a).value() << (asInt(position, b).value() & INT_SHIFT_MASK));
case LONG:
- return new Const.LongValue(a.asLong().value() << (b.asInteger().value() & LONG_SHIFT_MASK));
+ return new Const.LongValue(
+ asLong(position, a).value() << (asInt(position, b).value() & LONG_SHIFT_MASK));
default:
throw error(position, ErrorKind.OPERAND_TYPE, a.constantTypeKind());
}
}
- private Const.Value shiftRight(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value shiftRight(int position, Value a, Value b) {
a = promoteUnary(position, a);
b = promoteUnary(position, b);
switch (a.constantTypeKind()) {
case INT:
return new Const.IntValue(
- a.asInteger().value() >> (b.asInteger().value() & INT_SHIFT_MASK));
+ asInt(position, a).value() >> (asInt(position, b).value() & INT_SHIFT_MASK));
case LONG:
- return new Const.LongValue(a.asLong().value() >> (b.asInteger().value() & LONG_SHIFT_MASK));
+ return new Const.LongValue(
+ asLong(position, a).value() >> (asInt(position, b).value() & LONG_SHIFT_MASK));
default:
throw error(position, ErrorKind.OPERAND_TYPE, a.constantTypeKind());
}
}
- private Const.Value unsignedShiftRight(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value unsignedShiftRight(int position, Value a, Value b) {
a = promoteUnary(position, a);
b = promoteUnary(position, b);
switch (a.constantTypeKind()) {
case INT:
return new Const.IntValue(
- a.asInteger().value() >>> (b.asInteger().value() & INT_SHIFT_MASK));
+ asInt(position, a).value() >>> (asInt(position, b).value() & INT_SHIFT_MASK));
case LONG:
return new Const.LongValue(
- a.asLong().value() >>> (b.asInteger().value() & LONG_SHIFT_MASK));
+ asLong(position, a).value() >>> (asInt(position, b).value() & LONG_SHIFT_MASK));
default:
throw error(position, ErrorKind.OPERAND_TYPE, a.constantTypeKind());
}
}
- private Const.Value lessThan(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value lessThan(int position, Value a, Value b) {
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.BooleanValue(a.asInteger().value() < b.asInteger().value());
+ return new Const.BooleanValue(asInt(position, a).value() < asInt(position, b).value());
case LONG:
- return new Const.BooleanValue(a.asLong().value() < b.asLong().value());
+ return new Const.BooleanValue(asLong(position, a).value() < asLong(position, b).value());
case FLOAT:
- return new Const.BooleanValue(a.asFloat().value() < b.asFloat().value());
+ return new Const.BooleanValue(asFloat(position, a).value() < asFloat(position, b).value());
case DOUBLE:
- return new Const.BooleanValue(a.asDouble().value() < b.asDouble().value());
+ return new Const.BooleanValue(
+ asDouble(position, a).value() < asDouble(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value lessThanEqual(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value lessThanEqual(int position, Value a, Value b) {
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.BooleanValue(a.asInteger().value() <= b.asInteger().value());
+ return new Const.BooleanValue(asInt(position, a).value() <= asInt(position, b).value());
case LONG:
- return new Const.BooleanValue(a.asLong().value() <= b.asLong().value());
+ return new Const.BooleanValue(asLong(position, a).value() <= asLong(position, b).value());
case FLOAT:
- return new Const.BooleanValue(a.asFloat().value() <= b.asFloat().value());
+ return new Const.BooleanValue(asFloat(position, a).value() <= asFloat(position, b).value());
case DOUBLE:
- return new Const.BooleanValue(a.asDouble().value() <= b.asDouble().value());
+ return new Const.BooleanValue(
+ asDouble(position, a).value() <= asDouble(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value greaterThan(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value greaterThan(int position, Value a, Value b) {
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.BooleanValue(a.asInteger().value() > b.asInteger().value());
+ return new Const.BooleanValue(asInt(position, a).value() > asInt(position, b).value());
case LONG:
- return new Const.BooleanValue(a.asLong().value() > b.asLong().value());
+ return new Const.BooleanValue(asLong(position, a).value() > asLong(position, b).value());
case FLOAT:
- return new Const.BooleanValue(a.asFloat().value() > b.asFloat().value());
+ return new Const.BooleanValue(asFloat(position, a).value() > asFloat(position, b).value());
case DOUBLE:
- return new Const.BooleanValue(a.asDouble().value() > b.asDouble().value());
+ return new Const.BooleanValue(
+ asDouble(position, a).value() > asDouble(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value greaterThanEqual(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value greaterThanEqual(int position, Value a, Value b) {
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.BooleanValue(a.asInteger().value() >= b.asInteger().value());
+ return new Const.BooleanValue(asInt(position, a).value() >= asInt(position, b).value());
case LONG:
- return new Const.BooleanValue(a.asLong().value() >= b.asLong().value());
+ return new Const.BooleanValue(asLong(position, a).value() >= asLong(position, b).value());
case FLOAT:
- return new Const.BooleanValue(a.asFloat().value() >= b.asFloat().value());
+ return new Const.BooleanValue(asFloat(position, a).value() >= asFloat(position, b).value());
case DOUBLE:
- return new Const.BooleanValue(a.asDouble().value() >= b.asDouble().value());
+ return new Const.BooleanValue(
+ asDouble(position, a).value() >= asDouble(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value equal(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value equal(int position, Value a, Value b) {
switch (a.constantTypeKind()) {
case STRING:
- return new Const.BooleanValue(a.asString().value().equals(b.asString().value()));
+ return new Const.BooleanValue(
+ asString(position, a).value().equals(asString(position, b).value()));
case BOOLEAN:
- return new Const.BooleanValue(a.asBoolean().value() == b.asBoolean().value());
+ return new Const.BooleanValue(
+ asBoolean(position, a).value() == asBoolean(position, b).value());
default:
break;
}
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.BooleanValue(a.asInteger().value() == b.asInteger().value());
+ return new Const.BooleanValue(asInt(position, a).value() == asInt(position, b).value());
case LONG:
- return new Const.BooleanValue(a.asLong().value() == b.asLong().value());
+ return new Const.BooleanValue(asLong(position, a).value() == asLong(position, b).value());
case FLOAT:
- return new Const.BooleanValue(a.asFloat().value() == b.asFloat().value());
+ return new Const.BooleanValue(asFloat(position, a).value() == asFloat(position, b).value());
case DOUBLE:
- return new Const.BooleanValue(a.asDouble().value() == b.asDouble().value());
+ return new Const.BooleanValue(
+ asDouble(position, a).value() == asDouble(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value notEqual(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value notEqual(int position, Value a, Value b) {
switch (a.constantTypeKind()) {
case STRING:
- return new Const.BooleanValue(!a.asString().value().equals(b.asString().value()));
+ return new Const.BooleanValue(
+ !asString(position, a).value().equals(asString(position, b).value()));
case BOOLEAN:
- return new Const.BooleanValue(a.asBoolean().value() != b.asBoolean().value());
+ return new Const.BooleanValue(
+ asBoolean(position, a).value() != asBoolean(position, b).value());
default:
break;
}
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.BooleanValue(a.asInteger().value() != b.asInteger().value());
+ return new Const.BooleanValue(asInt(position, a).value() != asInt(position, b).value());
case LONG:
- return new Const.BooleanValue(a.asLong().value() != b.asLong().value());
+ return new Const.BooleanValue(asLong(position, a).value() != asLong(position, b).value());
case FLOAT:
- return new Const.BooleanValue(a.asFloat().value() != b.asFloat().value());
+ return new Const.BooleanValue(asFloat(position, a).value() != asFloat(position, b).value());
case DOUBLE:
- return new Const.BooleanValue(a.asDouble().value() != b.asDouble().value());
+ return new Const.BooleanValue(
+ asDouble(position, a).value() != asDouble(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value bitwiseAnd(int position, Const.Value a, Const.Value b) {
+ private Value bitwiseAnd(int position, Value a, Value b) {
switch (a.constantTypeKind()) {
case BOOLEAN:
- return new Const.BooleanValue(a.asBoolean().value() & b.asBoolean().value());
+ return new Const.BooleanValue(
+ asBoolean(position, a).value() & asBoolean(position, b).value());
default:
break;
}
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.IntValue(a.asInteger().value() & b.asInteger().value());
+ return new Const.IntValue(asInt(position, a).value() & asInt(position, b).value());
case LONG:
- return new Const.LongValue(a.asLong().value() & b.asLong().value());
+ return new Const.LongValue(asLong(position, a).value() & asLong(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value bitwiseOr(int position, Const.Value a, Const.Value b) {
+ private Value bitwiseOr(int position, Value a, Value b) {
switch (a.constantTypeKind()) {
case BOOLEAN:
- return new Const.BooleanValue(a.asBoolean().value() | b.asBoolean().value());
+ return new Const.BooleanValue(
+ asBoolean(position, a).value() | asBoolean(position, b).value());
default:
break;
}
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.IntValue(a.asInteger().value() | b.asInteger().value());
+ return new Const.IntValue(asInt(position, a).value() | asInt(position, b).value());
case LONG:
- return new Const.LongValue(a.asLong().value() | b.asLong().value());
+ return new Const.LongValue(asLong(position, a).value() | asLong(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value bitwiseXor(int position, Const.Value a, Const.Value b) {
+ private @Nullable Value bitwiseXor(int position, Value a, Value b) {
switch (a.constantTypeKind()) {
case BOOLEAN:
- return new Const.BooleanValue(a.asBoolean().value() ^ b.asBoolean().value());
+ return new Const.BooleanValue(
+ asBoolean(position, a).value() ^ asBoolean(position, b).value());
default:
break;
}
TurbineConstantTypeKind type = promoteBinary(position, a, b);
- a = coerce(a, type);
- b = coerce(b, type);
+ a = coerce(position, a, type);
+ b = coerce(position, b, type);
switch (type) {
case INT:
- return new Const.IntValue(a.asInteger().value() ^ b.asInteger().value());
+ return new Const.IntValue(asInt(position, a).value() ^ asInt(position, b).value());
case LONG:
- return new Const.LongValue(a.asLong().value() ^ b.asLong().value());
+ return new Const.LongValue(asLong(position, a).value() ^ asLong(position, b).value());
default:
throw error(position, ErrorKind.OPERAND_TYPE, type);
}
}
- private Const.Value evalBinary(Binary t) {
- Const.Value lhs = evalValue(t.lhs());
- Const.Value rhs = evalValue(t.rhs());
- if (lhs == null || rhs == null) {
- return null;
+ private @Nullable Value evalBinary(Binary t) {
+ Value result = null;
+ boolean first = true;
+ for (Expression child : t.children()) {
+ Value value = evalValue(child);
+ if (value == null) {
+ return null;
+ }
+ if (first) {
+ result = value;
+ } else {
+ result = evalBinary(child.position(), t.op(), requireNonNull(result), value);
+ }
+ first = false;
}
- switch (t.op()) {
+ return result;
+ }
+
+ private @Nullable Value evalBinary(int position, TurbineOperatorKind op, Value lhs, Value rhs) {
+ switch (op) {
case PLUS:
- return add(t.position(), lhs, rhs);
+ return add(position, lhs, rhs);
case MINUS:
- return subtract(t.position(), lhs, rhs);
+ return subtract(position, lhs, rhs);
case MULT:
- return mult(t.position(), lhs, rhs);
+ return mult(position, lhs, rhs);
case DIVIDE:
- return divide(t.position(), lhs, rhs);
+ return divide(position, lhs, rhs);
case MODULO:
- return mod(t.position(), lhs, rhs);
+ return mod(position, lhs, rhs);
case SHIFT_LEFT:
- return shiftLeft(t.position(), lhs, rhs);
+ return shiftLeft(position, lhs, rhs);
case SHIFT_RIGHT:
- return shiftRight(t.position(), lhs, rhs);
+ return shiftRight(position, lhs, rhs);
case UNSIGNED_SHIFT_RIGHT:
- return unsignedShiftRight(t.position(), lhs, rhs);
+ return unsignedShiftRight(position, lhs, rhs);
case LESS_THAN:
- return lessThan(t.position(), lhs, rhs);
+ return lessThan(position, lhs, rhs);
case GREATER_THAN:
- return greaterThan(t.position(), lhs, rhs);
+ return greaterThan(position, lhs, rhs);
case LESS_THAN_EQ:
- return lessThanEqual(t.position(), lhs, rhs);
+ return lessThanEqual(position, lhs, rhs);
case GREATER_THAN_EQ:
- return greaterThanEqual(t.position(), lhs, rhs);
+ return greaterThanEqual(position, lhs, rhs);
case EQUAL:
- return equal(t.position(), lhs, rhs);
+ return equal(position, lhs, rhs);
case NOT_EQUAL:
- return notEqual(t.position(), lhs, rhs);
+ return notEqual(position, lhs, rhs);
case AND:
- return new Const.BooleanValue(lhs.asBoolean().value() && rhs.asBoolean().value());
+ return new Const.BooleanValue(
+ asBoolean(position, lhs).value() && asBoolean(position, rhs).value());
case OR:
- return new Const.BooleanValue(lhs.asBoolean().value() || rhs.asBoolean().value());
+ return new Const.BooleanValue(
+ asBoolean(position, lhs).value() || asBoolean(position, rhs).value());
case BITWISE_AND:
- return bitwiseAnd(t.position(), lhs, rhs);
+ return bitwiseAnd(position, lhs, rhs);
case BITWISE_XOR:
- return bitwiseXor(t.position(), lhs, rhs);
+ return bitwiseXor(position, lhs, rhs);
case BITWISE_OR:
- return bitwiseOr(t.position(), lhs, rhs);
+ return bitwiseOr(position, lhs, rhs);
default:
- throw new AssertionError(t.op());
+ throw new AssertionError(op);
}
}
- private Const.Value promoteUnary(int position, Value v) {
+ private Value promoteUnary(int position, Value v) {
switch (v.constantTypeKind()) {
case CHAR:
case SHORT:
case BYTE:
- return v.asInteger();
+ return asInt(position, v);
case INT:
case LONG:
case FLOAT:
@@ -857,7 +1151,7 @@ public strictfp class ConstEvaluator {
}
}
- private TurbineConstantTypeKind promoteBinary(int position, Const.Value a, Const.Value b) {
+ private TurbineConstantTypeKind promoteBinary(int position, Value a, Value b) {
a = promoteUnary(position, a);
b = promoteUnary(position, b);
switch (a.constantTypeKind()) {
@@ -925,12 +1219,16 @@ public strictfp class ConstEvaluator {
if (info.sym() == null) {
return info;
}
-
- Map<String, Type> template = new LinkedHashMap<>();
- TypeBoundClass annoClass = env.get(info.sym());
+ TypeBoundClass annoClass = env.getNonNull(info.sym());
+ if (annoClass.kind() != TurbineTyKind.ANNOTATION) {
+ // we've already reported an error for non-annotation symbols used as annotations,
+ // skip error handling for annotation arguments
+ return info;
+ }
+ Map<String, MethodInfo> template = new LinkedHashMap<>();
if (annoClass != null) {
for (MethodInfo method : annoClass.methods()) {
- template.put(method.name(), method.returnType());
+ template.put(method.name(), method);
}
}
@@ -943,34 +1241,47 @@ public strictfp class ConstEvaluator {
key = assign.name().value();
expr = assign.expr();
} else {
+ if (info.args().size() != 1) {
+ throw error(arg.position(), ErrorKind.ANNOTATION_VALUE_NAME);
+ }
// expand the implicit 'value' name; `@Foo(42)` is sugar for `@Foo(value=42)`
key = "value";
expr = arg;
}
- Type ty = template.get(key);
- if (ty == null) {
- throw error(
+ MethodInfo methodInfo = template.remove(key);
+ if (methodInfo == null) {
+ log.error(
arg.position(),
ErrorKind.CANNOT_RESOLVE,
String.format("element %s() in %s", key, info.sym()));
+ continue;
}
- Const value = evalAnnotationValue(expr, ty);
+ Const value = evalAnnotationValue(expr, methodInfo.returnType());
if (value == null) {
- throw error(expr.position(), ErrorKind.EXPRESSION_ERROR);
+ log.error(expr.position(), ErrorKind.EXPRESSION_ERROR);
+ continue;
}
Const existing = values.put(key, value);
if (existing != null) {
- throw error(arg.position(), ErrorKind.INVALID_ANNOTATION_ARGUMENT);
+ log.error(arg.position(), ErrorKind.INVALID_ANNOTATION_ARGUMENT);
+ continue;
+ }
+ }
+ for (MethodInfo methodInfo : template.values()) {
+ if (!methodInfo.hasDefaultValue()) {
+ throw error(
+ info.tree().position(), ErrorKind.MISSING_ANNOTATION_ARGUMENT, methodInfo.name());
}
}
return info.withValues(ImmutableMap.copyOf(values));
}
- private TurbineAnnotationValue evalAnno(Tree.Anno t) {
+ private @Nullable TurbineAnnotationValue evalAnno(Tree.Anno t) {
LookupResult result = scope.lookup(new LookupKey(t.name()));
if (result == null) {
- throw error(
+ log.error(
t.name().get(0).position(), ErrorKind.CANNOT_RESOLVE, Joiner.on(".").join(t.name()));
+ return null;
}
ClassSymbol sym = (ClassSymbol) result.sym();
for (Ident name : result.remaining()) {
@@ -982,11 +1293,14 @@ public strictfp class ConstEvaluator {
if (sym == null) {
return null;
}
+ if (env.getNonNull(sym).kind() != TurbineTyKind.ANNOTATION) {
+ log.error(t.position(), ErrorKind.NOT_AN_ANNOTATION, sym);
+ }
AnnoInfo annoInfo = evaluateAnnotation(new AnnoInfo(source, sym, t, ImmutableMap.of()));
return new TurbineAnnotationValue(annoInfo);
}
- private Const.ArrayInitValue evalArrayInit(ArrayInit t) {
+ private @Nullable ArrayInitValue evalArrayInit(ArrayInit t) {
ImmutableList.Builder<Const> elements = ImmutableList.builder();
for (Expression e : t.exprs()) {
Const arg = eval(e);
@@ -998,20 +1312,22 @@ public strictfp class ConstEvaluator {
return new Const.ArrayInitValue(elements.build());
}
+ @Nullable
Const evalAnnotationValue(Tree tree, Type ty) {
if (ty == null) {
throw error(tree.position(), ErrorKind.EXPRESSION_ERROR);
}
Const value = eval(tree);
if (value == null) {
- throw error(tree.position(), ErrorKind.EXPRESSION_ERROR);
+ log.error(tree.position(), ErrorKind.EXPRESSION_ERROR);
+ return null;
}
switch (ty.tyKind()) {
case PRIM_TY:
- if (!(value instanceof Const.Value)) {
+ if (!(value instanceof Value)) {
throw error(tree.position(), ErrorKind.EXPRESSION_ERROR);
}
- return coerce((Const.Value) value, ((Type.PrimTy) ty).primkind());
+ return coerce(tree.position(), (Value) value, ((Type.PrimTy) ty).primkind());
case CLASS_TY:
case TY_VAR:
return value;
@@ -1024,7 +1340,7 @@ public strictfp class ConstEvaluator {
: ImmutableList.of(value);
ImmutableList.Builder<Const> coerced = ImmutableList.builder();
for (Const element : elements) {
- coerced.add(cast(elementType, element));
+ coerced.add(cast(tree.position(), elementType, element));
}
return new Const.ArrayInitValue(coerced.build());
}
@@ -1037,13 +1353,17 @@ public strictfp class ConstEvaluator {
return TurbineError.format(source, position, kind, args);
}
- public Const.Value evalFieldInitializer(Expression expression, Type type) {
+ private TurbineError typeError(int position, Value value, TurbineConstantTypeKind kind) {
+ return error(position, ErrorKind.TYPE_CONVERSION, value, value.constantTypeKind(), kind);
+ }
+
+ public @Nullable Value evalFieldInitializer(Expression expression, Type type) {
try {
Const value = eval(expression);
if (value == null || value.kind() != Const.Kind.PRIMITIVE) {
return null;
}
- return (Const.Value) cast(type, value);
+ return (Value) cast(expression.position(), type, value);
} catch (TurbineError error) {
for (TurbineDiagnostic diagnostic : error.diagnostics()) {
switch (diagnostic.kind()) {
diff --git a/java/com/google/turbine/binder/CtSymClassBinder.java b/java/com/google/turbine/binder/CtSymClassBinder.java
index a6f1b3d..f0e21f2 100644
--- a/java/com/google/turbine/binder/CtSymClassBinder.java
+++ b/java/com/google/turbine/binder/CtSymClassBinder.java
@@ -16,8 +16,11 @@
package com.google.turbine.binder;
+import static com.google.common.base.Ascii.toUpperCase;
import static com.google.common.base.StandardSystemProperty.JAVA_HOME;
+import static java.util.Objects.requireNonNull;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
@@ -37,15 +40,17 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** Constructs a platform {@link ClassPath} from the current JDK's ct.sym file. */
-public class CtSymClassBinder {
+public final class CtSymClassBinder {
- @Nullable
- public static ClassPath bind(String version) throws IOException {
- Path javaHome = Paths.get(JAVA_HOME.value());
- Path ctSym = javaHome.resolve("lib/ct.sym");
+ private static final int FEATURE_VERSION = Runtime.version().feature();
+
+ public static @Nullable ClassPath bind(int version) throws IOException {
+ String javaHome = JAVA_HOME.value();
+ requireNonNull(javaHome, "attempted to use --release, but JAVA_HOME is not set");
+ Path ctSym = Paths.get(javaHome).resolve("lib/ct.sym");
if (!Files.exists(ctSym)) {
throw new IllegalStateException("lib/ct.sym does not exist in " + javaHome);
}
@@ -54,12 +59,14 @@ public class CtSymClassBinder {
Env<ClassSymbol, BytecodeBoundClass> benv =
new Env<ClassSymbol, BytecodeBoundClass>() {
@Override
- public BytecodeBoundClass get(ClassSymbol sym) {
+ public @Nullable BytecodeBoundClass get(ClassSymbol sym) {
return map.get(sym);
}
};
// ct.sym contains directories whose names are the concatentation of a list of target versions
- // (e.g. 789) and which contain interface class files with a .sig extension.
+ // formatted as a single character 0-9 or A-Z (e.g. 789A) and which contain interface class
+ // files with a .sig extension.
+ String releaseString = formatReleaseVersion(version);
for (Zip.Entry ze : new Zip.ZipIterable(ctSym)) {
String name = ze.name();
if (!name.endsWith(".sig")) {
@@ -70,10 +77,13 @@ public class CtSymClassBinder {
continue;
}
// check if the directory matches the desired release
- // TODO(cushon): what happens when version numbers contain more than one digit?
- if (!ze.name().substring(0, idx).contains(version)) {
+ if (!ze.name().substring(0, idx).contains(releaseString)) {
continue;
}
+ if (FEATURE_VERSION >= 12) {
+ // JDK >= 12 includes the module name as a prefix
+ idx = name.indexOf('/', idx + 1);
+ }
if (name.substring(name.lastIndexOf('/') + 1).equals("module-info.sig")) {
ModuleInfo moduleInfo = BytecodeBinder.bindModuleInfo(name, toByteArrayOrDie(ze));
modules.put(new ModuleSymbol(moduleInfo.name()), moduleInfo);
@@ -107,7 +117,7 @@ public class CtSymClassBinder {
}
@Override
- public Supplier<byte[]> resource(String input) {
+ public @Nullable Supplier<byte[]> resource(String input) {
return null;
}
};
@@ -122,4 +132,14 @@ public class CtSymClassBinder {
}
});
}
+
+ @VisibleForTesting
+ static String formatReleaseVersion(int n) {
+ if (n <= 4 || n >= 36) {
+ throw new IllegalArgumentException("invalid release version: " + n);
+ }
+ return toUpperCase(Integer.toString(n, 36));
+ }
+
+ private CtSymClassBinder() {}
}
diff --git a/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java b/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java
index 7e3fbda..65c1021 100644
--- a/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java
+++ b/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java
@@ -30,6 +30,7 @@ import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo;
import com.google.turbine.binder.bound.TypeBoundClass.ParamInfo;
+import com.google.turbine.binder.bound.TypeBoundClass.RecordComponentInfo;
import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.diag.TurbineError;
@@ -65,14 +66,16 @@ import java.util.Map;
* constant binding is done, read the {@code @Target} meta-annotation for each ambiguous annotation,
* and move it to the appropriate location.
*/
-public class DisambiguateTypeAnnotations {
+public final class DisambiguateTypeAnnotations {
public static SourceTypeBoundClass bind(
SourceTypeBoundClass base, Env<ClassSymbol, TypeBoundClass> env) {
return new SourceTypeBoundClass(
base.interfaceTypes(),
+ base.permits(),
base.superClassType(),
base.typeParameterTypes(),
base.access(),
+ bindComponents(env, base.components(), TurbineElementType.RECORD_COMPONENT),
bindMethods(env, base.methods()),
bindFields(env, base.fields()),
base.owner(),
@@ -112,36 +115,58 @@ public class DisambiguateTypeAnnotations {
base.sym(),
base.tyParams(),
returnType,
- bindParameters(env, base.parameters()),
+ bindParameters(env, base.parameters(), TurbineElementType.PARAMETER),
base.exceptions(),
base.access(),
base.defaultValue(),
base.decl(),
declarationAnnotations.build(),
- base.receiver() != null ? bindParam(env, base.receiver()) : null);
+ base.receiver() != null
+ ? bindParam(env, base.receiver(), TurbineElementType.PARAMETER)
+ : null);
}
private static ImmutableList<ParamInfo> bindParameters(
- Env<ClassSymbol, TypeBoundClass> env, ImmutableList<ParamInfo> params) {
+ Env<ClassSymbol, TypeBoundClass> env,
+ ImmutableList<ParamInfo> params,
+ TurbineElementType declarationTarget) {
ImmutableList.Builder<ParamInfo> result = ImmutableList.builder();
for (ParamInfo param : params) {
- result.add(bindParam(env, param));
+ result.add(bindParam(env, param, declarationTarget));
}
return result.build();
}
- private static ParamInfo bindParam(Env<ClassSymbol, TypeBoundClass> env, ParamInfo base) {
+ private static ParamInfo bindParam(
+ Env<ClassSymbol, TypeBoundClass> env, ParamInfo base, TurbineElementType declarationTarget) {
ImmutableList.Builder<AnnoInfo> declarationAnnotations = ImmutableList.builder();
Type type =
disambiguate(
- env,
- TurbineElementType.PARAMETER,
- base.type(),
- base.annotations(),
- declarationAnnotations);
+ env, declarationTarget, base.type(), base.annotations(), declarationAnnotations);
return new ParamInfo(base.sym(), type, declarationAnnotations.build(), base.access());
}
+ private static ImmutableList<RecordComponentInfo> bindComponents(
+ Env<ClassSymbol, TypeBoundClass> env,
+ ImmutableList<RecordComponentInfo> components,
+ TurbineElementType declarationTarget) {
+ ImmutableList.Builder<RecordComponentInfo> result = ImmutableList.builder();
+ for (RecordComponentInfo component : components) {
+ ImmutableList.Builder<AnnoInfo> declarationAnnotations = ImmutableList.builder();
+ Type type =
+ disambiguate(
+ env,
+ declarationTarget,
+ component.type(),
+ component.annotations(),
+ declarationAnnotations);
+ result.add(
+ new RecordComponentInfo(
+ component.sym(), type, declarationAnnotations.build(), component.access()));
+ }
+ return result.build();
+ }
+
/**
* Moves type annotations in {@code annotations} to {@code type}, and adds any declaration
* annotations on {@code type} to {@code declarationAnnotations}.
@@ -317,4 +342,6 @@ public class DisambiguateTypeAnnotations {
}
return false;
}
+
+ private DisambiguateTypeAnnotations() {}
}
diff --git a/java/com/google/turbine/binder/FileManagerClassBinder.java b/java/com/google/turbine/binder/FileManagerClassBinder.java
new file mode 100644
index 0000000..d36d2d8
--- /dev/null
+++ b/java/com/google/turbine/binder/FileManagerClassBinder.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2020 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.google.turbine.binder;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.ByteStreams;
+import com.google.turbine.binder.bound.ModuleInfo;
+import com.google.turbine.binder.bytecode.BytecodeBoundClass;
+import com.google.turbine.binder.env.Env;
+import com.google.turbine.binder.env.SimpleEnv;
+import com.google.turbine.binder.lookup.LookupKey;
+import com.google.turbine.binder.lookup.LookupResult;
+import com.google.turbine.binder.lookup.PackageScope;
+import com.google.turbine.binder.lookup.Scope;
+import com.google.turbine.binder.lookup.TopLevelIndex;
+import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.binder.sym.ModuleSymbol;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import javax.tools.FileObject;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import org.jspecify.nullness.Nullable;
+
+/**
+ * Binds a {@link StandardJavaFileManager} to an {@link ClassPath}. This can be used to share a
+ * filemanager (and associated IO costs) between turbine and javac when running both in the same
+ * process.
+ */
+public final class FileManagerClassBinder {
+
+ public static ClassPath adapt(StandardJavaFileManager fileManager, StandardLocation location) {
+ PackageLookup packageLookup = new PackageLookup(fileManager, location);
+ Env<ClassSymbol, BytecodeBoundClass> env =
+ new Env<ClassSymbol, BytecodeBoundClass>() {
+ @Override
+ public @Nullable BytecodeBoundClass get(ClassSymbol sym) {
+ return packageLookup.getPackage(this, sym.packageName()).get(sym);
+ }
+ };
+ SimpleEnv<ModuleSymbol, ModuleInfo> moduleEnv = new SimpleEnv<>(ImmutableMap.of());
+ TopLevelIndex tli = new FileManagerTopLevelIndex(env, packageLookup);
+ return new ClassPath() {
+ @Override
+ public Env<ClassSymbol, BytecodeBoundClass> env() {
+ return env;
+ }
+
+ @Override
+ public Env<ModuleSymbol, ModuleInfo> moduleEnv() {
+ return moduleEnv;
+ }
+
+ @Override
+ public TopLevelIndex index() {
+ return tli;
+ }
+
+ @Override
+ public @Nullable Supplier<byte[]> resource(String path) {
+ return packageLookup.resource(path);
+ }
+ };
+ }
+
+ private static class PackageLookup {
+
+ private final Map<String, Map<ClassSymbol, BytecodeBoundClass>> packages = new HashMap<>();
+ private final StandardJavaFileManager fileManager;
+ private final StandardLocation location;
+
+ private PackageLookup(StandardJavaFileManager fileManager, StandardLocation location) {
+ this.fileManager = fileManager;
+ this.location = location;
+ }
+
+ private ImmutableMap<ClassSymbol, BytecodeBoundClass> listPackage(
+ Env<ClassSymbol, BytecodeBoundClass> env, String packageName) throws IOException {
+ Map<ClassSymbol, BytecodeBoundClass> result = new HashMap<>();
+ for (JavaFileObject jfo :
+ fileManager.list(
+ location,
+ packageName.replace('/', '.'),
+ EnumSet.of(JavaFileObject.Kind.CLASS),
+ false)) {
+ String binaryName = fileManager.inferBinaryName(location, jfo);
+ ClassSymbol sym = new ClassSymbol(binaryName.replace('.', '/'));
+ result.putIfAbsent(
+ sym,
+ new BytecodeBoundClass(
+ sym,
+ new Supplier<byte[]>() {
+ @Override
+ public byte[] get() {
+ try {
+ return ByteStreams.toByteArray(jfo.openInputStream());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ },
+ env,
+ /* jarFile= */ null));
+ }
+ return ImmutableMap.copyOf(result);
+ }
+
+ private Map<ClassSymbol, BytecodeBoundClass> getPackage(
+ Env<ClassSymbol, BytecodeBoundClass> env, String key) {
+ return packages.computeIfAbsent(
+ key,
+ k -> {
+ try {
+ return listPackage(env, key);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ });
+ }
+
+ public @Nullable Supplier<byte[]> resource(String resource) {
+ String dir;
+ String name;
+ int idx = resource.lastIndexOf('/');
+ if (idx != -1) {
+ dir = resource.substring(0, idx + 1);
+ name = resource.substring(idx + 1, resource.length());
+ } else {
+ dir = "";
+ name = resource;
+ }
+ FileObject fileObject;
+ try {
+ fileObject = fileManager.getFileForInput(location, dir, name);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ if (fileObject == null) {
+ return null;
+ }
+ return new Supplier<byte[]>() {
+ @Override
+ public byte[] get() {
+ try {
+ return ByteStreams.toByteArray(fileObject.openInputStream());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ };
+ }
+ }
+
+ private static class FileManagerTopLevelIndex implements TopLevelIndex {
+ private final Env<ClassSymbol, BytecodeBoundClass> env;
+ private final PackageLookup packageLookup;
+
+ public FileManagerTopLevelIndex(
+ Env<ClassSymbol, BytecodeBoundClass> env, PackageLookup packageLookup) {
+ this.env = env;
+ this.packageLookup = packageLookup;
+ }
+
+ @Override
+ public Scope scope() {
+ return new Scope() {
+ @Override
+ public @Nullable LookupResult lookup(LookupKey lookupKey) {
+ for (int i = lookupKey.simpleNames().size(); i > 0; i--) {
+ String p = Joiner.on('/').join(lookupKey.simpleNames().subList(0, i));
+ ClassSymbol sym = new ClassSymbol(p);
+ BytecodeBoundClass r = env.get(sym);
+ if (r != null) {
+ return new LookupResult(
+ sym,
+ new LookupKey(
+ lookupKey.simpleNames().subList(i - 1, lookupKey.simpleNames().size())));
+ }
+ }
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public @Nullable PackageScope lookupPackage(Iterable<String> names) {
+ String packageName = Joiner.on('/').join(names);
+ Map<ClassSymbol, BytecodeBoundClass> pkg = packageLookup.getPackage(env, packageName);
+ if (pkg.isEmpty()) {
+ return null;
+ }
+ return new PackageScope() {
+ @Override
+ public Iterable<ClassSymbol> classes() {
+ return pkg.keySet();
+ }
+
+ @Override
+ public @Nullable LookupResult lookup(LookupKey lookupKey) {
+ String className = lookupKey.first().value();
+ if (!packageName.isEmpty()) {
+ className = packageName + "/" + className;
+ }
+ ClassSymbol sym = new ClassSymbol(className);
+ if (!pkg.containsKey(sym)) {
+ return null;
+ }
+ return new LookupResult(sym, lookupKey);
+ }
+ };
+ }
+ }
+
+ private FileManagerClassBinder() {}
+}
diff --git a/java/com/google/turbine/binder/HierarchyBinder.java b/java/com/google/turbine/binder/HierarchyBinder.java
index 07d255c..ac2c840 100644
--- a/java/com/google/turbine/binder/HierarchyBinder.java
+++ b/java/com/google/turbine/binder/HierarchyBinder.java
@@ -34,6 +34,7 @@ import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.tree.Tree;
import com.google.turbine.tree.Tree.ClassTy;
import java.util.ArrayDeque;
+import org.jspecify.nullness.Nullable;
/** Type hierarchy binding. */
public class HierarchyBinder {
@@ -82,6 +83,9 @@ public class HierarchyBinder {
case CLASS:
superclass = !origin.equals(ClassSymbol.OBJECT) ? ClassSymbol.OBJECT : null;
break;
+ case RECORD:
+ superclass = ClassSymbol.RECORD;
+ break;
default:
throw new AssertionError(decl.tykind());
}
@@ -110,14 +114,15 @@ public class HierarchyBinder {
typeParameters.put(p.name().value(), new TyVarSymbol(origin, p.name().value()));
}
- return new SourceHeaderBoundClass(base, superclass, interfaces.build(), typeParameters.build());
+ return new SourceHeaderBoundClass(
+ base, superclass, interfaces.build(), typeParameters.buildOrThrow());
}
/**
* Resolves the {@link ClassSymbol} for the given {@link Tree.ClassTy}, with handling for
* non-canonical qualified type names.
*/
- private ClassSymbol resolveClass(Tree.ClassTy ty) {
+ private @Nullable ClassSymbol resolveClass(Tree.ClassTy ty) {
// flatten a left-recursive qualified type name to its component simple names
// e.g. Foo<Bar>.Baz -> ["Foo", "Bar"]
ArrayDeque<Tree.Ident> flat = new ArrayDeque<>();
@@ -142,7 +147,7 @@ public class HierarchyBinder {
return sym;
}
- private ClassSymbol resolveNext(ClassTy ty, ClassSymbol sym, Tree.Ident bit) {
+ private @Nullable ClassSymbol resolveNext(ClassTy ty, ClassSymbol sym, Tree.Ident bit) {
ClassSymbol next;
try {
next = Resolve.resolve(env, origin, sym, bit);
@@ -160,11 +165,11 @@ public class HierarchyBinder {
}
/** Resolve a qualified type name to a symbol. */
- private LookupResult lookup(Tree tree, LookupKey lookup) {
+ private @Nullable LookupResult lookup(Tree tree, LookupKey lookup) {
// Handle any lexically enclosing class declarations (if we're binding a member class).
// We could build out scopes for this, but it doesn't seem worth it. (And sharing the scopes
// with other members of the same enclosing declaration would be complicated.)
- for (ClassSymbol curr = base.owner(); curr != null; curr = env.get(curr).owner()) {
+ for (ClassSymbol curr = base.owner(); curr != null; curr = env.getNonNull(curr).owner()) {
ClassSymbol result;
try {
result = Resolve.resolve(env, origin, curr, lookup.first());
diff --git a/java/com/google/turbine/binder/JimageClassBinder.java b/java/com/google/turbine/binder/JimageClassBinder.java
index d11dda1..53a6a3a 100644
--- a/java/com/google/turbine/binder/JimageClassBinder.java
+++ b/java/com/google/turbine/binder/JimageClassBinder.java
@@ -17,6 +17,7 @@
package com.google.turbine.binder;
import static com.google.common.base.StandardSystemProperty.JAVA_HOME;
+import static java.util.Objects.requireNonNull;
import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
@@ -52,7 +53,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** Constructs a platform {@link ClassPath} from the current JDK's jimage file using jrtfs. */
public class JimageClassBinder {
@@ -104,11 +105,13 @@ public class JimageClassBinder {
this.modulesRoot = modules;
}
+ @Nullable
Path modulePath(String moduleName) {
Path path = modulesRoot.resolve(moduleName);
return Files.exists(path) ? path : null;
}
+ @Nullable
ModuleInfo module(String moduleName) {
ModuleInfo result = moduleMap.get(moduleName);
if (result == null) {
@@ -134,13 +137,14 @@ public class JimageClassBinder {
Env<ClassSymbol, BytecodeBoundClass> env =
new Env<ClassSymbol, BytecodeBoundClass>() {
@Override
- public BytecodeBoundClass get(ClassSymbol sym) {
+ public @Nullable BytecodeBoundClass get(ClassSymbol sym) {
return JimageClassBinder.this.env.get(sym);
}
};
for (String moduleName : moduleNames) {
if (moduleName != null) {
- Path modulePath = modulePath(moduleName);
+ // TODO(cushon): is this requireNonNull safe?
+ Path modulePath = requireNonNull(modulePath(moduleName), moduleName);
Path modulePackagePath = modulePath.resolve(packageName);
try (DirectoryStream<Path> ds = Files.newDirectoryStream(modulePackagePath)) {
for (Path path : ds) {
@@ -181,9 +185,8 @@ public class JimageClassBinder {
final Scope topLevelScope =
new Scope() {
- @Nullable
@Override
- public LookupResult lookup(LookupKey lookupKey) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey) {
// Find the longest prefix of the key that corresponds to a package name.
// TODO(cushon): SimpleTopLevelIndex uses a prefix map for this, does it matter?
Scope scope = null;
@@ -213,15 +216,14 @@ public class JimageClassBinder {
}
@Override
- public PackageScope lookupPackage(Iterable<String> name) {
+ public @Nullable PackageScope lookupPackage(Iterable<String> name) {
String packageName = Joiner.on('/').join(name);
if (!initPackage(packageName)) {
return null;
}
return new PackageScope() {
- @Nullable
@Override
- public LookupResult lookup(LookupKey lookupKey) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey) {
ClassSymbol sym = packageClassesBySimpleName.get(packageName, lookupKey.first().value());
return sym != null ? new LookupResult(sym, lookupKey) : null;
}
@@ -242,7 +244,7 @@ public class JimageClassBinder {
public Env<ClassSymbol, BytecodeBoundClass> env() {
return new Env<ClassSymbol, BytecodeBoundClass>() {
@Override
- public BytecodeBoundClass get(ClassSymbol sym) {
+ public @Nullable BytecodeBoundClass get(ClassSymbol sym) {
return initPackage(sym.packageName()) ? env.get(sym) : null;
}
};
@@ -252,7 +254,7 @@ public class JimageClassBinder {
public Env<ModuleSymbol, ModuleInfo> moduleEnv() {
return new Env<ModuleSymbol, ModuleInfo>() {
@Override
- public ModuleInfo get(ModuleSymbol module) {
+ public @Nullable ModuleInfo get(ModuleSymbol module) {
return module(module.name());
}
};
@@ -264,7 +266,7 @@ public class JimageClassBinder {
}
@Override
- public Supplier<byte[]> resource(String input) {
+ public @Nullable Supplier<byte[]> resource(String input) {
return null;
}
}
diff --git a/java/com/google/turbine/binder/ModuleBinder.java b/java/com/google/turbine/binder/ModuleBinder.java
index 748ff39..e88440d 100644
--- a/java/com/google/turbine/binder/ModuleBinder.java
+++ b/java/com/google/turbine/binder/ModuleBinder.java
@@ -16,8 +16,6 @@
package com.google.turbine.binder;
-import static com.google.common.base.Verify.verifyNotNull;
-
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -139,16 +137,16 @@ public class ModuleBinder {
}
}
if (!requiresJavaBase) {
- // everything requires java.base, either explicitly or implicitly
+ // Everything requires java.base, either explicitly or implicitly.
ModuleInfo javaBaseModule = moduleEnv.get(ModuleSymbol.JAVA_BASE);
- verifyNotNull(javaBaseModule, ModuleSymbol.JAVA_BASE.name());
+ // Tolerate a missing java.base module, e.g. when compiling a module against a non-modular
+ // bootclasspath, and just omit the version below.
+ String javaBaseVersion = javaBaseModule != null ? javaBaseModule.version() : null;
requires =
ImmutableList.<RequireInfo>builder()
.add(
new RequireInfo(
- ModuleSymbol.JAVA_BASE.name(),
- TurbineFlag.ACC_MANDATED,
- javaBaseModule.version()))
+ ModuleSymbol.JAVA_BASE.name(), TurbineFlag.ACC_MANDATED, javaBaseVersion))
.addAll(requires.build());
}
@@ -216,11 +214,12 @@ public class ModuleBinder {
}
ClassSymbol sym = (ClassSymbol) result.sym();
for (Tree.Ident name : result.remaining()) {
- sym = Resolve.resolve(env, /* origin= */ null, sym, name);
- if (sym == null) {
+ ClassSymbol next = Resolve.resolve(env, /* origin= */ null, sym, name);
+ if (next == null) {
throw error(
ErrorKind.SYMBOL_NOT_FOUND, pos, new ClassSymbol(sym.binaryName() + '$' + name));
}
+ sym = next;
}
return sym;
}
diff --git a/java/com/google/turbine/binder/Processing.java b/java/com/google/turbine/binder/Processing.java
index ecdf195..616bf2c 100644
--- a/java/com/google/turbine/binder/Processing.java
+++ b/java/com/google/turbine/binder/Processing.java
@@ -16,6 +16,8 @@
package com.google.turbine.binder;
+import static java.util.Objects.requireNonNull;
+
import com.google.auto.value.AutoValue;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
@@ -56,9 +58,7 @@ import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -71,12 +71,12 @@ import javax.annotation.processing.Processor;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** Top level annotation processing logic, see also {@link Binder}. */
public class Processing {
- static BindingResult process(
+ static @Nullable BindingResult process(
TurbineLog log,
final ImmutableList<CompUnit> initialSources,
final ClassPath classpath,
@@ -95,10 +95,9 @@ public class Processing {
TurbineFiler filer =
new TurbineFiler(
seen,
- new Function<String, Supplier<byte[]>>() {
- @Nullable
+ new Function<String, @Nullable Supplier<byte[]>>() {
@Override
- public Supplier<byte[]> apply(@Nullable String input) {
+ public @Nullable Supplier<byte[]> apply(String input) {
// TODO(cushon): should annotation processors be allowed to generate code with
// dependencies between source and bytecode, or vice versa?
// Currently generated classes are not available on the classpath when compiling
@@ -131,19 +130,13 @@ public class Processing {
try (Timers.Timer unused = timers.start(processor)) {
processor.init(processingEnv);
} catch (Throwable t) {
- reportProcessorCrash(log, processor, t);
+ logProcessorCrash(log, processor, t);
+ return null;
}
}
- Map<Processor, Pattern> wanted = new HashMap<>();
- for (Processor processor : processorInfo.processors()) {
- List<String> patterns = new ArrayList<>();
- for (String supportedAnnotationType : processor.getSupportedAnnotationTypes()) {
- // TODO(b/139026291): this handling of getSupportedAnnotationTypes isn't correct
- patterns.add(supportedAnnotationType.replace("*", ".*"));
- }
- wanted.put(processor, Pattern.compile(Joiner.on('|').join(patterns)));
- }
+ ImmutableMap<Processor, SupportedAnnotationTypes> wanted =
+ initializeSupportedAnnotationTypes(processorInfo);
Set<ClassSymbol> allSymbols = new HashSet<>();
@@ -163,12 +156,14 @@ public class Processing {
}
ImmutableSetMultimap<ClassSymbol, Symbol> allAnnotations = getAllAnnotations(env, syms);
TurbineRoundEnvironment roundEnv = null;
- for (Processor processor : processorInfo.processors()) {
+ for (Map.Entry<Processor, SupportedAnnotationTypes> e : wanted.entrySet()) {
+ Processor processor = e.getKey();
+ SupportedAnnotationTypes supportedAnnotationTypes = e.getValue();
Set<TypeElement> annotations = new HashSet<>();
- Pattern pattern = wanted.get(processor);
- boolean run = toRun.contains(processor);
+ boolean run = supportedAnnotationTypes.everything() || toRun.contains(processor);
for (ClassSymbol a : allAnnotations.keys()) {
- if (pattern.matcher(a.toString()).matches()) {
+ if (supportedAnnotationTypes.everything()
+ || supportedAnnotationTypes.pattern().matcher(a.toString()).matches()) {
annotations.add(factory.typeElement(a));
run = true;
}
@@ -184,7 +179,8 @@ public class Processing {
// TODO(cushon): consider disallowing this, or reporting a diagnostic
processor.process(annotations, roundEnv);
} catch (Throwable t) {
- reportProcessorCrash(log, processor, t);
+ logProcessorCrash(log, processor, t);
+ return null;
}
}
}
@@ -197,7 +193,7 @@ public class Processing {
}
errorRaised = log.errorRaised();
if (errorRaised) {
- log.maybeThrow();
+ break;
}
log.clear();
result =
@@ -228,7 +224,8 @@ public class Processing {
try (Timers.Timer unused = timers.start(processor)) {
processor.process(ImmutableSet.of(), roundEnv);
} catch (Throwable t) {
- reportProcessorCrash(log, processor, t);
+ logProcessorCrash(log, processor, t);
+ return null;
}
}
@@ -249,7 +246,9 @@ public class Processing {
classpath,
bootclasspath,
moduleVersion);
- log.maybeThrow();
+ if (log.anyErrors()) {
+ return null;
+ }
}
if (!filer.generatedClasses().isEmpty()) {
@@ -267,13 +266,44 @@ public class Processing {
return result;
}
- private static void reportProcessorCrash(TurbineLog log, Processor processor, Throwable t) {
+ private static ImmutableMap<Processor, SupportedAnnotationTypes>
+ initializeSupportedAnnotationTypes(ProcessorInfo processorInfo) {
+ ImmutableMap.Builder<Processor, SupportedAnnotationTypes> result = ImmutableMap.builder();
+ for (Processor processor : processorInfo.processors()) {
+ result.put(processor, SupportedAnnotationTypes.create(processor));
+ }
+ return result.buildOrThrow();
+ }
+
+ @AutoValue
+ abstract static class SupportedAnnotationTypes {
+
+ abstract boolean everything();
+
+ abstract Pattern pattern();
+
+ static SupportedAnnotationTypes create(Processor processor) {
+ List<String> patterns = new ArrayList<>();
+ boolean everything = false;
+ for (String supportedAnnotationType : processor.getSupportedAnnotationTypes()) {
+ if (supportedAnnotationType.equals("*")) {
+ everything = true;
+ } else {
+ // TODO(b/139026291): this handling of getSupportedAnnotationTypes isn't correct
+ patterns.add(supportedAnnotationType);
+ }
+ }
+ return new AutoValue_Processing_SupportedAnnotationTypes(
+ everything, Pattern.compile(Joiner.on('|').join(patterns)));
+ }
+ }
+
+ private static void logProcessorCrash(TurbineLog log, Processor processor, Throwable t) {
log.diagnostic(
Diagnostic.Kind.ERROR,
String.format(
"An exception occurred in %s:\n%s",
processor.getClass().getCanonicalName(), Throwables.getStackTraceAsString(t)));
- log.maybeThrow();
}
/** Returns a map from annotations present in the compilation to the annotated elements. */
@@ -281,7 +311,7 @@ public class Processing {
Env<ClassSymbol, TypeBoundClass> env, Iterable<ClassSymbol> syms) {
ImmutableSetMultimap.Builder<ClassSymbol, Symbol> result = ImmutableSetMultimap.builder();
for (ClassSymbol sym : syms) {
- TypeBoundClass info = env.get(sym);
+ TypeBoundClass info = env.getNonNull(sym);
for (AnnoInfo annoInfo : info.annotations()) {
if (sym.simpleName().equals("package-info")) {
addAnno(result, annoInfo, sym.owner());
@@ -313,8 +343,8 @@ public class Processing {
}
// TODO(cushon): consider memoizing this (or isAnnotationInherited) if they show up in profiles
- private static Set<ClassSymbol> inheritedAnnotations(
- Set<ClassSymbol> seen, ClassSymbol sym, Env<ClassSymbol, TypeBoundClass> env) {
+ private static ImmutableSet<ClassSymbol> inheritedAnnotations(
+ Set<ClassSymbol> seen, @Nullable ClassSymbol sym, Env<ClassSymbol, TypeBoundClass> env) {
ImmutableSet.Builder<ClassSymbol> result = ImmutableSet.builder();
ClassSymbol curr = sym;
while (curr != null && seen.add(curr)) {
@@ -359,88 +389,63 @@ public class Processing {
}
public static ProcessorInfo initializeProcessors(
+ SourceVersion sourceVersion,
ImmutableList<String> javacopts,
- ImmutableList<String> processorPath,
ImmutableSet<String> processorNames,
- ImmutableSet<String> builtinProcessors)
- throws MalformedURLException {
- ClassLoader processorLoader = null;
+ ClassLoader processorLoader) {
+ if (processorNames.isEmpty() || javacopts.contains("-proc:none")) {
+ return ProcessorInfo.empty();
+ }
+ ImmutableList<Processor> processors = instantiateProcessors(processorNames, processorLoader);
+ ImmutableMap<String, String> processorOptions = processorOptions(javacopts);
+ return ProcessorInfo.create(processors, processorLoader, processorOptions, sourceVersion);
+ }
+
+ private static ImmutableList<Processor> instantiateProcessors(
+ ImmutableSet<String> processorNames, ClassLoader processorLoader) {
ImmutableList.Builder<Processor> processors = ImmutableList.builder();
- ImmutableMap<String, String> processorOptions;
- if (!processorNames.isEmpty() && !javacopts.contains("-proc:none")) {
- if (!processorPath.isEmpty()) {
- processorLoader =
- new URLClassLoader(
- toUrls(processorPath),
- new ClassLoader(getPlatformClassLoader()) {
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- if (name.startsWith("com.sun.source.")
- || name.startsWith("com.sun.tools.")
- || name.startsWith("com.google.common.collect.")
- || name.startsWith("com.google.common.base.")
- || name.startsWith("com.google.common.graph.")
- || name.startsWith("com.google.devtools.build.buildjar.javac.statistics.")
- || name.startsWith("dagger.model.")
- || name.startsWith("dagger.spi.")
- || name.equals("com.google.turbine.processing.TurbineProcessingEnvironment")
- || builtinProcessors.contains(name)) {
- return Class.forName(name);
- }
- throw new ClassNotFoundException(name);
- }
- });
- } else {
- processorLoader = Processing.class.getClassLoader();
- }
- for (String processor : processorNames) {
- try {
- Class<? extends Processor> clazz =
- Class.forName(processor, false, processorLoader).asSubclass(Processor.class);
- processors.add(clazz.getConstructor().newInstance());
- } catch (ReflectiveOperationException e) {
- throw new LinkageError(e.getMessage(), e);
- }
+ for (String processor : processorNames) {
+ try {
+ Class<? extends Processor> clazz =
+ Class.forName(processor, false, processorLoader).asSubclass(Processor.class);
+ processors.add(clazz.getConstructor().newInstance());
+ } catch (ReflectiveOperationException e) {
+ throw new LinkageError(e.getMessage(), e);
}
- processorOptions = processorOptions(javacopts);
- } else {
- processorOptions = ImmutableMap.of();
}
- SourceVersion sourceVersion = SourceVersion.latestSupported();
- Iterator<String> it = javacopts.iterator();
- while (it.hasNext()) {
- String option = it.next();
- switch (option) {
- case "-target":
- if (it.hasNext()) {
- String value = it.next();
- switch (value) {
- case "5":
- case "1.5":
- sourceVersion = SourceVersion.RELEASE_5;
- break;
- case "6":
- case "1.6":
- sourceVersion = SourceVersion.RELEASE_6;
- break;
- case "7":
- case "1.7":
- sourceVersion = SourceVersion.RELEASE_7;
- break;
- case "8":
- sourceVersion = SourceVersion.RELEASE_8;
- break;
- default:
- break;
+ return processors.build();
+ }
+
+ public static ClassLoader processorLoader(
+ ImmutableList<String> processorPath, ImmutableSet<String> builtinProcessors)
+ throws MalformedURLException {
+ if (processorPath.isEmpty()) {
+ return Processing.class.getClassLoader();
+ }
+ return new URLClassLoader(
+ toUrls(processorPath),
+ new ClassLoader(ClassLoader.getPlatformClassLoader()) {
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ if (name.equals("com.google.turbine.processing.TurbineProcessingEnvironment")) {
+ return Class.forName(name);
}
+ if (!builtinProcessors.isEmpty()) {
+ if (name.startsWith("com.sun.source.")
+ || name.startsWith("com.sun.tools.")
+ || name.startsWith("com.google.common.collect.")
+ || name.startsWith("com.google.common.base.")
+ || name.startsWith("com.google.common.graph.")
+ || name.startsWith("com.google.devtools.build.buildjar.javac.statistics.")
+ || name.startsWith("dagger.model.")
+ || name.startsWith("dagger.spi.")
+ || builtinProcessors.contains(name)) {
+ return Class.forName(name);
+ }
+ }
+ throw new ClassNotFoundException(name);
}
- break;
- default:
- break;
- }
- }
- return ProcessorInfo.create(
- processors.build(), processorLoader, processorOptions, sourceVersion);
+ });
}
private static URL[] toUrls(ImmutableList<String> processorPath) throws MalformedURLException {
@@ -452,15 +457,6 @@ public class Processing {
return urls;
}
- public static ClassLoader getPlatformClassLoader() {
- try {
- return (ClassLoader) ClassLoader.class.getMethod("getPlatformClassLoader").invoke(null);
- } catch (ReflectiveOperationException e) {
- // In earlier releases, set 'null' as the parent to delegate to the boot class loader.
- return null;
- }
- }
-
private static ImmutableMap<String, String> processorOptions(ImmutableList<String> javacopts) {
Map<String, String> result = new LinkedHashMap<>(); // ImmutableMap.Builder rejects duplicates
for (String javacopt : javacopts) {
@@ -492,8 +488,7 @@ public class Processing {
* The classloader to use for annotation processor implementations, and any annotations they
* access reflectively.
*/
- @Nullable
- abstract ClassLoader loader();
+ abstract @Nullable ClassLoader loader();
/** Command line annotation processing options, passed to javac with {@code -Akey=value}. */
abstract ImmutableMap<String, String> options();
@@ -548,9 +543,12 @@ public class Processing {
ImmutableMap<String, Duration> build() {
ImmutableMap.Builder<String, Duration> result = ImmutableMap.builder();
for (Map.Entry<Class<?>, Stopwatch> e : processorTimers.entrySet()) {
- result.put(e.getKey().getCanonicalName(), e.getValue().elapsed());
+ // requireNonNull is safe, barring bizarre processor implementations (e.g., anonymous class)
+ result.put(requireNonNull(e.getKey().getCanonicalName()), e.getValue().elapsed());
}
- return result.build();
+ return result.buildOrThrow();
}
}
+
+ private Processing() {}
}
diff --git a/java/com/google/turbine/binder/Resolve.java b/java/com/google/turbine/binder/Resolve.java
index 28a8be3..6b76389 100644
--- a/java/com/google/turbine/binder/Resolve.java
+++ b/java/com/google/turbine/binder/Resolve.java
@@ -31,26 +31,27 @@ import com.google.turbine.tree.Tree;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
+import org.jspecify.nullness.Nullable;
/** Qualified name resolution. */
-public class Resolve {
+public final class Resolve {
/**
* Performs JLS 6.5.5.2 qualified type name resolution of a type with the given simple name,
* qualified by the given symbol. The search considers members that are inherited from
* superclasses or interfaces.
*/
- public static ClassSymbol resolve(
+ public static @Nullable ClassSymbol resolve(
Env<ClassSymbol, ? extends HeaderBoundClass> env,
- ClassSymbol origin,
+ @Nullable ClassSymbol origin,
ClassSymbol sym,
Tree.Ident simpleName) {
return resolve(env, origin, sym, simpleName, new HashSet<>());
}
- private static ClassSymbol resolve(
+ private static @Nullable ClassSymbol resolve(
Env<ClassSymbol, ? extends HeaderBoundClass> env,
- ClassSymbol origin,
+ @Nullable ClassSymbol origin,
ClassSymbol sym,
Tree.Ident simpleName,
Set<ClassSymbol> seen) {
@@ -69,13 +70,13 @@ public class Resolve {
}
if (bound.superclass() != null) {
result = resolve(env, origin, bound.superclass(), simpleName, seen);
- if (result != null && visible(origin, result, env.get(result))) {
+ if (result != null && visible(origin, result, env.getNonNull(result))) {
return result;
}
}
for (ClassSymbol i : bound.interfaces()) {
result = resolve(env, origin, i, simpleName, seen);
- if (result != null && visible(origin, result, env.get(result))) {
+ if (result != null && visible(origin, result, env.getNonNull(result))) {
return result;
}
}
@@ -87,10 +88,10 @@ public class Resolve {
* env} and {@code origin} symbol.
*/
public static ResolveFunction resolveFunction(
- Env<ClassSymbol, ? extends HeaderBoundClass> env, ClassSymbol origin) {
+ Env<ClassSymbol, ? extends HeaderBoundClass> env, @Nullable ClassSymbol origin) {
return new ResolveFunction() {
@Override
- public ClassSymbol resolveOne(ClassSymbol base, Tree.Ident name) {
+ public @Nullable ClassSymbol resolveOne(ClassSymbol base, Tree.Ident name) {
try {
return Resolve.resolve(env, origin, base, name);
} catch (LazyBindingError e) {
@@ -113,24 +114,24 @@ public class Resolve {
}
@Override
- public ClassSymbol resolveOne(ClassSymbol sym, Tree.Ident bit) {
+ public @Nullable ClassSymbol resolveOne(ClassSymbol sym, Tree.Ident bit) {
BoundClass ci = env.get(sym);
if (ci == null) {
return null;
}
- sym = ci.children().get(bit.value());
- if (sym == null) {
+ ClassSymbol result = ci.children().get(bit.value());
+ if (result == null) {
return null;
}
- if (!visible(sym)) {
+ if (!visible(result)) {
return null;
}
- return sym;
+ return result;
}
@Override
public boolean visible(ClassSymbol sym) {
- TurbineVisibility visibility = TurbineVisibility.fromAccess(env.get(sym).access());
+ TurbineVisibility visibility = TurbineVisibility.fromAccess(env.getNonNull(sym).access());
switch (visibility) {
case PUBLIC:
return true;
@@ -149,14 +150,17 @@ public class Resolve {
* qualified by the given symbol. The search considers members that are inherited from
* superclasses or interfaces.
*/
- public static FieldInfo resolveField(
- Env<ClassSymbol, TypeBoundClass> env, ClassSymbol origin, ClassSymbol sym, Tree.Ident name) {
+ public static @Nullable FieldInfo resolveField(
+ Env<ClassSymbol, TypeBoundClass> env,
+ @Nullable ClassSymbol origin,
+ ClassSymbol sym,
+ Tree.Ident name) {
return resolveField(env, origin, sym, name, new HashSet<>());
}
- private static FieldInfo resolveField(
+ private static @Nullable FieldInfo resolveField(
Env<ClassSymbol, TypeBoundClass> env,
- ClassSymbol origin,
+ @Nullable ClassSymbol origin,
ClassSymbol sym,
Tree.Ident name,
Set<ClassSymbol> seen) {
@@ -189,23 +193,26 @@ public class Resolve {
}
/** Is the given field visible when inherited into class origin? */
- private static boolean visible(ClassSymbol origin, FieldInfo info) {
+ private static boolean visible(@Nullable ClassSymbol origin, FieldInfo info) {
return visible(origin, info.sym().owner(), info.access());
}
/** Is the given type visible when inherited into class origin? */
- private static boolean visible(ClassSymbol origin, ClassSymbol sym, HeaderBoundClass info) {
+ private static boolean visible(
+ @Nullable ClassSymbol origin, ClassSymbol sym, HeaderBoundClass info) {
return visible(origin, sym, info.access());
}
- private static boolean visible(ClassSymbol origin, ClassSymbol owner, int access) {
+ private static boolean visible(@Nullable ClassSymbol origin, ClassSymbol owner, int access) {
TurbineVisibility visibility = TurbineVisibility.fromAccess(access);
switch (visibility) {
case PUBLIC:
case PROTECTED:
return true;
case PACKAGE:
- return Objects.equals(owner.packageName(), origin.packageName());
+ // origin can be null if we aren't in a package scope (e.g. we're processing a module
+ // declaration), in which case package-visible members aren't visible
+ return origin != null && Objects.equals(owner.packageName(), origin.packageName());
case PRIVATE:
// Private members of lexically enclosing declarations are not handled,
// since this visibility check is only used for inherited members.
@@ -213,4 +220,6 @@ public class Resolve {
}
throw new AssertionError(visibility);
}
+
+ private Resolve() {}
}
diff --git a/java/com/google/turbine/binder/TypeBinder.java b/java/com/google/turbine/binder/TypeBinder.java
index 7b01856..92d2827 100644
--- a/java/com/google/turbine/binder/TypeBinder.java
+++ b/java/com/google/turbine/binder/TypeBinder.java
@@ -16,6 +16,9 @@
package com.google.turbine.binder;
+import static com.google.common.collect.Iterables.getLast;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -25,6 +28,7 @@ import com.google.turbine.binder.bound.SourceTypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo;
import com.google.turbine.binder.bound.TypeBoundClass.ParamInfo;
+import com.google.turbine.binder.bound.TypeBoundClass.RecordComponentInfo;
import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo;
import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.lookup.CompoundScope;
@@ -35,6 +39,7 @@ import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.binder.sym.MethodSymbol;
import com.google.turbine.binder.sym.ParamSymbol;
+import com.google.turbine.binder.sym.RecordComponentSymbol;
import com.google.turbine.binder.sym.Symbol;
import com.google.turbine.binder.sym.TyVarSymbol;
import com.google.turbine.diag.TurbineError.ErrorKind;
@@ -54,12 +59,14 @@ import com.google.turbine.tree.TurbineModifier;
import com.google.turbine.type.AnnoInfo;
import com.google.turbine.type.Type;
import com.google.turbine.type.Type.IntersectionTy;
+import com.google.turbine.types.Deannotate;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.jspecify.nullness.Nullable;
/** Type binding. */
public class TypeBinder {
@@ -76,7 +83,7 @@ public class TypeBinder {
}
@Override
- public LookupResult lookup(LookupKey lookup) {
+ public @Nullable LookupResult lookup(LookupKey lookup) {
if (name.equals(lookup.first().value())) {
return new LookupResult(sym, lookup);
}
@@ -93,7 +100,7 @@ public class TypeBinder {
}
@Override
- public LookupResult lookup(LookupKey lookupKey) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey) {
Symbol sym = tps.get(lookupKey.first().value());
return sym != null ? new LookupResult(sym, lookupKey) : null;
}
@@ -113,14 +120,14 @@ public class TypeBinder {
}
@Override
- public LookupResult lookup(LookupKey lookup) {
+ public @Nullable LookupResult lookup(LookupKey lookup) {
ClassSymbol curr = sym;
while (curr != null) {
- HeaderBoundClass info = env.get(curr);
Symbol result = Resolve.resolve(env, sym, curr, lookup.first());
if (result != null) {
return new LookupResult(result, lookup);
}
+ HeaderBoundClass info = env.getNonNull(curr);
result = info.typeParameters().get(lookup.first().value());
if (result != null) {
return new LookupResult(result, lookup);
@@ -165,8 +172,10 @@ public class TypeBinder {
CompoundScope enclosingScope =
base.scope()
.toScope(Resolve.resolveFunction(env, owner))
- .append(new SingletonScope(base.decl().name().value(), owner))
- .append(new ClassMemberScope(base.owner(), env));
+ .append(new SingletonScope(base.decl().name().value(), owner));
+ if (base.owner() != null) {
+ enclosingScope = enclosingScope.append(new ClassMemberScope(base.owner(), env));
+ }
ImmutableList<AnnoInfo> annotations = bindAnnotations(enclosingScope, base.decl().annos());
@@ -209,6 +218,9 @@ public class TypeBinder {
}
superClassType = Type.ClassTy.OBJECT;
break;
+ case RECORD:
+ superClassType = Type.ClassTy.asNonParametricClassTy(ClassSymbol.RECORD);
+ break;
default:
throw new AssertionError(base.decl().tykind());
}
@@ -217,26 +229,43 @@ public class TypeBinder {
interfaceTypes.add(bindClassTy(bindingScope, i));
}
+ ImmutableList.Builder<ClassSymbol> permits = ImmutableList.builder();
+ for (Tree.ClassTy i : base.decl().permits()) {
+ Type type = bindClassTy(bindingScope, i);
+ if (!type.tyKind().equals(Type.TyKind.CLASS_TY)) {
+ throw new AssertionError(type.tyKind());
+ }
+ permits.add(((Type.ClassTy) type).sym());
+ }
+
CompoundScope scope =
base.scope()
.toScope(Resolve.resolveFunction(env, owner))
.append(new SingletonScope(base.decl().name().value(), owner))
.append(new ClassMemberScope(owner, env));
- List<MethodInfo> methods =
+ SyntheticMethods syntheticMethods = new SyntheticMethods();
+
+ ImmutableList<RecordComponentInfo> components = bindComponents(scope, base.decl().components());
+
+ ImmutableList.Builder<MethodInfo> methods =
ImmutableList.<MethodInfo>builder()
- .addAll(syntheticMethods())
- .addAll(bindMethods(scope, base.decl().members()))
- .build();
+ .addAll(syntheticMethods(syntheticMethods, components))
+ .addAll(bindMethods(scope, base.decl().members(), components));
+ if (base.kind().equals(TurbineTyKind.RECORD)) {
+ methods.addAll(syntheticRecordMethods(syntheticMethods, components));
+ }
ImmutableList<FieldInfo> fields = bindFields(scope, base.decl().members());
return new SourceTypeBoundClass(
interfaceTypes.build(),
+ permits.build(),
superClassType,
typeParameterTypes,
base.access(),
- ImmutableList.copyOf(methods),
+ components,
+ methods.build(),
fields,
base.owner(),
base.kind(),
@@ -251,23 +280,79 @@ public class TypeBinder {
base.decl());
}
+ /**
+ * A generated for synthetic {@link MethodSymbol}s.
+ *
+ * <p>Each {@link MethodSymbol} contains an index into its enclosing class, to enable comparing
+ * the symbols for equality. For synthetic methods we use an arbitrary unique negative index.
+ */
+ private static class SyntheticMethods {
+
+ private int idx = -1;
+
+ MethodSymbol create(ClassSymbol owner, String name) {
+ return new MethodSymbol(idx--, owner, name);
+ }
+ }
+
+ private ImmutableList<RecordComponentInfo> bindComponents(
+ CompoundScope scope, ImmutableList<Tree.VarDecl> components) {
+ ImmutableList.Builder<RecordComponentInfo> result = ImmutableList.builder();
+ for (Tree.VarDecl p : components) {
+ int access = 0;
+ for (TurbineModifier m : p.mods()) {
+ access |= m.flag();
+ }
+ RecordComponentInfo param =
+ new RecordComponentInfo(
+ new RecordComponentSymbol(owner, p.name().value()),
+ bindTy(scope, p.ty()),
+ bindAnnotations(scope, p.annos()),
+ access);
+ result.add(param);
+ }
+ return result.build();
+ }
+
/** Collect synthetic and implicit methods, including default constructors and enum methods. */
- ImmutableList<MethodInfo> syntheticMethods() {
+ ImmutableList<MethodInfo> syntheticMethods(
+ SyntheticMethods syntheticMethods, ImmutableList<RecordComponentInfo> components) {
switch (base.kind()) {
case CLASS:
- return maybeDefaultConstructor();
+ return maybeDefaultConstructor(syntheticMethods);
+ case RECORD:
+ return maybeDefaultRecordConstructor(syntheticMethods, components);
case ENUM:
- return syntheticEnumMethods();
+ return syntheticEnumMethods(syntheticMethods);
default:
return ImmutableList.of();
}
}
- private ImmutableList<MethodInfo> maybeDefaultConstructor() {
+ private ImmutableList<MethodInfo> maybeDefaultRecordConstructor(
+ SyntheticMethods syntheticMethods, ImmutableList<RecordComponentInfo> components) {
+ if (hasConstructor()) {
+ return ImmutableList.of();
+ }
+ MethodSymbol symbol = syntheticMethods.create(owner, "<init>");
+ ImmutableList.Builder<ParamInfo> params = ImmutableList.builder();
+ for (RecordComponentInfo component : components) {
+ params.add(
+ new ParamInfo(
+ new ParamSymbol(symbol, component.name()),
+ component.type(),
+ component.annotations(),
+ component.access()));
+ }
+ return ImmutableList.of(
+ syntheticConstructor(symbol, params.build(), TurbineVisibility.fromAccess(base.access())));
+ }
+
+ private ImmutableList<MethodInfo> maybeDefaultConstructor(SyntheticMethods syntheticMethods) {
if (hasConstructor()) {
return ImmutableList.of();
}
- MethodSymbol symbol = new MethodSymbol(-1, owner, "<init>");
+ MethodSymbol symbol = syntheticMethods.create(owner, "<init>");
ImmutableList<ParamInfo> formals;
if (hasEnclosingInstance(base)) {
formals = ImmutableList.of(enclosingInstanceParameter(symbol));
@@ -282,6 +367,10 @@ public class TypeBinder {
MethodSymbol symbol, ImmutableList<ParamInfo> formals, TurbineVisibility visibility) {
int access = visibility.flag();
access |= (base.access() & TurbineFlag.ACC_STRICT);
+ if (!formals.isEmpty()
+ && (getLast(formals).access() & TurbineFlag.ACC_VARARGS) == TurbineFlag.ACC_VARARGS) {
+ access |= TurbineFlag.ACC_VARARGS;
+ }
return new MethodInfo(
symbol,
ImmutableMap.of(),
@@ -304,7 +393,7 @@ public class TypeBinder {
}
int enclosingInstances = 0;
for (ClassSymbol sym = base.owner(); sym != null; ) {
- HeaderBoundClass info = env.get(sym);
+ HeaderBoundClass info = env.getNonNull(sym);
if (((info.access() & TurbineFlag.ACC_STATIC) == TurbineFlag.ACC_STATIC)
|| info.owner() == null) {
break;
@@ -335,15 +424,15 @@ public class TypeBinder {
TurbineFlag.ACC_SYNTHETIC));
}
- private ImmutableList<MethodInfo> syntheticEnumMethods() {
+ private ImmutableList<MethodInfo> syntheticEnumMethods(SyntheticMethods syntheticMethods) {
ImmutableList.Builder<MethodInfo> methods = ImmutableList.builder();
int access = 0;
access |= (base.access() & TurbineFlag.ACC_STRICT);
if (!hasConstructor()) {
- MethodSymbol symbol = new MethodSymbol(-1, owner, "<init>");
+ MethodSymbol symbol = syntheticMethods.create(owner, "<init>");
methods.add(syntheticConstructor(symbol, enumCtorParams(symbol), TurbineVisibility.PRIVATE));
}
- MethodSymbol valuesMethod = new MethodSymbol(-2, owner, "values");
+ MethodSymbol valuesMethod = syntheticMethods.create(owner, "values");
methods.add(
new MethodInfo(
valuesMethod,
@@ -356,7 +445,7 @@ public class TypeBinder {
null,
ImmutableList.of(),
null));
- MethodSymbol valueOfMethod = new MethodSymbol(-3, owner, "valueOf");
+ MethodSymbol valueOfMethod = syntheticMethods.create(owner, "valueOf");
methods.add(
new MethodInfo(
valueOfMethod,
@@ -377,6 +466,71 @@ public class TypeBinder {
return methods.build();
}
+ private ImmutableList<MethodInfo> syntheticRecordMethods(
+ SyntheticMethods syntheticMethods, ImmutableList<RecordComponentInfo> components) {
+ ImmutableList.Builder<MethodInfo> methods = ImmutableList.builder();
+ MethodSymbol toStringMethod = syntheticMethods.create(owner, "toString");
+ methods.add(
+ new MethodInfo(
+ toStringMethod,
+ ImmutableMap.of(),
+ Type.ClassTy.STRING,
+ ImmutableList.of(),
+ ImmutableList.of(),
+ TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_FINAL,
+ null,
+ null,
+ ImmutableList.of(),
+ null));
+ MethodSymbol hashCodeMethod = syntheticMethods.create(owner, "hashCode");
+ methods.add(
+ new MethodInfo(
+ hashCodeMethod,
+ ImmutableMap.of(),
+ Type.PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()),
+ ImmutableList.of(),
+ ImmutableList.of(),
+ TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_FINAL,
+ null,
+ null,
+ ImmutableList.of(),
+ null));
+ MethodSymbol equalsMethod = syntheticMethods.create(owner, "equals");
+ methods.add(
+ new MethodInfo(
+ equalsMethod,
+ ImmutableMap.of(),
+ Type.PrimTy.create(TurbineConstantTypeKind.BOOLEAN, ImmutableList.of()),
+ ImmutableList.of(
+ new ParamInfo(
+ new ParamSymbol(equalsMethod, "other"),
+ Type.ClassTy.OBJECT,
+ ImmutableList.of(),
+ TurbineFlag.ACC_MANDATED)),
+ ImmutableList.of(),
+ TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_FINAL,
+ null,
+ null,
+ ImmutableList.of(),
+ null));
+ for (RecordComponentInfo c : components) {
+ MethodSymbol componentMethod = syntheticMethods.create(owner, c.name());
+ methods.add(
+ new MethodInfo(
+ componentMethod,
+ ImmutableMap.of(),
+ c.type(),
+ ImmutableList.of(),
+ ImmutableList.of(),
+ TurbineFlag.ACC_PUBLIC,
+ null,
+ null,
+ c.annotations(),
+ null));
+ }
+ return methods.build();
+ }
+
private boolean hasConstructor() {
for (Tree m : base.decl().members()) {
if (m.kind() != Kind.METH_DECL) {
@@ -394,7 +548,8 @@ public class TypeBinder {
ImmutableList<Tree.TyParam> trees, CompoundScope scope, Map<String, TyVarSymbol> symbols) {
ImmutableMap.Builder<TyVarSymbol, TyVarInfo> result = ImmutableMap.builder();
for (Tree.TyParam tree : trees) {
- TyVarSymbol sym = symbols.get(tree.name().value());
+ // `symbols` is constructed to guarantee the requireNonNull call is safe.
+ TyVarSymbol sym = requireNonNull(symbols.get(tree.name().value()));
ImmutableList.Builder<Type> bounds = ImmutableList.builder();
for (Tree bound : tree.bounds()) {
bounds.add(bindTy(scope, bound));
@@ -405,21 +560,25 @@ public class TypeBinder {
new TyVarInfo(
IntersectionTy.create(bounds.build()), /* lowerBound= */ null, annotations));
}
- return result.build();
+ return result.buildOrThrow();
}
- private List<MethodInfo> bindMethods(CompoundScope scope, ImmutableList<Tree> members) {
+ private List<MethodInfo> bindMethods(
+ CompoundScope scope,
+ ImmutableList<Tree> members,
+ ImmutableList<RecordComponentInfo> components) {
List<MethodInfo> methods = new ArrayList<>();
int idx = 0;
for (Tree member : members) {
if (member.kind() == Tree.Kind.METH_DECL) {
- methods.add(bindMethod(idx++, scope, (Tree.MethDecl) member));
+ methods.add(bindMethod(idx++, scope, (MethDecl) member, components));
}
}
return methods;
}
- private MethodInfo bindMethod(int idx, CompoundScope scope, Tree.MethDecl t) {
+ private MethodInfo bindMethod(
+ int idx, CompoundScope scope, MethDecl t, ImmutableList<RecordComponentInfo> components) {
MethodSymbol sym = new MethodSymbol(idx, owner, t.name().value());
@@ -429,7 +588,7 @@ public class TypeBinder {
for (Tree.TyParam pt : t.typarams()) {
builder.put(pt.name().value(), new TyVarSymbol(sym, pt.name().value()));
}
- typeParameters = builder.build();
+ typeParameters = builder.buildOrThrow();
}
// type parameters can refer to each other in f-bounds, so update the scope first
@@ -449,8 +608,26 @@ public class TypeBinder {
if (name.equals("<init>")) {
if (hasEnclosingInstance(base)) {
parameters.add(enclosingInstanceParameter(sym));
- } else if (base.kind() == TurbineTyKind.ENUM && name.equals("<init>")) {
- parameters.addAll(enumCtorParams(sym));
+ } else {
+ switch (base.kind()) {
+ case ENUM:
+ parameters.addAll(enumCtorParams(sym));
+ break;
+ case RECORD:
+ if (t.mods().contains(TurbineModifier.COMPACT_CTOR)) {
+ for (RecordComponentInfo component : components) {
+ parameters.add(
+ new ParamInfo(
+ new ParamSymbol(sym, component.name()),
+ component.type(),
+ component.annotations(),
+ component.access()));
+ }
+ }
+ break;
+ default:
+ break;
+ }
}
}
ParamInfo receiver = null;
@@ -493,6 +670,9 @@ public class TypeBinder {
== 0) {
access |= TurbineFlag.ACC_ABSTRACT;
}
+ if ((access & TurbineFlag.ACC_FINAL) == TurbineFlag.ACC_FINAL) {
+ log.error(t.position(), ErrorKind.UNEXPECTED_MODIFIER, TurbineModifier.FINAL);
+ }
break;
case ENUM:
if (name.equals("<init>")) {
@@ -575,8 +755,8 @@ public class TypeBinder {
return result.build();
}
- private ClassSymbol resolveAnnoSymbol(
- Anno tree, ImmutableList<Ident> name, LookupResult lookupResult) {
+ private @Nullable ClassSymbol resolveAnnoSymbol(
+ Anno tree, ImmutableList<Ident> name, @Nullable LookupResult lookupResult) {
if (lookupResult == null) {
log.error(tree.position(), ErrorKind.CANNOT_RESOLVE, Joiner.on('.').join(name));
return null;
@@ -588,13 +768,13 @@ public class TypeBinder {
return null;
}
}
- if (env.get(sym).kind() != TurbineTyKind.ANNOTATION) {
+ if (env.getNonNull(sym).kind() != TurbineTyKind.ANNOTATION) {
log.error(tree.position(), ErrorKind.NOT_AN_ANNOTATION, sym);
}
return sym;
}
- private ClassSymbol resolveNext(ClassSymbol sym, Ident bit) {
+ private @Nullable ClassSymbol resolveNext(ClassSymbol sym, Ident bit) {
ClassSymbol next = Resolve.resolve(env, owner, sym, bit);
if (next == null) {
log.error(
@@ -618,7 +798,14 @@ public class TypeBinder {
case WILD_TY:
return bindWildTy(scope, (Tree.WildTy) ty);
default:
- return bindTy(scope, ty);
+ Type result = bindTy(scope, ty);
+ if (result.tyKind().equals(Type.TyKind.PRIM_TY)) {
+ // Omit type annotations when printing the type in the diagnostic, since they're
+ // irrelevant and could be invalid if there were deferred errors.
+ // TODO(cushon): consider ensuring this is done for all diagnostics that mention types
+ log.error(ty.position(), ErrorKind.UNEXPECTED_TYPE, Deannotate.deannotate(result));
+ }
+ return result;
}
}
@@ -691,10 +878,11 @@ public class TypeBinder {
sym, bindTyArgs(scope, flat.get(idx++).tyargs()), annotations));
for (; idx < flat.size(); idx++) {
Tree.ClassTy curr = flat.get(idx);
- sym = resolveNext(sym, curr.name());
- if (sym == null) {
+ ClassSymbol next = resolveNext(sym, curr.name());
+ if (next == null) {
return Type.ErrorTy.create(bits);
}
+ sym = next;
annotations = bindAnnotations(scope, curr.annos());
classes.add(
diff --git a/java/com/google/turbine/binder/bound/AnnotationMetadata.java b/java/com/google/turbine/binder/bound/AnnotationMetadata.java
index 31860b6..5ae04b0 100644
--- a/java/com/google/turbine/binder/bound/AnnotationMetadata.java
+++ b/java/com/google/turbine/binder/bound/AnnotationMetadata.java
@@ -23,9 +23,10 @@ import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.model.TurbineElementType;
import java.lang.annotation.RetentionPolicy;
import java.util.EnumSet;
+import org.jspecify.nullness.Nullable;
/**
- * Annotation metadata, e.g. from {@link @java.lang.annotation.Target}, {@link
+ * Annotation metadata, e.g. from {@link java.lang.annotation.Target}, {@link
* java.lang.annotation.Retention}, and {@link java.lang.annotation.Repeatable}.
*/
public class AnnotationMetadata {
@@ -41,12 +42,12 @@ public class AnnotationMetadata {
private final RetentionPolicy retention;
private final ImmutableSet<TurbineElementType> target;
- private final ClassSymbol repeatable;
+ private final @Nullable ClassSymbol repeatable;
public AnnotationMetadata(
- RetentionPolicy retention,
- ImmutableSet<TurbineElementType> annotationTarget,
- ClassSymbol repeatable) {
+ @Nullable RetentionPolicy retention,
+ @Nullable ImmutableSet<TurbineElementType> annotationTarget,
+ @Nullable ClassSymbol repeatable) {
this.retention = firstNonNull(retention, RetentionPolicy.CLASS);
this.target = firstNonNull(annotationTarget, DEFAULT_TARGETS);
this.repeatable = repeatable;
@@ -63,7 +64,7 @@ public class AnnotationMetadata {
}
/** The container annotation for {@code @Repeated} annotations. */
- public ClassSymbol repeatable() {
+ public @Nullable ClassSymbol repeatable() {
return repeatable;
}
}
diff --git a/java/com/google/turbine/binder/bound/BoundClass.java b/java/com/google/turbine/binder/bound/BoundClass.java
index 61dee0f..1e29b42 100644
--- a/java/com/google/turbine/binder/bound/BoundClass.java
+++ b/java/com/google/turbine/binder/bound/BoundClass.java
@@ -19,7 +19,7 @@ package com.google.turbine.binder.bound;
import com.google.common.collect.ImmutableMap;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.model.TurbineTyKind;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/**
* The initial bound tree representation.
diff --git a/java/com/google/turbine/binder/bound/EnumConstantValue.java b/java/com/google/turbine/binder/bound/EnumConstantValue.java
index e99c6ed..20a5756 100644
--- a/java/com/google/turbine/binder/bound/EnumConstantValue.java
+++ b/java/com/google/turbine/binder/bound/EnumConstantValue.java
@@ -18,6 +18,7 @@ package com.google.turbine.binder.bound;
import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.model.Const;
+import org.jspecify.nullness.Nullable;
/** An enum constant. */
public class EnumConstantValue extends Const {
@@ -43,7 +44,7 @@ public class EnumConstantValue extends Const {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof EnumConstantValue && sym().equals(((EnumConstantValue) obj).sym());
}
diff --git a/java/com/google/turbine/binder/bound/HeaderBoundClass.java b/java/com/google/turbine/binder/bound/HeaderBoundClass.java
index 14807bb..9658016 100644
--- a/java/com/google/turbine/binder/bound/HeaderBoundClass.java
+++ b/java/com/google/turbine/binder/bound/HeaderBoundClass.java
@@ -20,15 +20,17 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.TyVarSymbol;
+import org.jspecify.nullness.Nullable;
/** A bound node that augments {@link BoundClass} with superclasses and interfaces. */
public interface HeaderBoundClass extends BoundClass {
/** The superclass of the declaration. */
+ @Nullable
ClassSymbol superclass();
/** The interfaces of the declaration. */
ImmutableList<ClassSymbol> interfaces();
/** Declared type parameters. */
- public ImmutableMap<String, TyVarSymbol> typeParameters();
+ ImmutableMap<String, TyVarSymbol> typeParameters();
}
diff --git a/java/com/google/turbine/binder/bound/ModuleInfo.java b/java/com/google/turbine/binder/bound/ModuleInfo.java
index f21213b..5dc8720 100644
--- a/java/com/google/turbine/binder/bound/ModuleInfo.java
+++ b/java/com/google/turbine/binder/bound/ModuleInfo.java
@@ -19,13 +19,13 @@ package com.google.turbine.binder.bound;
import com.google.common.collect.ImmutableList;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.type.AnnoInfo;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** A bound module declaration (see JLS §7.7). */
public class ModuleInfo {
private final String name;
- @Nullable private final String version;
+ private final @Nullable String version;
private final int flags;
private final ImmutableList<AnnoInfo> annos;
private final ImmutableList<RequireInfo> requires;
@@ -59,8 +59,7 @@ public class ModuleInfo {
return name;
}
- @Nullable
- public String version() {
+ public @Nullable String version() {
return version;
}
@@ -97,9 +96,9 @@ public class ModuleInfo {
private final String moduleName;
private final int flags;
- private final String version;
+ private final @Nullable String version;
- public RequireInfo(String moduleName, int flags, String version) {
+ public RequireInfo(String moduleName, int flags, @Nullable String version) {
this.moduleName = moduleName;
this.flags = flags;
this.version = version;
@@ -113,7 +112,7 @@ public class ModuleInfo {
return flags;
}
- public String version() {
+ public @Nullable String version() {
return version;
}
}
diff --git a/java/com/google/turbine/binder/bound/PackageSourceBoundClass.java b/java/com/google/turbine/binder/bound/PackageSourceBoundClass.java
index 2dd2e4e..77832f9 100644
--- a/java/com/google/turbine/binder/bound/PackageSourceBoundClass.java
+++ b/java/com/google/turbine/binder/bound/PackageSourceBoundClass.java
@@ -23,6 +23,7 @@ import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.diag.SourceFile;
import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.tree.Tree;
+import org.jspecify.nullness.Nullable;
/** A {@link BoundClass} with shared lookup scopes for the current compilation unit and package. */
public class PackageSourceBoundClass implements BoundClass {
@@ -52,7 +53,7 @@ public class PackageSourceBoundClass implements BoundClass {
}
@Override
- public ClassSymbol owner() {
+ public @Nullable ClassSymbol owner() {
return base.owner();
}
diff --git a/java/com/google/turbine/binder/bound/SourceBoundClass.java b/java/com/google/turbine/binder/bound/SourceBoundClass.java
index 9e27ff3..7a6f33f 100644
--- a/java/com/google/turbine/binder/bound/SourceBoundClass.java
+++ b/java/com/google/turbine/binder/bound/SourceBoundClass.java
@@ -20,18 +20,19 @@ import com.google.common.collect.ImmutableMap;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.tree.Tree;
+import org.jspecify.nullness.Nullable;
/** A {@link BoundClass} that corresponds to a source file being compiled. */
public class SourceBoundClass implements BoundClass {
private final ClassSymbol sym;
- private final ClassSymbol owner;
+ private final @Nullable ClassSymbol owner;
private final ImmutableMap<String, ClassSymbol> children;
private final int access;
private final Tree.TyDecl decl;
public SourceBoundClass(
ClassSymbol sym,
- ClassSymbol owner,
+ @Nullable ClassSymbol owner,
ImmutableMap<String, ClassSymbol> children,
int access,
Tree.TyDecl decl) {
@@ -52,7 +53,7 @@ public class SourceBoundClass implements BoundClass {
}
@Override
- public ClassSymbol owner() {
+ public @Nullable ClassSymbol owner() {
return owner;
}
diff --git a/java/com/google/turbine/binder/bound/SourceHeaderBoundClass.java b/java/com/google/turbine/binder/bound/SourceHeaderBoundClass.java
index c15d0dd..210ff0b 100644
--- a/java/com/google/turbine/binder/bound/SourceHeaderBoundClass.java
+++ b/java/com/google/turbine/binder/bound/SourceHeaderBoundClass.java
@@ -25,18 +25,19 @@ import com.google.turbine.binder.sym.TyVarSymbol;
import com.google.turbine.diag.SourceFile;
import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.tree.Tree;
+import org.jspecify.nullness.Nullable;
/** A {@link HeaderBoundClass} that corresponds to a source file being compiled. */
public class SourceHeaderBoundClass implements HeaderBoundClass {
private final PackageSourceBoundClass base;
- private final ClassSymbol superclass;
+ private final @Nullable ClassSymbol superclass;
private final ImmutableList<ClassSymbol> interfaces;
private final ImmutableMap<String, TyVarSymbol> typeParameters;
public SourceHeaderBoundClass(
PackageSourceBoundClass base,
- ClassSymbol superclass,
+ @Nullable ClassSymbol superclass,
ImmutableList<ClassSymbol> interfaces,
ImmutableMap<String, TyVarSymbol> typeParameters) {
this.base = base;
@@ -46,7 +47,7 @@ public class SourceHeaderBoundClass implements HeaderBoundClass {
}
@Override
- public ClassSymbol superclass() {
+ public @Nullable ClassSymbol superclass() {
return superclass;
}
@@ -66,7 +67,7 @@ public class SourceHeaderBoundClass implements HeaderBoundClass {
}
@Override
- public ClassSymbol owner() {
+ public @Nullable ClassSymbol owner() {
return base.owner();
}
diff --git a/java/com/google/turbine/binder/bound/SourceModuleInfo.java b/java/com/google/turbine/binder/bound/SourceModuleInfo.java
index 1163e9f..66ba0e4 100644
--- a/java/com/google/turbine/binder/bound/SourceModuleInfo.java
+++ b/java/com/google/turbine/binder/bound/SourceModuleInfo.java
@@ -24,7 +24,7 @@ import com.google.turbine.binder.bound.ModuleInfo.RequireInfo;
import com.google.turbine.binder.bound.ModuleInfo.UseInfo;
import com.google.turbine.diag.SourceFile;
import com.google.turbine.type.AnnoInfo;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** A {@link ModuleInfo} that corresponds to a source file being compiled. */
public class SourceModuleInfo extends ModuleInfo {
diff --git a/java/com/google/turbine/binder/bound/SourceTypeBoundClass.java b/java/com/google/turbine/binder/bound/SourceTypeBoundClass.java
index 69a2593..5e9817e 100644
--- a/java/com/google/turbine/binder/bound/SourceTypeBoundClass.java
+++ b/java/com/google/turbine/binder/bound/SourceTypeBoundClass.java
@@ -29,53 +29,59 @@ import com.google.turbine.type.AnnoInfo;
import com.google.turbine.type.Type;
import com.google.turbine.type.Type.ClassTy;
import com.google.turbine.type.Type.TyKind;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** A HeaderBoundClass for classes compiled from source. */
public class SourceTypeBoundClass implements TypeBoundClass {
private final TurbineTyKind kind;
- private final ClassSymbol owner;
+ private final @Nullable ClassSymbol owner;
private final ImmutableMap<String, ClassSymbol> children;
private final int access;
private final ImmutableMap<String, TyVarSymbol> typeParameters;
private final ImmutableMap<TyVarSymbol, TyVarInfo> typeParameterTypes;
- private final Type superClassType;
+ private final @Nullable Type superClassType;
private final ImmutableList<Type> interfaceTypes;
+ private final ImmutableList<ClassSymbol> permits;
+ private final ImmutableList<RecordComponentInfo> components;
private final ImmutableList<MethodInfo> methods;
private final ImmutableList<FieldInfo> fields;
private final CompoundScope enclosingScope;
private final CompoundScope scope;
private final MemberImportIndex memberImports;
- private final AnnotationMetadata annotationMetadata;
+ private final @Nullable AnnotationMetadata annotationMetadata;
private final ImmutableList<AnnoInfo> annotations;
private final Tree.TyDecl decl;
private final SourceFile source;
public SourceTypeBoundClass(
ImmutableList<Type> interfaceTypes,
- Type superClassType,
+ ImmutableList<ClassSymbol> permits,
+ @Nullable Type superClassType,
ImmutableMap<TyVarSymbol, TyVarInfo> typeParameterTypes,
int access,
+ ImmutableList<RecordComponentInfo> components,
ImmutableList<MethodInfo> methods,
ImmutableList<FieldInfo> fields,
- ClassSymbol owner,
+ @Nullable ClassSymbol owner,
TurbineTyKind kind,
ImmutableMap<String, ClassSymbol> children,
ImmutableMap<String, TyVarSymbol> typeParameters,
CompoundScope enclosingScope,
CompoundScope scope,
MemberImportIndex memberImports,
- AnnotationMetadata annotationMetadata,
+ @Nullable AnnotationMetadata annotationMetadata,
ImmutableList<AnnoInfo> annotations,
SourceFile source,
Tree.TyDecl decl) {
this.interfaceTypes = interfaceTypes;
+ this.permits = permits;
this.superClassType = superClassType;
this.typeParameterTypes = typeParameterTypes;
this.access = access;
+ this.components = components;
this.methods = methods;
this.fields = fields;
this.owner = owner;
@@ -92,7 +98,7 @@ public class SourceTypeBoundClass implements TypeBoundClass {
}
@Override
- public ClassSymbol superclass() {
+ public @Nullable ClassSymbol superclass() {
if (superClassType == null) {
return null;
}
@@ -114,6 +120,11 @@ public class SourceTypeBoundClass implements TypeBoundClass {
}
@Override
+ public ImmutableList<ClassSymbol> permits() {
+ return permits;
+ }
+
+ @Override
public int access() {
return access;
}
@@ -123,9 +134,8 @@ public class SourceTypeBoundClass implements TypeBoundClass {
return kind;
}
- @Nullable
@Override
- public ClassSymbol owner() {
+ public @Nullable ClassSymbol owner() {
return owner;
}
@@ -146,10 +156,16 @@ public class SourceTypeBoundClass implements TypeBoundClass {
/** The super-class type. */
@Override
- public Type superClassType() {
+ public @Nullable Type superClassType() {
return superClassType;
}
+ /** The record components. */
+ @Override
+ public ImmutableList<RecordComponentInfo> components() {
+ return components;
+ }
+
/** Declared methods. */
@Override
public ImmutableList<MethodInfo> methods() {
@@ -157,7 +173,7 @@ public class SourceTypeBoundClass implements TypeBoundClass {
}
@Override
- public AnnotationMetadata annotationMetadata() {
+ public @Nullable AnnotationMetadata annotationMetadata() {
return annotationMetadata;
}
diff --git a/java/com/google/turbine/binder/bound/TurbineAnnotationValue.java b/java/com/google/turbine/binder/bound/TurbineAnnotationValue.java
index 808d603..b6737d6 100644
--- a/java/com/google/turbine/binder/bound/TurbineAnnotationValue.java
+++ b/java/com/google/turbine/binder/bound/TurbineAnnotationValue.java
@@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.model.Const;
import com.google.turbine.type.AnnoInfo;
+import org.jspecify.nullness.Nullable;
/** An annotation literal constant. */
public class TurbineAnnotationValue extends Const {
@@ -56,7 +57,7 @@ public class TurbineAnnotationValue extends Const {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineAnnotationValue
&& info().equals(((TurbineAnnotationValue) obj).info());
}
diff --git a/java/com/google/turbine/binder/bound/TurbineClassValue.java b/java/com/google/turbine/binder/bound/TurbineClassValue.java
index df55501..c6ba6ef 100644
--- a/java/com/google/turbine/binder/bound/TurbineClassValue.java
+++ b/java/com/google/turbine/binder/bound/TurbineClassValue.java
@@ -19,6 +19,7 @@ package com.google.turbine.binder.bound;
import com.google.turbine.model.Const;
import com.google.turbine.type.Type;
import java.util.Objects;
+import org.jspecify.nullness.Nullable;
/** A class literal constant. */
public class TurbineClassValue extends Const {
@@ -50,7 +51,7 @@ public class TurbineClassValue extends Const {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineClassValue && type().equals(((TurbineClassValue) obj).type());
}
}
diff --git a/java/com/google/turbine/binder/bound/TypeBoundClass.java b/java/com/google/turbine/binder/bound/TypeBoundClass.java
index e8933ac..8321bde 100644
--- a/java/com/google/turbine/binder/bound/TypeBoundClass.java
+++ b/java/com/google/turbine/binder/bound/TypeBoundClass.java
@@ -16,12 +16,13 @@
package com.google.turbine.binder.bound;
-
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.binder.sym.MethodSymbol;
import com.google.turbine.binder.sym.ParamSymbol;
+import com.google.turbine.binder.sym.RecordComponentSymbol;
import com.google.turbine.binder.sym.TyVarSymbol;
import com.google.turbine.model.Const;
import com.google.turbine.model.TurbineFlag;
@@ -31,17 +32,21 @@ import com.google.turbine.type.AnnoInfo;
import com.google.turbine.type.Type;
import com.google.turbine.type.Type.IntersectionTy;
import com.google.turbine.type.Type.MethodTy;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** A bound node that augments {@link HeaderBoundClass} with type information. */
public interface TypeBoundClass extends HeaderBoundClass {
/** The super-class type. */
+ @Nullable
Type superClassType();
/** Implemented interface types. */
ImmutableList<Type> interfaceTypes();
+ /** The permitted direct subclasses. */
+ ImmutableList<ClassSymbol> permits();
+
ImmutableMap<TyVarSymbol, TyVarInfo> typeParameterTypes();
/** Declared fields. */
@@ -50,10 +55,14 @@ public interface TypeBoundClass extends HeaderBoundClass {
/** Declared methods. */
ImmutableList<MethodInfo> methods();
+ /** Record components. */
+ ImmutableList<RecordComponentInfo> components();
+
/**
- * Annotation metadata, e.g. from {@link @java.lang.annotation.Target}, {@link
+ * Annotation metadata, e.g. from {@link java.lang.annotation.Target}, {@link
* java.lang.annotation.Retention}, and {@link java.lang.annotation.Repeatable}.
*/
+ @Nullable
AnnotationMetadata annotationMetadata();
/** Declaration annotations. */
@@ -62,7 +71,7 @@ public interface TypeBoundClass extends HeaderBoundClass {
/** A type parameter declaration. */
class TyVarInfo {
private final IntersectionTy upperBound;
- @Nullable private final Type lowerBound;
+ private final @Nullable Type lowerBound;
private final ImmutableList<AnnoInfo> annotations;
public TyVarInfo(
@@ -81,8 +90,7 @@ public interface TypeBoundClass extends HeaderBoundClass {
}
/** The lower bound. */
- @Nullable
- public Type lowerBound() {
+ public @Nullable Type lowerBound() {
return lowerBound;
}
@@ -99,16 +107,16 @@ public interface TypeBoundClass extends HeaderBoundClass {
private final int access;
private final ImmutableList<AnnoInfo> annotations;
- private final Tree.VarDecl decl;
- private final Const.Value value;
+ private final Tree.@Nullable VarDecl decl;
+ private final Const.@Nullable Value value;
public FieldInfo(
FieldSymbol sym,
Type type,
int access,
ImmutableList<AnnoInfo> annotations,
- Tree.VarDecl decl,
- Const.Value value) {
+ Tree.@Nullable VarDecl decl,
+ Const.@Nullable Value value) {
this.sym = sym;
this.type = type;
this.access = access;
@@ -138,12 +146,12 @@ public interface TypeBoundClass extends HeaderBoundClass {
}
/** The field's declaration. */
- public Tree.VarDecl decl() {
+ public Tree.@Nullable VarDecl decl() {
return decl;
}
/** The constant field value. */
- public Const.Value value() {
+ public Const.@Nullable Value value() {
return value;
}
@@ -161,8 +169,8 @@ public interface TypeBoundClass extends HeaderBoundClass {
private final ImmutableList<ParamInfo> parameters;
private final ImmutableList<Type> exceptions;
private final int access;
- private final Const defaultValue;
- private final MethDecl decl;
+ private final @Nullable Const defaultValue;
+ private final @Nullable MethDecl decl;
private final ImmutableList<AnnoInfo> annotations;
private final @Nullable ParamInfo receiver;
@@ -173,8 +181,8 @@ public interface TypeBoundClass extends HeaderBoundClass {
ImmutableList<ParamInfo> parameters,
ImmutableList<Type> exceptions,
int access,
- Const defaultValue,
- MethDecl decl,
+ @Nullable Const defaultValue,
+ @Nullable MethDecl decl,
ImmutableList<AnnoInfo> annotations,
@Nullable ParamInfo receiver) {
this.sym = sym;
@@ -225,12 +233,20 @@ public interface TypeBoundClass extends HeaderBoundClass {
}
/** The default value of an annotation interface method. */
- public Const defaultValue() {
+ public @Nullable Const defaultValue() {
return defaultValue;
}
+ /**
+ * Returns true for annotation members with a default value. The default value may not have been
+ * bound yet, in which case {@link #defaultValue} may still return {@code null}.
+ */
+ public boolean hasDefaultValue() {
+ return decl() != null ? decl().defaultValue().isPresent() : defaultValue() != null;
+ }
+
/** The declaration. */
- public MethDecl decl() {
+ public @Nullable MethDecl decl() {
return decl;
}
@@ -311,4 +327,45 @@ public interface TypeBoundClass extends HeaderBoundClass {
return access;
}
}
+
+ /** A record component. */
+ class RecordComponentInfo {
+ private final RecordComponentSymbol sym;
+ private final Type type;
+ private final int access;
+ private final ImmutableList<AnnoInfo> annotations;
+
+ public RecordComponentInfo(
+ RecordComponentSymbol sym, Type type, ImmutableList<AnnoInfo> annotations, int access) {
+ this.sym = sym;
+ this.type = type;
+ this.access = access;
+ this.annotations = annotations;
+ }
+
+ /** The record component's symbol. */
+ public RecordComponentSymbol sym() {
+ return sym;
+ }
+
+ /** The record component type. */
+ public Type type() {
+ return type;
+ }
+
+ /** Record component annotations. */
+ public ImmutableList<AnnoInfo> annotations() {
+ return annotations;
+ }
+
+ /** The Record component's name. */
+ public String name() {
+ return sym.name();
+ }
+
+ /** The Record component's modifiers. */
+ public int access() {
+ return access;
+ }
+ }
}
diff --git a/java/com/google/turbine/binder/bound/package-info.java b/java/com/google/turbine/binder/bound/package-info.java
new file mode 100644
index 0000000..8839101
--- /dev/null
+++ b/java/com/google/turbine/binder/bound/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+@org.jspecify.nullness.NullMarked
+package com.google.turbine.binder.bound;
diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBinder.java b/java/com/google/turbine/binder/bytecode/BytecodeBinder.java
index 66d4cf0..82f8a8c 100644
--- a/java/com/google/turbine/binder/bytecode/BytecodeBinder.java
+++ b/java/com/google/turbine/binder/bytecode/BytecodeBinder.java
@@ -16,6 +16,8 @@
package com.google.turbine.binder.bytecode;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.turbine.binder.bound.EnumConstantValue;
@@ -40,6 +42,7 @@ import com.google.turbine.bytecode.sig.Sig.WildTySig;
import com.google.turbine.bytecode.sig.SigParser;
import com.google.turbine.model.Const;
import com.google.turbine.model.Const.ArrayInitValue;
+import com.google.turbine.model.Const.Value;
import com.google.turbine.type.AnnoInfo;
import com.google.turbine.type.Type;
import java.util.ArrayList;
@@ -49,7 +52,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
/** Bind {@link Type}s from bytecode. */
-public class BytecodeBinder {
+public final class BytecodeBinder {
static Type.ClassTy bindClassTy(Sig.ClassTySig sig, Function<String, TyVarSymbol> scope) {
StringBuilder sb = new StringBuilder(sig.pkg());
@@ -134,7 +137,7 @@ public class BytecodeBinder {
for (Map.Entry<String, ElementValue> e : value.elementValuePairs().entrySet()) {
values.put(e.getKey(), bindValue(e.getValue()));
}
- return new TurbineAnnotationValue(new AnnoInfo(null, sym, null, values.build()));
+ return new TurbineAnnotationValue(new AnnoInfo(null, sym, null, values.buildOrThrow()));
}
static ImmutableList<AnnoInfo> bindAnnotations(List<AnnotationInfo> input) {
@@ -175,19 +178,23 @@ public class BytecodeBinder {
// TODO(b/32626659): this is not bug-compatible with javac
switch (((Type.PrimTy) type).primkind()) {
case CHAR:
- return new Const.CharValue(value.asChar().value());
+ return new Const.CharValue((char) asInt(value));
case SHORT:
- return new Const.ShortValue(value.asShort().value());
+ return new Const.ShortValue((short) asInt(value));
case BOOLEAN:
// boolean constants are encoded as integers
- return new Const.BooleanValue(value.asInteger().value() != 0);
+ return new Const.BooleanValue(asInt(value) != 0);
case BYTE:
- return new Const.ByteValue(value.asByte().value());
+ return new Const.ByteValue((byte) asInt(value));
default:
return value;
}
}
+ private static int asInt(Value value) {
+ return ((Const.IntValue) value).value();
+ }
+
private static Const bindEnumValue(EnumConstValue value) {
return new EnumConstantValue(
new FieldSymbol(asClassSymbol(value.typeName()), value.constName()));
@@ -201,6 +208,7 @@ public class BytecodeBinder {
public static ModuleInfo bindModuleInfo(String path, Supplier<byte[]> bytes) {
ClassFile classFile = ClassReader.read(path, bytes.get());
ClassFile.ModuleInfo module = classFile.module();
+ requireNonNull(module, path);
return new ModuleInfo(
module.name(),
module.version(),
@@ -212,4 +220,6 @@ public class BytecodeBinder {
/* uses= */ ImmutableList.of(),
/* provides= */ ImmutableList.of());
}
+
+ private BytecodeBinder() {}
}
diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
index b992643..cc97dcb 100644
--- a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
+++ b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
@@ -18,6 +18,7 @@ package com.google.turbine.binder.bytecode;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
@@ -25,8 +26,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.turbine.binder.bound.AnnotationMetadata;
-import com.google.turbine.binder.bound.BoundClass;
-import com.google.turbine.binder.bound.HeaderBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.sym.ClassSymbol;
@@ -59,7 +58,7 @@ import com.google.turbine.type.Type.IntersectionTy;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
import java.util.function.Function;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/**
* A bound class backed by a class file.
@@ -69,18 +68,18 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* resolved and canonicalized so there are no cycles. The laziness also minimizes the amount of work
* done on the classpath.
*/
-public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBoundClass {
+public class BytecodeBoundClass implements TypeBoundClass {
private final ClassSymbol sym;
private final Env<ClassSymbol, BytecodeBoundClass> env;
private final Supplier<ClassFile> classFile;
- private final String jarFile;
+ private final @Nullable String jarFile;
public BytecodeBoundClass(
ClassSymbol sym,
Supplier<byte[]> bytes,
Env<ClassSymbol, BytecodeBoundClass> env,
- String jarFile) {
+ @Nullable String jarFile) {
this.sym = sym;
this.env = env;
this.jarFile = jarFile;
@@ -124,11 +123,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
return kind.get();
}
- private final Supplier<ClassSymbol> owner =
+ private final Supplier<@Nullable ClassSymbol> owner =
Suppliers.memoize(
- new Supplier<ClassSymbol>() {
+ new Supplier<@Nullable ClassSymbol>() {
@Override
- public ClassSymbol get() {
+ public @Nullable ClassSymbol get() {
for (ClassFile.InnerClass inner : classFile.get().innerClasses()) {
if (sym.binaryName().equals(inner.innerClass())) {
return new ClassSymbol(inner.outerClass());
@@ -138,9 +137,8 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
}
});
- @Nullable
@Override
- public ClassSymbol owner() {
+ public @Nullable ClassSymbol owner() {
return owner.get();
}
@@ -159,7 +157,7 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
result.put(inner.innerName(), new ClassSymbol(inner.innerClass()));
}
}
- return result.build();
+ return result.buildOrThrow();
}
});
@@ -188,11 +186,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
return access.get();
}
- private final Supplier<ClassSig> sig =
+ private final Supplier<@Nullable ClassSig> sig =
Suppliers.memoize(
- new Supplier<ClassSig>() {
+ new Supplier<@Nullable ClassSig>() {
@Override
- public ClassSig get() {
+ public @Nullable ClassSig get() {
String signature = classFile.get().signature();
if (signature == null) {
return null;
@@ -214,7 +212,7 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
for (Sig.TyParamSig p : csig.tyParams()) {
result.put(p.name(), new TyVarSymbol(sym, p.name()));
}
- return result.build();
+ return result.buildOrThrow();
}
});
@@ -223,11 +221,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
return tyParams.get();
}
- private final Supplier<ClassSymbol> superclass =
+ private final Supplier<@Nullable ClassSymbol> superclass =
Suppliers.memoize(
- new Supplier<ClassSymbol>() {
+ new Supplier<@Nullable ClassSymbol>() {
@Override
- public ClassSymbol get() {
+ public @Nullable ClassSymbol get() {
String superclass = classFile.get().superName();
if (superclass == null) {
return null;
@@ -237,7 +235,7 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
});
@Override
- public ClassSymbol superclass() {
+ public @Nullable ClassSymbol superclass() {
return superclass.get();
}
@@ -259,11 +257,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
return interfaces.get();
}
- private final Supplier<ClassTy> superClassType =
+ private final Supplier<@Nullable ClassTy> superClassType =
Suppliers.memoize(
- new Supplier<ClassTy>() {
+ new Supplier<@Nullable ClassTy>() {
@Override
- public ClassTy get() {
+ public @Nullable ClassTy get() {
if (superclass() == null) {
return null;
}
@@ -276,7 +274,7 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
});
@Override
- public ClassTy superClassType() {
+ public @Nullable ClassTy superClassType() {
return superClassType.get();
}
@@ -308,6 +306,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
return interfaceTypes.get();
}
+ @Override
+ public ImmutableList<ClassSymbol> permits() {
+ return ImmutableList.of();
+ }
+
private final Supplier<ImmutableMap<TyVarSymbol, TyVarInfo>> typeParameterTypes =
Suppliers.memoize(
new Supplier<ImmutableMap<TyVarSymbol, TyVarInfo>>() {
@@ -319,9 +322,10 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
ImmutableMap.Builder<TyVarSymbol, TyVarInfo> tparams = ImmutableMap.builder();
Function<String, TyVarSymbol> scope = makeScope(env, sym, typeParameters());
for (Sig.TyParamSig p : sig.get().tyParams()) {
- tparams.put(typeParameters().get(p.name()), bindTyParam(p, scope));
+ // typeParameters() is constructed to guarantee the requireNonNull call is safe.
+ tparams.put(requireNonNull(typeParameters().get(p.name())), bindTyParam(p, scope));
}
- return tparams.build();
+ return tparams.buildOrThrow();
}
});
@@ -380,14 +384,19 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
public ImmutableList<MethodInfo> get() {
ImmutableList.Builder<MethodInfo> methods = ImmutableList.builder();
int idx = 0;
- for (ClassFile.MethodInfo m : classFile.get().methods()) {
- methods.add(bindMethod(idx++, m));
+ ClassFile cf = classFile.get();
+ for (ClassFile.MethodInfo m : cf.methods()) {
+ if (m.name().equals("<clinit>")) {
+ // Don't bother reading class initializers, which we don't need
+ continue;
+ }
+ methods.add(bindMethod(cf, idx++, m));
}
return methods.build();
}
});
- private MethodInfo bindMethod(int methodIdx, ClassFile.MethodInfo m) {
+ private MethodInfo bindMethod(ClassFile classFile, int methodIdx, ClassFile.MethodInfo m) {
MethodSymbol methodSymbol = new MethodSymbol(methodIdx, sym, m.name());
Sig.MethodSig sig = new SigParser(firstNonNull(m.signature(), m.descriptor())).parseMethodSig();
@@ -397,7 +406,7 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
for (Sig.TyParamSig p : sig.tyParams()) {
result.put(p.name(), new TyVarSymbol(methodSymbol, p.name()));
}
- tyParams = result.build();
+ tyParams = result.buildOrThrow();
}
ImmutableMap<TyVarSymbol, TyVarInfo> tyParamTypes;
@@ -405,17 +414,15 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
ImmutableMap.Builder<TyVarSymbol, TyVarInfo> tparams = ImmutableMap.builder();
Function<String, TyVarSymbol> scope = makeScope(env, sym, tyParams);
for (Sig.TyParamSig p : sig.tyParams()) {
- tparams.put(tyParams.get(p.name()), bindTyParam(p, scope));
+ // tyParams is constructed to guarantee the requireNonNull call is safe.
+ tparams.put(requireNonNull(tyParams.get(p.name())), bindTyParam(p, scope));
}
- tyParamTypes = tparams.build();
+ tyParamTypes = tparams.buildOrThrow();
}
Function<String, TyVarSymbol> scope = makeScope(env, sym, tyParams);
- Type ret = null;
- if (sig.returnType() != null) {
- ret = BytecodeBinder.bindTy(sig.returnType(), scope);
- }
+ Type ret = BytecodeBinder.bindTy(sig.returnType(), scope);
ImmutableList.Builder<ParamInfo> formals = ImmutableList.builder();
int idx = 0;
@@ -460,13 +467,19 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
ImmutableList<AnnoInfo> annotations = BytecodeBinder.bindAnnotations(m.annotations());
+ int access = m.access();
+ if (((classFile.access() & TurbineFlag.ACC_INTERFACE) == TurbineFlag.ACC_INTERFACE)
+ && (access & (TurbineFlag.ACC_ABSTRACT | TurbineFlag.ACC_STATIC)) == 0) {
+ access |= TurbineFlag.ACC_DEFAULT;
+ }
+
return new MethodInfo(
methodSymbol,
tyParamTypes,
ret,
formals.build(),
exceptions.build(),
- m.access(),
+ access,
defaultValue,
/* decl= */ null,
annotations,
@@ -478,11 +491,16 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
return methods.get();
}
- private final Supplier<AnnotationMetadata> annotationMetadata =
+ @Override
+ public ImmutableList<RecordComponentInfo> components() {
+ return ImmutableList.of();
+ }
+
+ private final Supplier<@Nullable AnnotationMetadata> annotationMetadata =
Suppliers.memoize(
- new Supplier<AnnotationMetadata>() {
+ new Supplier<@Nullable AnnotationMetadata>() {
@Override
- public AnnotationMetadata get() {
+ public @Nullable AnnotationMetadata get() {
if ((access() & TurbineFlag.ACC_ANNOTATION) != TurbineFlag.ACC_ANNOTATION) {
return null;
}
@@ -508,8 +526,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
}
});
- private static RetentionPolicy bindRetention(AnnotationInfo annotation) {
+ private static @Nullable RetentionPolicy bindRetention(AnnotationInfo annotation) {
ElementValue val = annotation.elementValuePairs().get("value");
+ if (val == null) {
+ return null;
+ }
if (val.kind() != Kind.ENUM) {
return null;
}
@@ -523,6 +544,7 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
private static ImmutableSet<TurbineElementType> bindTarget(AnnotationInfo annotation) {
ImmutableSet.Builder<TurbineElementType> result = ImmutableSet.builder();
ElementValue val = annotation.elementValuePairs().get("value");
+ requireNonNull(val);
switch (val.kind()) {
case ARRAY:
for (ElementValue element : ((ArrayValue) val).elements()) {
@@ -547,8 +569,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
}
}
- private static ClassSymbol bindRepeatable(AnnotationInfo annotation) {
+ private static @Nullable ClassSymbol bindRepeatable(AnnotationInfo annotation) {
ElementValue val = annotation.elementValuePairs().get("value");
+ if (val == null) {
+ return null;
+ }
switch (val.kind()) {
case CLASS:
String className = ((ConstTurbineClassValue) val).className();
@@ -560,7 +585,7 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
}
@Override
- public AnnotationMetadata annotationMetadata() {
+ public @Nullable AnnotationMetadata annotationMetadata() {
return annotationMetadata.get();
}
@@ -611,7 +636,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou
}
/** The jar file the symbol was loaded from. */
- public String jarFile() {
+ public @Nullable String jarFile() {
+ String transitiveJar = classFile.get().transitiveJar();
+ if (transitiveJar != null) {
+ return transitiveJar;
+ }
return jarFile;
}
diff --git a/java/com/google/turbine/binder/bytecode/package-info.java b/java/com/google/turbine/binder/bytecode/package-info.java
new file mode 100644
index 0000000..d2d9708
--- /dev/null
+++ b/java/com/google/turbine/binder/bytecode/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@org.jspecify.nullness.NullMarked
+package com.google.turbine.binder.bytecode;
diff --git a/java/com/google/turbine/binder/env/CompoundEnv.java b/java/com/google/turbine/binder/env/CompoundEnv.java
index 9b216e3..391a2c3 100644
--- a/java/com/google/turbine/binder/env/CompoundEnv.java
+++ b/java/com/google/turbine/binder/env/CompoundEnv.java
@@ -19,12 +19,12 @@ package com.google.turbine.binder.env;
import static java.util.Objects.requireNonNull;
import com.google.turbine.binder.sym.Symbol;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** An {@link Env} that chains two existing envs together. */
public class CompoundEnv<S extends Symbol, V> implements Env<S, V> {
- private final Env<S, ? extends V> base;
+ private final @Nullable Env<S, ? extends V> base;
private final Env<S, ? extends V> env;
private CompoundEnv(@Nullable Env<S, ? extends V> base, Env<S, ? extends V> env) {
@@ -33,7 +33,7 @@ public class CompoundEnv<S extends Symbol, V> implements Env<S, V> {
}
@Override
- public V get(S sym) {
+ public @Nullable V get(S sym) {
V result = env.get(sym);
if (result != null) {
return result;
diff --git a/java/com/google/turbine/binder/env/Env.java b/java/com/google/turbine/binder/env/Env.java
index 6ee38a4..463c65d 100644
--- a/java/com/google/turbine/binder/env/Env.java
+++ b/java/com/google/turbine/binder/env/Env.java
@@ -18,12 +18,13 @@ package com.google.turbine.binder.env;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.Symbol;
+import org.jspecify.nullness.Nullable;
/**
- * An environment that maps {@link Symbols} {@code S} to bound nodes {@code V}.
+ * An environment that maps {@link Symbol}s {@code S} to bound nodes {@code V}.
*
- * <p>For example, {@link BoundClass} represents superclasses as a {@link ClassSymbol}, which only
- * contains the binary name of the type. To get the {@link BoundClass} for that supertype, an {@code
+ * <p>For example, {@code BoundClass} represents superclasses as a {@link ClassSymbol}, which only
+ * contains the binary name of the type. To get the {@code BoundClass} for that supertype, an {@code
* Env<BoundClass>} is used.
*
* <p>The indirection through env makes it possible to represent a graph with cycles using immutable
@@ -34,5 +35,14 @@ import com.google.turbine.binder.sym.Symbol;
*/
public interface Env<S extends Symbol, V> {
/** Returns the information associated with the given symbol in this environment. */
+ @Nullable
V get(S sym);
+
+ default V getNonNull(S sym) {
+ V result = get(sym);
+ if (result == null) {
+ throw new NullPointerException(sym.toString());
+ }
+ return result;
+ }
}
diff --git a/java/com/google/turbine/binder/env/LazyEnv.java b/java/com/google/turbine/binder/env/LazyEnv.java
index 9e8afd5..0b311f7 100644
--- a/java/com/google/turbine/binder/env/LazyEnv.java
+++ b/java/com/google/turbine/binder/env/LazyEnv.java
@@ -22,22 +22,23 @@ import com.google.turbine.binder.sym.Symbol;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
+import org.jspecify.nullness.Nullable;
/**
* An env that permits an analysis pass to access information about symbols from the current pass,
* recursively. Cycles are detected, and result in an {@link LazyBindingError} being thrown.
*
- * <p>This is used primarily for resolving the supertype hierarchy in {@link HierarchyBinder}. The
- * supertype hierarchy forms a directed acyclic graph, and {@link HierarchyBinder} needs to process
+ * <p>This is used primarily for resolving the supertype hierarchy in {@code HierarchyBinder}. The
+ * supertype hierarchy forms a directed acyclic graph, and {@code HierarchyBinder} needs to process
* classes in a topological sort order of that graph. Unfortuntately, we can't produce a suitable
* sort order until the graph exists.
*
* @param <T> the interface type of the bound node {@link V}, shared by any underlying environments.
- * @param <V> a specific implementation of {@code T}. For example, during hierarchy binding {@link
+ * @param <V> a specific implementation of {@code T}. For example, during hierarchy binding {@code
* SourceHeaderBoundClass} nodes are being completed from the sources being compiled, and the
- * analysis of a given symbol may require looking up {@link HeaderBoundClass} nodes that will
- * either be backed by other {@link SourceHeaderBoundClass} nodes or {@link BytecodeBoundClass}
- * nodes. So the phase uses an {@link LazyEnv<HeaderBoundClass, SourceHeaderBoundClass>}.
+ * analysis of a given symbol may require looking up {@code HeaderBoundClass} nodes that will
+ * either be backed by other {@code SourceHeaderBoundClass} nodes or {@code BytecodeBoundClass}
+ * nodes. So the phase uses an {@code LazyEnv<HeaderBoundClass, SourceHeaderBoundClass>}.
*/
public class LazyEnv<S extends Symbol, T, V extends T> implements Env<S, V> {
@@ -48,7 +49,7 @@ public class LazyEnv<S extends Symbol, T, V extends T> implements Env<S, V> {
private final ImmutableMap<S, Completer<S, T, V>> completers;
/** Values that have already been computed. */
- private final Map<S, V> cache = new LinkedHashMap<>();
+ private final Map<S, @Nullable V> cache = new LinkedHashMap<>();
/** An underlying env of already-computed {@code T}s that can be queried during completion. */
private final Env<S, T> rec;
@@ -59,7 +60,7 @@ public class LazyEnv<S extends Symbol, T, V extends T> implements Env<S, V> {
}
@Override
- public V get(S sym) {
+ public @Nullable V get(S sym) {
V v = cache.get(sym);
if (v != null) {
return v;
@@ -80,6 +81,7 @@ public class LazyEnv<S extends Symbol, T, V extends T> implements Env<S, V> {
/** A lazy value provider which is given access to the current environment. */
public interface Completer<S extends Symbol, T, V extends T> {
/** Provides the value for the given symbol in the current environment. */
+ @Nullable
V complete(Env<S, T> env, S k);
}
diff --git a/java/com/google/turbine/binder/env/SimpleEnv.java b/java/com/google/turbine/binder/env/SimpleEnv.java
index b07bf5f..9de5c9f 100644
--- a/java/com/google/turbine/binder/env/SimpleEnv.java
+++ b/java/com/google/turbine/binder/env/SimpleEnv.java
@@ -17,9 +17,11 @@
package com.google.turbine.binder.env;
import com.google.common.collect.ImmutableMap;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.turbine.binder.sym.Symbol;
import java.util.LinkedHashMap;
import java.util.Map;
+import org.jspecify.nullness.Nullable;
/** A simple {@link ImmutableMap}-backed {@link Env}. */
public class SimpleEnv<K extends Symbol, V> implements Env<K, V> {
@@ -42,7 +44,9 @@ public class SimpleEnv<K extends Symbol, V> implements Env<K, V> {
public static class Builder<K extends Symbol, V> {
private final Map<K, V> map = new LinkedHashMap<>();
- public V put(K sym, V v) {
+ // TODO(cushon): audit the cases where this return value is being ignored
+ @CanIgnoreReturnValue
+ public @Nullable V put(K sym, V v) {
return map.put(sym, v);
}
@@ -52,7 +56,7 @@ public class SimpleEnv<K extends Symbol, V> implements Env<K, V> {
}
@Override
- public V get(K sym) {
+ public @Nullable V get(K sym) {
return map.get(sym);
}
}
diff --git a/java/com/google/turbine/binder/env/package-info.java b/java/com/google/turbine/binder/env/package-info.java
new file mode 100644
index 0000000..fa57245
--- /dev/null
+++ b/java/com/google/turbine/binder/env/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+@org.jspecify.nullness.NullMarked
+package com.google.turbine.binder.env;
diff --git a/java/com/google/turbine/binder/lookup/CanonicalSymbolResolver.java b/java/com/google/turbine/binder/lookup/CanonicalSymbolResolver.java
index 1e33d5f..d44f4e4 100644
--- a/java/com/google/turbine/binder/lookup/CanonicalSymbolResolver.java
+++ b/java/com/google/turbine/binder/lookup/CanonicalSymbolResolver.java
@@ -18,11 +18,13 @@ package com.google.turbine.binder.lookup;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.tree.Tree;
+import org.jspecify.nullness.Nullable;
/** Canonical type resolution. Breaks a circular dependency between binding and import handling. */
public interface CanonicalSymbolResolver extends ImportScope.ResolveFunction {
/** Resolves a single member type of the given symbol by canonical name. */
@Override
+ @Nullable
ClassSymbol resolveOne(ClassSymbol sym, Tree.Ident bit);
/** Returns true if the given symbol is visible from the current package. */
diff --git a/java/com/google/turbine/binder/lookup/CompoundScope.java b/java/com/google/turbine/binder/lookup/CompoundScope.java
index 11309bf..bedf775 100644
--- a/java/com/google/turbine/binder/lookup/CompoundScope.java
+++ b/java/com/google/turbine/binder/lookup/CompoundScope.java
@@ -18,21 +18,21 @@ package com.google.turbine.binder.lookup;
import static com.google.common.base.Preconditions.checkNotNull;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** A {@link Scope} that chains other scopes together. */
public class CompoundScope implements Scope {
private final Scope scope;
- @Nullable private final Scope base;
+ private final @Nullable Scope base;
- private CompoundScope(Scope scope, Scope base) {
+ private CompoundScope(Scope scope, @Nullable Scope base) {
this.scope = checkNotNull(scope);
this.base = base;
}
@Override
- public LookupResult lookup(LookupKey key) {
+ public @Nullable LookupResult lookup(LookupKey key) {
LookupResult result = scope.lookup(key);
if (result != null) {
return result;
diff --git a/java/com/google/turbine/binder/lookup/CompoundTopLevelIndex.java b/java/com/google/turbine/binder/lookup/CompoundTopLevelIndex.java
index b41edb0..e7fa45f 100644
--- a/java/com/google/turbine/binder/lookup/CompoundTopLevelIndex.java
+++ b/java/com/google/turbine/binder/lookup/CompoundTopLevelIndex.java
@@ -19,7 +19,7 @@ package com.google.turbine.binder.lookup;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** A {@link TopLevelIndex} that aggregates multiple indices into one. */
// Note: this implementation doesn't detect if the indices contain incompatible information,
@@ -42,9 +42,8 @@ public class CompoundTopLevelIndex implements TopLevelIndex {
private final Scope scope =
new Scope() {
- @Nullable
@Override
- public LookupResult lookup(LookupKey lookupKey) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey) {
// Return the first matching symbol.
for (TopLevelIndex index : indexes) {
LookupResult result = index.scope().lookup(lookupKey);
@@ -62,14 +61,19 @@ public class CompoundTopLevelIndex implements TopLevelIndex {
}
@Override
- public PackageScope lookupPackage(Iterable<String> packagename) {
+ public @Nullable PackageScope lookupPackage(Iterable<String> packagename) {
// When returning package scopes, build up a compound scope containing entries from all
// indices with matching packages.
PackageScope result = null;
for (TopLevelIndex index : indexes) {
PackageScope packageScope = index.lookupPackage(packagename);
- if (packageScope != null) {
- result = result == null ? packageScope : result.append(packageScope);
+ if (packageScope == null) {
+ continue;
+ }
+ if (result == null) {
+ result = packageScope;
+ } else {
+ result = result.append(packageScope);
}
}
return result;
diff --git a/java/com/google/turbine/binder/lookup/ImportIndex.java b/java/com/google/turbine/binder/lookup/ImportIndex.java
index fd57223..bcd9366 100644
--- a/java/com/google/turbine/binder/lookup/ImportIndex.java
+++ b/java/com/google/turbine/binder/lookup/ImportIndex.java
@@ -31,6 +31,7 @@ import com.google.turbine.tree.Tree.Ident;
import com.google.turbine.tree.Tree.ImportDecl;
import java.util.HashMap;
import java.util.Map;
+import org.jspecify.nullness.Nullable;
/**
* A scope that provides entries for the single-type imports in a compilation unit.
@@ -59,7 +60,7 @@ public class ImportIndex implements ImportScope {
CanonicalSymbolResolver resolve,
final TopLevelIndex cpi,
ImmutableList<ImportDecl> imports) {
- Map<String, Supplier<ImportScope>> thunks = new HashMap<>();
+ Map<String, Supplier<@Nullable ImportScope>> thunks = new HashMap<>();
for (final Tree.ImportDecl i : imports) {
if (i.stat() || i.wild()) {
continue;
@@ -67,9 +68,9 @@ public class ImportIndex implements ImportScope {
thunks.put(
getLast(i.type()).value(),
Suppliers.memoize(
- new Supplier<ImportScope>() {
+ new Supplier<@Nullable ImportScope>() {
@Override
- public ImportScope get() {
+ public @Nullable ImportScope get() {
return namedImport(log, cpi, i, resolve);
}
}));
@@ -84,9 +85,9 @@ public class ImportIndex implements ImportScope {
thunks.putIfAbsent(
last,
Suppliers.memoize(
- new Supplier<ImportScope>() {
+ new Supplier<@Nullable ImportScope>() {
@Override
- public ImportScope get() {
+ public @Nullable ImportScope get() {
return staticNamedImport(log, cpi, i);
}
}));
@@ -95,7 +96,7 @@ public class ImportIndex implements ImportScope {
}
/** Fully resolve the canonical name of a non-static named import. */
- private static ImportScope namedImport(
+ private static @Nullable ImportScope namedImport(
TurbineLogWithSource log, TopLevelIndex cpi, ImportDecl i, CanonicalSymbolResolver resolve) {
LookupResult result = cpi.scope().lookup(new LookupKey(i.type()));
if (result == null) {
@@ -119,7 +120,7 @@ public class ImportIndex implements ImportScope {
};
}
- private static ClassSymbol resolveNext(
+ private static @Nullable ClassSymbol resolveNext(
TurbineLogWithSource log, CanonicalSymbolResolver resolve, ClassSymbol sym, Ident bit) {
ClassSymbol next = resolve.resolveOne(sym, bit);
if (next == null) {
@@ -138,7 +139,7 @@ public class ImportIndex implements ImportScope {
* hierarchy analysis is complete, so for now we resolve the base {@code java.util.HashMap} and
* defer the rest.
*/
- private static ImportScope staticNamedImport(
+ private static @Nullable ImportScope staticNamedImport(
TurbineLogWithSource log, TopLevelIndex cpi, ImportDecl i) {
LookupResult base = cpi.scope().lookup(new LookupKey(i.type()));
if (base == null) {
@@ -148,7 +149,7 @@ public class ImportIndex implements ImportScope {
}
return new ImportScope() {
@Override
- public LookupResult lookup(LookupKey lookupKey, ResolveFunction resolve) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey, ResolveFunction resolve) {
ClassSymbol sym = (ClassSymbol) base.sym();
for (Tree.Ident bit : base.remaining()) {
sym = resolve.resolveOne(sym, bit);
@@ -164,7 +165,7 @@ public class ImportIndex implements ImportScope {
}
@Override
- public LookupResult lookup(LookupKey lookup, ResolveFunction resolve) {
+ public @Nullable LookupResult lookup(LookupKey lookup, ResolveFunction resolve) {
Supplier<ImportScope> thunk = thunks.get(lookup.first().value());
if (thunk == null) {
return null;
diff --git a/java/com/google/turbine/binder/lookup/ImportScope.java b/java/com/google/turbine/binder/lookup/ImportScope.java
index 2e6917e..a33a8e2 100644
--- a/java/com/google/turbine/binder/lookup/ImportScope.java
+++ b/java/com/google/turbine/binder/lookup/ImportScope.java
@@ -18,6 +18,7 @@ package com.google.turbine.binder.lookup;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.tree.Tree;
+import org.jspecify.nullness.Nullable;
/**
* A scope for imports. Non-canonical imports depend on hierarchy analysis, so to break the cycle we
@@ -32,17 +33,19 @@ public interface ImportScope {
*/
@FunctionalInterface
interface ResolveFunction {
+ @Nullable
ClassSymbol resolveOne(ClassSymbol base, Tree.Ident name);
}
/** See {@link Scope#lookup(LookupKey)}. */
+ @Nullable
LookupResult lookup(LookupKey lookupKey, ResolveFunction resolve);
/** Adds a scope to the chain, in the manner of {@link CompoundScope#append(Scope)}. */
default ImportScope append(ImportScope next) {
return new ImportScope() {
@Override
- public LookupResult lookup(LookupKey lookupKey, ResolveFunction resolve) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey, ResolveFunction resolve) {
LookupResult result = next.lookup(lookupKey, resolve);
if (result != null) {
return result;
@@ -60,7 +63,7 @@ public interface ImportScope {
static ImportScope fromScope(Scope scope) {
return new ImportScope() {
@Override
- public LookupResult lookup(LookupKey lookupKey, ResolveFunction resolve) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey, ResolveFunction resolve) {
return scope.lookup(lookupKey);
}
};
@@ -71,7 +74,7 @@ public interface ImportScope {
return CompoundScope.base(
new Scope() {
@Override
- public LookupResult lookup(LookupKey lookupKey) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey) {
return ImportScope.this.lookup(lookupKey, resolve);
}
});
diff --git a/java/com/google/turbine/binder/lookup/MemberImportIndex.java b/java/com/google/turbine/binder/lookup/MemberImportIndex.java
index a8ecc7a..d825396 100644
--- a/java/com/google/turbine/binder/lookup/MemberImportIndex.java
+++ b/java/com/google/turbine/binder/lookup/MemberImportIndex.java
@@ -30,21 +30,22 @@ import com.google.turbine.tree.Tree.ImportDecl;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
+import org.jspecify.nullness.Nullable;
/** An index for statically imported members, in particular constant variables. */
public class MemberImportIndex {
/** A cache of resolved static imports, keyed by the simple name of the member. */
- private final Map<String, Supplier<ClassSymbol>> cache = new LinkedHashMap<>();
+ private final Map<String, Supplier<@Nullable ClassSymbol>> cache = new LinkedHashMap<>();
- private final ImmutableList<Supplier<ClassSymbol>> classes;
+ private final ImmutableList<Supplier<@Nullable ClassSymbol>> classes;
public MemberImportIndex(
SourceFile source,
CanonicalSymbolResolver resolve,
TopLevelIndex tli,
ImmutableList<ImportDecl> imports) {
- ImmutableList.Builder<Supplier<ClassSymbol>> packageScopes = ImmutableList.builder();
+ ImmutableList.Builder<Supplier<@Nullable ClassSymbol>> packageScopes = ImmutableList.builder();
for (ImportDecl i : imports) {
if (!i.stat()) {
continue;
@@ -52,9 +53,9 @@ public class MemberImportIndex {
if (i.wild()) {
packageScopes.add(
Suppliers.memoize(
- new Supplier<ClassSymbol>() {
+ new Supplier<@Nullable ClassSymbol>() {
@Override
- public ClassSymbol get() {
+ public @Nullable ClassSymbol get() {
LookupResult result = tli.scope().lookup(new LookupKey(i.type()));
if (result == null) {
return null;
@@ -70,15 +71,18 @@ public class MemberImportIndex {
cache.put(
getLast(i.type()).value(),
Suppliers.memoize(
- new Supplier<ClassSymbol>() {
+ new Supplier<@Nullable ClassSymbol>() {
@Override
- public ClassSymbol get() {
+ public @Nullable ClassSymbol get() {
LookupResult result = tli.scope().lookup(new LookupKey(i.type()));
if (result == null) {
return null;
}
ClassSymbol sym = (ClassSymbol) result.sym();
for (int i = 0; i < result.remaining().size() - 1; i++) {
+ if (sym == null) {
+ return null;
+ }
sym = resolve.resolveOne(sym, result.remaining().get(i));
}
return sym;
@@ -107,8 +111,8 @@ public class MemberImportIndex {
}
/** Resolves the owner of a single-member static import of the given simple name. */
- public ClassSymbol singleMemberImport(String simpleName) {
- Supplier<ClassSymbol> cachedResult = cache.get(simpleName);
+ public @Nullable ClassSymbol singleMemberImport(String simpleName) {
+ Supplier<@Nullable ClassSymbol> cachedResult = cache.get(simpleName);
return cachedResult != null ? cachedResult.get() : null;
}
diff --git a/java/com/google/turbine/binder/lookup/PackageScope.java b/java/com/google/turbine/binder/lookup/PackageScope.java
index 695e802..94e950f 100644
--- a/java/com/google/turbine/binder/lookup/PackageScope.java
+++ b/java/com/google/turbine/binder/lookup/PackageScope.java
@@ -18,7 +18,7 @@ package com.google.turbine.binder.lookup;
import com.google.common.collect.Iterables;
import com.google.turbine.binder.sym.ClassSymbol;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/**
* A scope that corresponds to a particular package, which supports iteration over its enclosed
diff --git a/java/com/google/turbine/binder/lookup/Scope.java b/java/com/google/turbine/binder/lookup/Scope.java
index 12466f4..eb9f5cb 100644
--- a/java/com/google/turbine/binder/lookup/Scope.java
+++ b/java/com/google/turbine/binder/lookup/Scope.java
@@ -16,7 +16,7 @@
package com.google.turbine.binder.lookup;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** A scope that defines types, and supports qualified name resolution. */
public interface Scope {
diff --git a/java/com/google/turbine/binder/lookup/SimpleTopLevelIndex.java b/java/com/google/turbine/binder/lookup/SimpleTopLevelIndex.java
index 4ec05bc..179f603 100644
--- a/java/com/google/turbine/binder/lookup/SimpleTopLevelIndex.java
+++ b/java/com/google/turbine/binder/lookup/SimpleTopLevelIndex.java
@@ -23,7 +23,7 @@ import com.google.turbine.binder.sym.ClassSymbol;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/**
* An index of canonical type names where all members are known statically.
@@ -36,17 +36,17 @@ public class SimpleTopLevelIndex implements TopLevelIndex {
/** A class symbol or package. */
public static class Node {
- public Node lookup(String bit) {
+ public @Nullable Node lookup(String bit) {
return children.get(bit);
}
- @Nullable private final ClassSymbol sym;
+ private final @Nullable ClassSymbol sym;
// TODO(cushon): the set of children is typically going to be small, consider optimizing this
// to use a denser representation where appropriate.
private final Map<String, Node> children = new HashMap<>();
- Node(ClassSymbol sym) {
+ Node(@Nullable ClassSymbol sym) {
this.sym = sym;
}
@@ -56,7 +56,7 @@ public class SimpleTopLevelIndex implements TopLevelIndex {
*
* @return {@code null} if an existing symbol with the same name has already been inserted.
*/
- private Node insert(String name, ClassSymbol sym) {
+ private @Nullable Node insert(String name, @Nullable ClassSymbol sym) {
Node child = children.get(name);
if (child != null) {
if (child.sym != null) {
@@ -83,7 +83,7 @@ public class SimpleTopLevelIndex implements TopLevelIndex {
final Node root = new Node(null);
/** Inserts a {@link ClassSymbol} into the index, creating any needed packages. */
- public boolean insert(ClassSymbol sym) {
+ public void insert(ClassSymbol sym) {
String binaryName = sym.binaryName();
int start = 0;
int end = binaryName.indexOf('/');
@@ -95,7 +95,7 @@ public class SimpleTopLevelIndex implements TopLevelIndex {
// symbol), bail out. When inserting elements from the classpath, this results in the
// expected first-match-wins semantics.
if (curr == null) {
- return false;
+ return;
}
start = end + 1;
end = binaryName.indexOf('/', start);
@@ -103,9 +103,9 @@ public class SimpleTopLevelIndex implements TopLevelIndex {
String simpleName = binaryName.substring(start);
curr = curr.insert(simpleName, sym);
if (curr == null || !Objects.equals(curr.sym, sym)) {
- return false;
+ return;
}
- return true;
+ return;
}
}
@@ -133,8 +133,7 @@ public class SimpleTopLevelIndex implements TopLevelIndex {
final Scope scope =
new Scope() {
@Override
- @Nullable
- public LookupResult lookup(LookupKey lookupKey) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey) {
Node curr = root;
while (true) {
curr = curr.lookup(lookupKey.first().value());
@@ -159,7 +158,7 @@ public class SimpleTopLevelIndex implements TopLevelIndex {
/** Returns a {@link Scope} that performs lookups in the given qualified package name. */
@Override
- public PackageScope lookupPackage(Iterable<String> packagename) {
+ public @Nullable PackageScope lookupPackage(Iterable<String> packagename) {
Node curr = root;
for (String bit : packagename) {
curr = curr.lookup(bit);
@@ -179,7 +178,7 @@ public class SimpleTopLevelIndex implements TopLevelIndex {
}
@Override
- public LookupResult lookup(LookupKey lookupKey) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey) {
Node result = node.lookup(lookupKey.first().value());
if (result != null && result.sym != null) {
return new LookupResult(result.sym, lookupKey);
diff --git a/java/com/google/turbine/binder/lookup/TopLevelIndex.java b/java/com/google/turbine/binder/lookup/TopLevelIndex.java
index a364119..049ac5c 100644
--- a/java/com/google/turbine/binder/lookup/TopLevelIndex.java
+++ b/java/com/google/turbine/binder/lookup/TopLevelIndex.java
@@ -16,6 +16,7 @@
package com.google.turbine.binder.lookup;
+import org.jspecify.nullness.Nullable;
/**
* An index of canonical type names.
@@ -35,5 +36,6 @@ public interface TopLevelIndex {
Scope scope();
/** Returns a scope to look up members of the given package. */
+ @Nullable
PackageScope lookupPackage(Iterable<String> packagename);
}
diff --git a/java/com/google/turbine/binder/lookup/WildImportIndex.java b/java/com/google/turbine/binder/lookup/WildImportIndex.java
index cfe58c7..8b4bab1 100644
--- a/java/com/google/turbine/binder/lookup/WildImportIndex.java
+++ b/java/com/google/turbine/binder/lookup/WildImportIndex.java
@@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableList;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.tree.Tree;
import com.google.turbine.tree.Tree.ImportDecl;
+import org.jspecify.nullness.Nullable;
/**
* A scope that provides best-effort lookup for on-demand imported types in a compilation unit.
@@ -45,14 +46,14 @@ public class WildImportIndex implements ImportScope {
CanonicalSymbolResolver importResolver,
final TopLevelIndex cpi,
ImmutableList<ImportDecl> imports) {
- ImmutableList.Builder<Supplier<ImportScope>> packageScopes = ImmutableList.builder();
+ ImmutableList.Builder<Supplier<@Nullable ImportScope>> packageScopes = ImmutableList.builder();
for (final ImportDecl i : imports) {
if (i.wild()) {
packageScopes.add(
Suppliers.memoize(
- new Supplier<ImportScope>() {
+ new Supplier<@Nullable ImportScope>() {
@Override
- public ImportScope get() {
+ public @Nullable ImportScope get() {
if (i.stat()) {
return staticOnDemandImport(cpi, i, importResolver);
} else {
@@ -66,7 +67,7 @@ public class WildImportIndex implements ImportScope {
}
/** Full resolve the type for a non-static on-demand import. */
- private static ImportScope onDemandImport(
+ private static @Nullable ImportScope onDemandImport(
TopLevelIndex cpi, ImportDecl i, final CanonicalSymbolResolver importResolver) {
ImmutableList.Builder<String> flatNames = ImmutableList.builder();
for (Tree.Ident ident : i.type()) {
@@ -77,7 +78,7 @@ public class WildImportIndex implements ImportScope {
// a wildcard import of a package
return new ImportScope() {
@Override
- public LookupResult lookup(LookupKey lookupKey, ResolveFunction resolve) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey, ResolveFunction resolve) {
return packageIndex.lookup(lookupKey);
}
};
@@ -92,7 +93,7 @@ public class WildImportIndex implements ImportScope {
}
return new ImportScope() {
@Override
- public LookupResult lookup(LookupKey lookupKey, ResolveFunction unused) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey, ResolveFunction unused) {
return resolveMember(member, importResolver, importResolver, lookupKey);
}
};
@@ -103,7 +104,7 @@ public class WildImportIndex implements ImportScope {
* ImportScope#staticNamedImport} for an explanation of why the possibly non-canonical part is
* deferred).
*/
- private static ImportScope staticOnDemandImport(
+ private static @Nullable ImportScope staticOnDemandImport(
TopLevelIndex cpi, ImportDecl i, final CanonicalSymbolResolver importResolver) {
LookupResult result = cpi.scope().lookup(new LookupKey(i.type()));
if (result == null) {
@@ -111,7 +112,7 @@ public class WildImportIndex implements ImportScope {
}
return new ImportScope() {
@Override
- public LookupResult lookup(LookupKey lookupKey, ResolveFunction resolve) {
+ public @Nullable LookupResult lookup(LookupKey lookupKey, ResolveFunction resolve) {
ClassSymbol member = resolveImportBase(result, resolve, importResolver);
if (member == null) {
return null;
@@ -121,7 +122,7 @@ public class WildImportIndex implements ImportScope {
};
}
- private static LookupResult resolveMember(
+ private static @Nullable LookupResult resolveMember(
ClassSymbol base,
ResolveFunction resolve,
CanonicalSymbolResolver importResolver,
@@ -136,7 +137,7 @@ public class WildImportIndex implements ImportScope {
return new LookupResult(member, lookupKey);
}
- static ClassSymbol resolveImportBase(
+ static @Nullable ClassSymbol resolveImportBase(
LookupResult result, ResolveFunction resolve, CanonicalSymbolResolver importResolver) {
ClassSymbol member = (ClassSymbol) result.sym();
for (Tree.Ident bit : result.remaining()) {
@@ -152,7 +153,7 @@ public class WildImportIndex implements ImportScope {
}
@Override
- public LookupResult lookup(LookupKey lookup, ResolveFunction resolve) {
+ public @Nullable LookupResult lookup(LookupKey lookup, ResolveFunction resolve) {
for (Supplier<ImportScope> packageScope : packages) {
ImportScope scope = packageScope.get();
if (scope == null) {
diff --git a/java/com/google/turbine/binder/lookup/package-info.java b/java/com/google/turbine/binder/lookup/package-info.java
new file mode 100644
index 0000000..7784138
--- /dev/null
+++ b/java/com/google/turbine/binder/lookup/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+@org.jspecify.nullness.NullMarked
+package com.google.turbine.binder.lookup;
diff --git a/java/com/google/turbine/binder/package-info.java b/java/com/google/turbine/binder/package-info.java
new file mode 100644
index 0000000..9f550e0
--- /dev/null
+++ b/java/com/google/turbine/binder/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+@org.jspecify.nullness.NullMarked
+package com.google.turbine.binder;
diff --git a/java/com/google/turbine/binder/sym/ClassSymbol.java b/java/com/google/turbine/binder/sym/ClassSymbol.java
index 20513e7..9bb556f 100644
--- a/java/com/google/turbine/binder/sym/ClassSymbol.java
+++ b/java/com/google/turbine/binder/sym/ClassSymbol.java
@@ -17,6 +17,7 @@
package com.google.turbine.binder.sym;
import com.google.errorprone.annotations.Immutable;
+import org.jspecify.nullness.Nullable;
/**
* A class symbol.
@@ -32,6 +33,7 @@ public class ClassSymbol implements Symbol {
public static final ClassSymbol OBJECT = new ClassSymbol("java/lang/Object");
public static final ClassSymbol STRING = new ClassSymbol("java/lang/String");
public static final ClassSymbol ENUM = new ClassSymbol("java/lang/Enum");
+ public static final ClassSymbol RECORD = new ClassSymbol("java/lang/Record");
public static final ClassSymbol ANNOTATION = new ClassSymbol("java/lang/annotation/Annotation");
public static final ClassSymbol INHERITED = new ClassSymbol("java/lang/annotation/Inherited");
public static final ClassSymbol CLONEABLE = new ClassSymbol("java/lang/Cloneable");
@@ -68,7 +70,7 @@ public class ClassSymbol implements Symbol {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
return o instanceof ClassSymbol && className.equals(((ClassSymbol) o).className);
}
diff --git a/java/com/google/turbine/binder/sym/FieldSymbol.java b/java/com/google/turbine/binder/sym/FieldSymbol.java
index d6c3cbc..1040500 100644
--- a/java/com/google/turbine/binder/sym/FieldSymbol.java
+++ b/java/com/google/turbine/binder/sym/FieldSymbol.java
@@ -18,6 +18,7 @@ package com.google.turbine.binder.sym;
import com.google.errorprone.annotations.Immutable;
import java.util.Objects;
+import org.jspecify.nullness.Nullable;
/** A field symbol. */
@Immutable
@@ -51,7 +52,7 @@ public class FieldSymbol implements Symbol {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof FieldSymbol)) {
return false;
}
diff --git a/java/com/google/turbine/binder/sym/MethodSymbol.java b/java/com/google/turbine/binder/sym/MethodSymbol.java
index f4b211d..12c1aa5 100644
--- a/java/com/google/turbine/binder/sym/MethodSymbol.java
+++ b/java/com/google/turbine/binder/sym/MethodSymbol.java
@@ -18,6 +18,7 @@ package com.google.turbine.binder.sym;
import com.google.errorprone.annotations.Immutable;
import java.util.Objects;
+import org.jspecify.nullness.Nullable;
/** A method symbol. */
@Immutable
@@ -58,7 +59,7 @@ public class MethodSymbol implements Symbol {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof MethodSymbol)) {
return false;
}
diff --git a/java/com/google/turbine/binder/sym/ModuleSymbol.java b/java/com/google/turbine/binder/sym/ModuleSymbol.java
index e442353..4ce5c7a 100644
--- a/java/com/google/turbine/binder/sym/ModuleSymbol.java
+++ b/java/com/google/turbine/binder/sym/ModuleSymbol.java
@@ -17,6 +17,7 @@
package com.google.turbine.binder.sym;
import com.google.errorprone.annotations.Immutable;
+import org.jspecify.nullness.Nullable;
/** A module symbol. */
@Immutable
@@ -43,7 +44,7 @@ public class ModuleSymbol implements Symbol {
}
@Override
- public boolean equals(Object other) {
+ public boolean equals(@Nullable Object other) {
return other instanceof ModuleSymbol && name.equals(((ModuleSymbol) other).name);
}
diff --git a/java/com/google/turbine/binder/sym/PackageSymbol.java b/java/com/google/turbine/binder/sym/PackageSymbol.java
index 8354a34..be071e0 100644
--- a/java/com/google/turbine/binder/sym/PackageSymbol.java
+++ b/java/com/google/turbine/binder/sym/PackageSymbol.java
@@ -17,6 +17,7 @@
package com.google.turbine.binder.sym;
import com.google.errorprone.annotations.Immutable;
+import org.jspecify.nullness.Nullable;
/** A package symbol. */
@Immutable
@@ -34,7 +35,7 @@ public class PackageSymbol implements Symbol {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof PackageSymbol && binaryName.equals(((PackageSymbol) obj).binaryName);
}
diff --git a/java/com/google/turbine/binder/sym/ParamSymbol.java b/java/com/google/turbine/binder/sym/ParamSymbol.java
index 328658e..e939223 100644
--- a/java/com/google/turbine/binder/sym/ParamSymbol.java
+++ b/java/com/google/turbine/binder/sym/ParamSymbol.java
@@ -18,6 +18,7 @@ package com.google.turbine.binder.sym;
import com.google.errorprone.annotations.Immutable;
import java.util.Objects;
+import org.jspecify.nullness.Nullable;
/** A parameter symbol. */
@Immutable
@@ -51,7 +52,7 @@ public class ParamSymbol implements Symbol {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof ParamSymbol)) {
return false;
}
diff --git a/java/com/google/turbine/binder/sym/RecordComponentSymbol.java b/java/com/google/turbine/binder/sym/RecordComponentSymbol.java
new file mode 100644
index 0000000..c3f44f6
--- /dev/null
+++ b/java/com/google/turbine/binder/sym/RecordComponentSymbol.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.google.turbine.binder.sym;
+
+import com.google.errorprone.annotations.Immutable;
+import java.util.Objects;
+import org.jspecify.nullness.Nullable;
+
+/** A record component symbol. */
+@Immutable
+public class RecordComponentSymbol implements Symbol {
+ private final ClassSymbol owner;
+ private final String name;
+
+ public RecordComponentSymbol(ClassSymbol owner, String name) {
+ this.owner = owner;
+ this.name = name;
+ }
+
+ /** The enclosing class. */
+ public ClassSymbol owner() {
+ return owner;
+ }
+
+ /** The parameter name. */
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public Kind symKind() {
+ return Kind.RECORD_COMPONENT;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, owner);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof RecordComponentSymbol)) {
+ return false;
+ }
+ RecordComponentSymbol other = (RecordComponentSymbol) obj;
+ return name().equals(other.name()) && owner().equals(other.owner());
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/java/com/google/turbine/binder/sym/Symbol.java b/java/com/google/turbine/binder/sym/Symbol.java
index bc142cb..b1eb8e1 100644
--- a/java/com/google/turbine/binder/sym/Symbol.java
+++ b/java/com/google/turbine/binder/sym/Symbol.java
@@ -28,6 +28,7 @@ public interface Symbol {
METHOD,
FIELD,
PARAMETER,
+ RECORD_COMPONENT,
MODULE,
PACKAGE
}
diff --git a/java/com/google/turbine/binder/sym/TyVarSymbol.java b/java/com/google/turbine/binder/sym/TyVarSymbol.java
index 1ecec11..5ba0788 100644
--- a/java/com/google/turbine/binder/sym/TyVarSymbol.java
+++ b/java/com/google/turbine/binder/sym/TyVarSymbol.java
@@ -18,6 +18,7 @@ package com.google.turbine.binder.sym;
import com.google.errorprone.annotations.Immutable;
import java.util.Objects;
+import org.jspecify.nullness.Nullable;
/** A type variable symbol. */
@Immutable
@@ -52,7 +53,7 @@ public class TyVarSymbol implements Symbol {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof TyVarSymbol)) {
return false;
}
diff --git a/java/com/google/turbine/binder/sym/package-info.java b/java/com/google/turbine/binder/sym/package-info.java
new file mode 100644
index 0000000..96f3a87
--- /dev/null
+++ b/java/com/google/turbine/binder/sym/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+@org.jspecify.nullness.NullMarked
+package com.google.turbine.binder.sym;
diff --git a/java/com/google/turbine/bytecode/AnnotationWriter.java b/java/com/google/turbine/bytecode/AnnotationWriter.java
index b547971..ccae0f1 100644
--- a/java/com/google/turbine/bytecode/AnnotationWriter.java
+++ b/java/com/google/turbine/bytecode/AnnotationWriter.java
@@ -33,8 +33,9 @@ import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.ThrowsTarget;
import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.TypeParameterBoundTarget;
import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.TypeParameterTarget;
import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.TypePath;
+import com.google.turbine.model.Const;
import com.google.turbine.model.Const.Value;
-import java.util.Map.Entry;
+import java.util.Map;
/** Writes an {@link AnnotationInfo} to a class file. */
public class AnnotationWriter {
@@ -50,7 +51,7 @@ public class AnnotationWriter {
public void writeAnnotation(AnnotationInfo annotation) {
output.writeShort(pool.utf8(annotation.typeName()));
output.writeShort(annotation.elementValuePairs().size());
- for (Entry<String, ElementValue> entry : annotation.elementValuePairs().entrySet()) {
+ for (Map.Entry<String, ElementValue> entry : annotation.elementValuePairs().entrySet()) {
output.writeShort(pool.utf8(entry.getKey()));
writeElementValue(entry.getValue());
}
@@ -79,31 +80,31 @@ public class AnnotationWriter {
private void writeConstElementValue(Value value) {
switch (value.constantTypeKind()) {
case BYTE:
- writeConst('B', pool.integer(value.asInteger().value()));
+ writeConst('B', pool.integer(((Const.ByteValue) value).value()));
break;
case CHAR:
- writeConst('C', pool.integer(value.asInteger().value()));
+ writeConst('C', pool.integer(((Const.CharValue) value).value()));
break;
case SHORT:
- writeConst('S', pool.integer(value.asInteger().value()));
+ writeConst('S', pool.integer(((Const.ShortValue) value).value()));
break;
case DOUBLE:
- writeConst('D', pool.doubleInfo(value.asDouble().value()));
+ writeConst('D', pool.doubleInfo(((Const.DoubleValue) value).value()));
break;
case FLOAT:
- writeConst('F', pool.floatInfo(value.asFloat().value()));
+ writeConst('F', pool.floatInfo(((Const.FloatValue) value).value()));
break;
case INT:
- writeConst('I', pool.integer(value.asInteger().value()));
+ writeConst('I', pool.integer(((Const.IntValue) value).value()));
break;
case LONG:
- writeConst('J', pool.longInfo(value.asLong().value()));
+ writeConst('J', pool.longInfo(((Const.LongValue) value).value()));
break;
case STRING:
- writeConst('s', pool.utf8(value.asString().value()));
+ writeConst('s', pool.utf8(((Const.StringValue) value).value()));
break;
case BOOLEAN:
- writeConst('Z', pool.integer(value.asBoolean().value() ? 1 : 0));
+ writeConst('Z', pool.integer(((Const.BooleanValue) value).value() ? 1 : 0));
break;
default:
throw new AssertionError(value.constantTypeKind());
diff --git a/java/com/google/turbine/bytecode/Attribute.java b/java/com/google/turbine/bytecode/Attribute.java
index 29efb60..ad6ffc1 100644
--- a/java/com/google/turbine/bytecode/Attribute.java
+++ b/java/com/google/turbine/bytecode/Attribute.java
@@ -41,7 +41,12 @@ interface Attribute {
RUNTIME_VISIBLE_TYPE_ANNOTATIONS("RuntimeVisibleTypeAnnotations"),
RUNTIME_INVISIBLE_TYPE_ANNOTATIONS("RuntimeInvisibleTypeAnnotations"),
METHOD_PARAMETERS("MethodParameters"),
- MODULE("Module");
+ MODULE("Module"),
+ NEST_HOST("NestHost"),
+ NEST_MEMBERS("NestMembers"),
+ RECORD("Record"),
+ TURBINE_TRANSITIVE_JAR("TurbineTransitiveJar"),
+ PERMITTED_SUBCLASSES("PermittedSubclasses");
private final String signature;
@@ -309,4 +314,115 @@ interface Attribute {
return module;
}
}
+
+ /** A JVMS §4.7.28 NestHost attribute. */
+ class NestHost implements Attribute {
+
+ private final String hostClass;
+
+ public NestHost(String hostClass) {
+ this.hostClass = hostClass;
+ }
+
+ String hostClass() {
+ return hostClass;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.NEST_HOST;
+ }
+ }
+
+ /** A JVMS §4.7.29 NestHost attribute. */
+ class NestMembers implements Attribute {
+
+ private final ImmutableList<String> classes;
+
+ public NestMembers(ImmutableList<String> classes) {
+ this.classes = classes;
+ }
+
+ ImmutableList<String> classes() {
+ return classes;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.NEST_MEMBERS;
+ }
+ }
+
+ /** A JVMS §4.7.30 Record attribute. */
+ class Record implements Attribute {
+
+ private final ImmutableList<Component> components;
+
+ public Record(ImmutableList<Component> components) {
+ this.components = components;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.RECORD;
+ }
+
+ ImmutableList<Component> components() {
+ return components;
+ }
+
+ /** A JVMS §4.7.30 Record component info. */
+ static class Component {
+ private final String name;
+ private final String descriptor;
+ private final List<Attribute> attributes;
+
+ Component(String name, String descriptor, List<Attribute> attributes) {
+ this.name = name;
+ this.descriptor = descriptor;
+ this.attributes = attributes;
+ }
+
+ String name() {
+ return name;
+ }
+
+ String descriptor() {
+ return descriptor;
+ }
+
+ List<Attribute> attributes() {
+ return attributes;
+ }
+ }
+ }
+
+ /** A JVMS §4.7.31 PermittedSubclasses attribute. */
+ class PermittedSubclasses implements Attribute {
+ final List<String> permits;
+
+ public PermittedSubclasses(List<String> permits) {
+ this.permits = permits;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.PERMITTED_SUBCLASSES;
+ }
+ }
+
+ /** A custom attribute for recording the original jar of repackaged transitive classes. */
+ class TurbineTransitiveJar implements Attribute {
+
+ final String transitiveJar;
+
+ public TurbineTransitiveJar(String transitiveJar) {
+ this.transitiveJar = transitiveJar;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.TURBINE_TRANSITIVE_JAR;
+ }
+ }
}
diff --git a/java/com/google/turbine/bytecode/AttributeWriter.java b/java/com/google/turbine/bytecode/AttributeWriter.java
index c5ffd16..6aac19a 100644
--- a/java/com/google/turbine/bytecode/AttributeWriter.java
+++ b/java/com/google/turbine/bytecode/AttributeWriter.java
@@ -24,6 +24,7 @@ import com.google.turbine.bytecode.Attribute.ExceptionsAttribute;
import com.google.turbine.bytecode.Attribute.InnerClasses;
import com.google.turbine.bytecode.Attribute.MethodParameters;
import com.google.turbine.bytecode.Attribute.Signature;
+import com.google.turbine.bytecode.Attribute.TurbineTransitiveJar;
import com.google.turbine.bytecode.Attribute.TypeAnnotations;
import com.google.turbine.bytecode.ClassFile.AnnotationInfo;
import com.google.turbine.bytecode.ClassFile.MethodInfo.ParameterInfo;
@@ -41,56 +42,69 @@ import java.util.List;
public class AttributeWriter {
private final ConstantPool pool;
- private final ByteArrayDataOutput output;
- public AttributeWriter(ConstantPool pool, ByteArrayDataOutput output) {
+ public AttributeWriter(ConstantPool pool) {
this.pool = pool;
- this.output = output;
}
/** Writes a single attribute. */
- public void write(Attribute attribute) {
+ public void write(ByteArrayDataOutput output, Attribute attribute) {
switch (attribute.kind()) {
case SIGNATURE:
- writeSignatureAttribute((Signature) attribute);
+ writeSignatureAttribute(output, (Signature) attribute);
break;
case EXCEPTIONS:
- writeExceptionsAttribute((ExceptionsAttribute) attribute);
+ writeExceptionsAttribute(output, (ExceptionsAttribute) attribute);
break;
case INNER_CLASSES:
- writeInnerClasses((InnerClasses) attribute);
+ writeInnerClasses(output, (InnerClasses) attribute);
break;
case CONSTANT_VALUE:
- writeConstantValue((ConstantValue) attribute);
+ writeConstantValue(output, (ConstantValue) attribute);
break;
case RUNTIME_VISIBLE_ANNOTATIONS:
case RUNTIME_INVISIBLE_ANNOTATIONS:
- writeAnnotation((Attribute.Annotations) attribute);
+ writeAnnotation(output, (Attribute.Annotations) attribute);
break;
case ANNOTATION_DEFAULT:
- writeAnnotationDefault((Attribute.AnnotationDefault) attribute);
+ writeAnnotationDefault(output, (Attribute.AnnotationDefault) attribute);
break;
case RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS:
case RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS:
- writeParameterAnnotations((Attribute.ParameterAnnotations) attribute);
+ writeParameterAnnotations(output, (Attribute.ParameterAnnotations) attribute);
break;
case DEPRECATED:
- writeDeprecated(attribute);
+ writeDeprecated(output, attribute);
break;
case RUNTIME_INVISIBLE_TYPE_ANNOTATIONS:
case RUNTIME_VISIBLE_TYPE_ANNOTATIONS:
- writeTypeAnnotation((Attribute.TypeAnnotations) attribute);
+ writeTypeAnnotation(output, (Attribute.TypeAnnotations) attribute);
break;
case METHOD_PARAMETERS:
- writeMethodParameters((Attribute.MethodParameters) attribute);
+ writeMethodParameters(output, (Attribute.MethodParameters) attribute);
break;
case MODULE:
- writeModule((Attribute.Module) attribute);
+ writeModule(output, (Attribute.Module) attribute);
+ break;
+ case NEST_HOST:
+ writeNestHost(output, (Attribute.NestHost) attribute);
+ break;
+ case NEST_MEMBERS:
+ writeNestMembers(output, (Attribute.NestMembers) attribute);
+ break;
+ case RECORD:
+ writeRecord(output, (Attribute.Record) attribute);
+ break;
+ case PERMITTED_SUBCLASSES:
+ writePermittedSubclasses(output, (Attribute.PermittedSubclasses) attribute);
+ break;
+ case TURBINE_TRANSITIVE_JAR:
+ writeTurbineTransitiveJar(output, (Attribute.TurbineTransitiveJar) attribute);
break;
}
}
- private void writeInnerClasses(InnerClasses attribute) {
+ private void writeInnerClasses(ByteArrayDataOutput output, InnerClasses attribute) {
output.writeShort(pool.utf8(attribute.kind().signature()));
output.writeInt(attribute.inners.size() * 8 + 2);
output.writeShort(attribute.inners.size());
@@ -102,7 +116,7 @@ public class AttributeWriter {
}
}
- private void writeExceptionsAttribute(ExceptionsAttribute attribute) {
+ private void writeExceptionsAttribute(ByteArrayDataOutput output, ExceptionsAttribute attribute) {
output.writeShort(pool.utf8(attribute.kind().signature()));
output.writeInt(2 + attribute.exceptions.size() * 2);
output.writeShort(attribute.exceptions.size());
@@ -111,44 +125,50 @@ public class AttributeWriter {
}
}
- private void writeSignatureAttribute(Signature attribute) {
+ private void writeSignatureAttribute(ByteArrayDataOutput output, Signature attribute) {
output.writeShort(pool.utf8(attribute.kind().signature()));
output.writeInt(2);
output.writeShort(pool.utf8(attribute.signature));
}
- public void writeConstantValue(ConstantValue attribute) {
+ public void writeConstantValue(ByteArrayDataOutput output, ConstantValue attribute) {
output.writeShort(pool.utf8(attribute.kind().signature()));
output.writeInt(2);
Const.Value value = attribute.value;
switch (value.constantTypeKind()) {
case INT:
+ output.writeShort(pool.integer(((Const.IntValue) value).value()));
+ break;
case CHAR:
+ output.writeShort(pool.integer(((Const.CharValue) value).value()));
+ break;
case SHORT:
+ output.writeShort(pool.integer(((Const.ShortValue) value).value()));
+ break;
case BYTE:
- output.writeShort(pool.integer(value.asInteger().value()));
+ output.writeShort(pool.integer(((Const.ByteValue) value).value()));
break;
case LONG:
- output.writeShort(pool.longInfo(value.asLong().value()));
+ output.writeShort(pool.longInfo(((Const.LongValue) value).value()));
break;
case DOUBLE:
- output.writeShort(pool.doubleInfo(value.asDouble().value()));
+ output.writeShort(pool.doubleInfo(((Const.DoubleValue) value).value()));
break;
case FLOAT:
- output.writeShort(pool.floatInfo(value.asFloat().value()));
+ output.writeShort(pool.floatInfo(((Const.FloatValue) value).value()));
break;
case BOOLEAN:
- output.writeShort(pool.integer(value.asBoolean().value() ? 1 : 0));
+ output.writeShort(pool.integer(((Const.BooleanValue) value).value() ? 1 : 0));
break;
case STRING:
- output.writeShort(pool.string(value.asString().value()));
+ output.writeShort(pool.string(((Const.StringValue) value).value()));
break;
default:
throw new AssertionError(value.constantTypeKind());
}
}
- public void writeAnnotation(Annotations attribute) {
+ public void writeAnnotation(ByteArrayDataOutput output, Annotations attribute) {
output.writeShort(pool.utf8(attribute.kind().signature()));
ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
tmp.writeShort(attribute.annotations().size());
@@ -160,7 +180,8 @@ public class AttributeWriter {
output.write(data);
}
- public void writeAnnotationDefault(Attribute.AnnotationDefault attribute) {
+ public void writeAnnotationDefault(
+ ByteArrayDataOutput output, Attribute.AnnotationDefault attribute) {
output.writeShort(pool.utf8(attribute.kind().signature()));
ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
new AnnotationWriter(pool, tmp).writeElementValue(attribute.value());
@@ -169,7 +190,8 @@ public class AttributeWriter {
output.write(data);
}
- public void writeParameterAnnotations(Attribute.ParameterAnnotations attribute) {
+ public void writeParameterAnnotations(
+ ByteArrayDataOutput output, Attribute.ParameterAnnotations attribute) {
output.writeShort(pool.utf8(attribute.kind().signature()));
ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
tmp.writeByte(attribute.annotations().size());
@@ -184,12 +206,12 @@ public class AttributeWriter {
output.write(data);
}
- private void writeDeprecated(Attribute attribute) {
+ private void writeDeprecated(ByteArrayDataOutput output, Attribute attribute) {
output.writeShort(pool.utf8(attribute.kind().signature()));
output.writeInt(0);
}
- private void writeTypeAnnotation(TypeAnnotations attribute) {
+ private void writeTypeAnnotation(ByteArrayDataOutput output, TypeAnnotations attribute) {
output.writeShort(pool.utf8(attribute.kind().signature()));
ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
tmp.writeShort(attribute.annotations().size());
@@ -201,7 +223,7 @@ public class AttributeWriter {
output.write(data);
}
- private void writeMethodParameters(MethodParameters attribute) {
+ private void writeMethodParameters(ByteArrayDataOutput output, MethodParameters attribute) {
output.writeShort(pool.utf8(attribute.kind().signature()));
output.writeInt(attribute.parameters().size() * 4 + 1);
output.writeByte(attribute.parameters().size());
@@ -211,7 +233,7 @@ public class AttributeWriter {
}
}
- private void writeModule(Attribute.Module attribute) {
+ private void writeModule(ByteArrayDataOutput output, Attribute.Module attribute) {
ModuleInfo module = attribute.module();
ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
@@ -266,4 +288,53 @@ public class AttributeWriter {
output.writeInt(data.length);
output.write(data);
}
+
+ private void writeNestHost(ByteArrayDataOutput output, Attribute.NestHost attribute) {
+ output.writeShort(pool.utf8(attribute.kind().signature()));
+ output.writeInt(2);
+ output.writeShort(pool.classInfo(attribute.hostClass()));
+ }
+
+ private void writeNestMembers(ByteArrayDataOutput output, Attribute.NestMembers attribute) {
+ output.writeShort(pool.utf8(attribute.kind().signature()));
+ output.writeInt(2 + attribute.classes().size() * 2);
+ output.writeShort(attribute.classes().size());
+ for (String classes : attribute.classes()) {
+ output.writeShort(pool.classInfo(classes));
+ }
+ }
+
+ private void writeRecord(ByteArrayDataOutput output, Attribute.Record attribute) {
+ output.writeShort(pool.utf8(attribute.kind().signature()));
+ ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
+ tmp.writeShort(attribute.components().size());
+ for (Attribute.Record.Component c : attribute.components()) {
+ tmp.writeShort(pool.utf8(c.name()));
+ tmp.writeShort(pool.utf8(c.descriptor()));
+ tmp.writeShort(c.attributes().size());
+ for (Attribute a : c.attributes()) {
+ write(tmp, a);
+ }
+ }
+ byte[] data = tmp.toByteArray();
+ output.writeInt(data.length);
+ output.write(data);
+ }
+
+ private void writePermittedSubclasses(
+ ByteArrayDataOutput output, Attribute.PermittedSubclasses attribute) {
+ output.writeShort(pool.utf8(attribute.kind().signature()));
+ output.writeInt(2 + attribute.permits.size() * 2);
+ output.writeShort(attribute.permits.size());
+ for (String permits : attribute.permits) {
+ output.writeShort(pool.classInfo(permits));
+ }
+ }
+
+ private void writeTurbineTransitiveJar(
+ ByteArrayDataOutput output, TurbineTransitiveJar attribute) {
+ output.writeShort(pool.utf8(attribute.kind().signature()));
+ output.writeInt(2);
+ output.writeShort(pool.utf8(attribute.transitiveJar));
+ }
}
diff --git a/java/com/google/turbine/bytecode/ByteReader.java b/java/com/google/turbine/bytecode/ByteReader.java
index 5458b49..a9deff2 100644
--- a/java/com/google/turbine/bytecode/ByteReader.java
+++ b/java/com/google/turbine/bytecode/ByteReader.java
@@ -20,9 +20,11 @@ import static com.google.common.base.Verify.verify;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.ByteArrayInputStream;
/** A {@link ByteArrayDataInput} wrapper that tracks the current byte array index. */
+@CanIgnoreReturnValue
public class ByteReader {
private final byte[] bytes;
diff --git a/java/com/google/turbine/bytecode/ClassFile.java b/java/com/google/turbine/bytecode/ClassFile.java
index 8ee2aac..820f17d 100644
--- a/java/com/google/turbine/bytecode/ClassFile.java
+++ b/java/com/google/turbine/bytecode/ClassFile.java
@@ -26,46 +26,64 @@ import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.Map;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** A JVMS §4.1 ClassFile. */
public class ClassFile {
private final int access;
+ private final int majorVersion;
private final String name;
- private final String signature;
- private final String superClass;
+ private final @Nullable String signature;
+ private final @Nullable String superClass;
private final List<String> interfaces;
+ private final List<String> permits;
private final List<MethodInfo> methods;
private final List<FieldInfo> fields;
private final List<AnnotationInfo> annotations;
private final List<InnerClass> innerClasses;
private final ImmutableList<TypeAnnotationInfo> typeAnnotations;
- @Nullable private final ModuleInfo module;
+ private final @Nullable ModuleInfo module;
+ private final @Nullable String nestHost;
+ private final ImmutableList<String> nestMembers;
+ private final @Nullable RecordInfo record;
+ private final @Nullable String transitiveJar;
public ClassFile(
int access,
+ int majorVersion,
String name,
- String signature,
- String superClass,
+ @Nullable String signature,
+ @Nullable String superClass,
List<String> interfaces,
+ List<String> permits,
List<MethodInfo> methods,
List<FieldInfo> fields,
List<AnnotationInfo> annotations,
List<InnerClass> innerClasses,
ImmutableList<TypeAnnotationInfo> typeAnnotations,
- @Nullable ModuleInfo module) {
+ @Nullable ModuleInfo module,
+ @Nullable String nestHost,
+ ImmutableList<String> nestMembers,
+ @Nullable RecordInfo record,
+ @Nullable String transitiveJar) {
this.access = access;
+ this.majorVersion = majorVersion;
this.name = name;
this.signature = signature;
this.superClass = superClass;
this.interfaces = interfaces;
+ this.permits = permits;
this.methods = methods;
this.fields = fields;
this.annotations = annotations;
this.innerClasses = innerClasses;
this.typeAnnotations = typeAnnotations;
this.module = module;
+ this.nestHost = nestHost;
+ this.nestMembers = nestMembers;
+ this.record = record;
+ this.transitiveJar = transitiveJar;
}
/** Class access and property flags. */
@@ -73,18 +91,23 @@ public class ClassFile {
return access;
}
+ /** Class file major version. */
+ public int majorVersion() {
+ return majorVersion;
+ }
+
/** The name of the class or interface. */
public String name() {
return name;
}
/** The value of the Signature attribute. */
- public String signature() {
+ public @Nullable String signature() {
return signature;
}
/** The super class. */
- public String superName() {
+ public @Nullable String superName() {
return superClass;
}
@@ -93,6 +116,11 @@ public class ClassFile {
return interfaces;
}
+ /** The permitted direct subclasses. */
+ public List<String> permits() {
+ return permits;
+ }
+
/** Methods declared by this class or interfaces type. */
public List<MethodInfo> methods() {
return methods;
@@ -119,19 +147,35 @@ public class ClassFile {
}
/** A module attribute. */
- @Nullable
- public ModuleInfo module() {
+ public @Nullable ModuleInfo module() {
return module;
}
+ public @Nullable String nestHost() {
+ return nestHost;
+ }
+
+ public ImmutableList<String> nestMembers() {
+ return nestMembers;
+ }
+
+ public @Nullable RecordInfo record() {
+ return record;
+ }
+
+ /** The original jar of a repackaged transitive class. */
+ public @Nullable String transitiveJar() {
+ return transitiveJar;
+ }
+
/** The contents of a JVMS §4.5 field_info structure. */
public static class FieldInfo {
private final int access;
private final String name;
private final String descriptor;
- @Nullable private final String signature;
- private final Const.@Nullable Value value;
+ private final @Nullable String signature;
+ private final @Nullable Value value;
private final List<AnnotationInfo> annotations;
private final ImmutableList<TypeAnnotationInfo> typeAnnotations;
@@ -140,7 +184,7 @@ public class ClassFile {
String name,
String descriptor,
@Nullable String signature,
- Value value,
+ @Nullable Value value,
List<AnnotationInfo> annotations,
ImmutableList<TypeAnnotationInfo> typeAnnotations) {
this.access = access;
@@ -168,8 +212,7 @@ public class ClassFile {
}
/** The value of Signature attribute. */
- @Nullable
- public String signature() {
+ public @Nullable String signature() {
return signature;
}
@@ -231,7 +274,7 @@ public class ClassFile {
private final int access;
private final String name;
private final String descriptor;
- @Nullable private final String signature;
+ private final @Nullable String signature;
private final List<String> exceptions;
private final AnnotationInfo.@Nullable ElementValue defaultValue;
private final List<AnnotationInfo> annotations;
@@ -278,8 +321,7 @@ public class ClassFile {
}
/** The value of Signature attribute. */
- @Nullable
- public String signature() {
+ public @Nullable String signature() {
return signature;
}
@@ -721,16 +763,16 @@ public class ClassFile {
}
}
- private final TypePath parent;
- private final TypePath.Kind kind;
+ private final @Nullable TypePath parent;
+ private final TypePath.@Nullable Kind kind;
private final int index;
- private TypePath(TypePath.Kind kind, TypePath parent) {
+ private TypePath(TypePath.@Nullable Kind kind, @Nullable TypePath parent) {
// JVMS 4.7.20.2: type_argument_index is 0 if the bound kind is not TYPE_ARGUMENT
this(0, kind, parent);
}
- private TypePath(int index, TypePath.Kind kind, TypePath parent) {
+ private TypePath(int index, TypePath.@Nullable Kind kind, @Nullable TypePath parent) {
this.index = index;
this.kind = kind;
this.parent = parent;
@@ -743,13 +785,13 @@ public class ClassFile {
/** The JVMS 4.7.20.2-A serialized value of the type_path_kind. */
public byte tag() {
- return (byte) kind.tag;
+ return (byte) requireNonNull(kind).tag;
}
/** Returns a flattened view of the type path. */
public ImmutableList<TypePath> flatten() {
Deque<TypePath> flat = new ArrayDeque<>();
- for (TypePath curr = this; curr.kind != null; curr = curr.parent) {
+ for (TypePath curr = this; requireNonNull(curr).kind != null; curr = curr.parent) {
flat.addFirst(curr);
}
return ImmutableList.copyOf(flat);
@@ -761,7 +803,7 @@ public class ClassFile {
public static class ModuleInfo {
private final String name;
- private final String version;
+ private final @Nullable String version;
private final int flags;
private final ImmutableList<RequireInfo> requires;
private final ImmutableList<ExportInfo> exports;
@@ -772,7 +814,7 @@ public class ClassFile {
public ModuleInfo(
String name,
int flags,
- String version,
+ @Nullable String version,
ImmutableList<RequireInfo> requires,
ImmutableList<ExportInfo> exports,
ImmutableList<OpenInfo> opens,
@@ -796,7 +838,7 @@ public class ClassFile {
return flags;
}
- public String version() {
+ public @Nullable String version() {
return version;
}
@@ -825,9 +867,9 @@ public class ClassFile {
private final String moduleName;
private final int flags;
- private final String version;
+ private final @Nullable String version;
- public RequireInfo(String moduleName, int flags, String version) {
+ public RequireInfo(String moduleName, int flags, @Nullable String version) {
this.moduleName = moduleName;
this.flags = flags;
this.version = version;
@@ -841,7 +883,7 @@ public class ClassFile {
return flags;
}
- public String version() {
+ public @Nullable String version() {
return version;
}
}
@@ -932,4 +974,61 @@ public class ClassFile {
}
}
}
+
+ /** A JVMS §4.7.30 Record attribute. */
+ public static class RecordInfo {
+
+ /** A JVMS §4.7.30 Record component attribute. */
+ public static class RecordComponentInfo {
+
+ private final String name;
+ private final String descriptor;
+ private final @Nullable String signature;
+ private final ImmutableList<AnnotationInfo> annotations;
+ private final ImmutableList<TypeAnnotationInfo> typeAnnotations;
+
+ public RecordComponentInfo(
+ String name,
+ String descriptor,
+ @Nullable String signature,
+ ImmutableList<AnnotationInfo> annotations,
+ ImmutableList<TypeAnnotationInfo> typeAnnotations) {
+ this.name = name;
+ this.descriptor = descriptor;
+ this.signature = signature;
+ this.annotations = annotations;
+ this.typeAnnotations = typeAnnotations;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public String descriptor() {
+ return descriptor;
+ }
+
+ public @Nullable String signature() {
+ return signature;
+ }
+
+ public ImmutableList<AnnotationInfo> annotations() {
+ return annotations;
+ }
+
+ public ImmutableList<TypeAnnotationInfo> typeAnnotations() {
+ return typeAnnotations;
+ }
+ }
+
+ public RecordInfo(ImmutableList<RecordComponentInfo> recordComponents) {
+ this.recordComponents = recordComponents;
+ }
+
+ private final ImmutableList<RecordComponentInfo> recordComponents;
+
+ public ImmutableList<RecordComponentInfo> recordComponents() {
+ return recordComponents;
+ }
+ }
}
diff --git a/java/com/google/turbine/bytecode/ClassReader.java b/java/com/google/turbine/bytecode/ClassReader.java
index 9c79b42..740026a 100644
--- a/java/com/google/turbine/bytecode/ClassReader.java
+++ b/java/com/google/turbine/bytecode/ClassReader.java
@@ -16,6 +16,8 @@
package com.google.turbine.bytecode;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CheckReturnValue;
@@ -37,7 +39,7 @@ import com.google.turbine.model.Const;
import com.google.turbine.model.TurbineFlag;
import java.util.ArrayList;
import java.util.List;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** A JVMS §4 class file reader. */
public class ClassReader {
@@ -53,7 +55,7 @@ public class ClassReader {
return new ClassReader(path, bytes).read();
}
- @Nullable private final String path;
+ private final @Nullable String path;
private final ByteReader reader;
private ClassReader(@Nullable String path, byte[] bytes) {
@@ -106,6 +108,7 @@ public class ClassReader {
List<ClassFile.InnerClass> innerclasses = ImmutableList.of();
ImmutableList.Builder<ClassFile.AnnotationInfo> annotations = ImmutableList.builder();
ClassFile.ModuleInfo module = null;
+ String transitiveJar = null;
int attributesCount = reader.u2();
for (int j = 0; j < attributesCount; j++) {
int attributeNameIndex = reader.u2();
@@ -124,6 +127,9 @@ public class ClassReader {
case "Module":
module = readModule(constantPool);
break;
+ case "TurbineTransitiveJar":
+ transitiveJar = readTurbineTransitiveJar(constantPool);
+ break;
default:
reader.skip(reader.u4());
break;
@@ -132,16 +138,22 @@ public class ClassReader {
return new ClassFile(
accessFlags,
+ majorVersion,
thisClass,
signature,
superClass,
interfaces,
+ /* permits= */ ImmutableList.of(),
methodinfos,
fieldinfos,
annotations.build(),
innerclasses,
ImmutableList.of(),
- module);
+ module,
+ /* nestHost= */ null,
+ /* nestMembers= */ ImmutableList.of(),
+ /* record= */ null,
+ transitiveJar);
}
/** Reads a JVMS 4.7.9 Signature attribute. */
@@ -168,6 +180,7 @@ public class ClassReader {
String innerName = innerNameIndex != 0 ? constantPool.utf8(innerNameIndex) : null;
int innerClassAccessFlags = reader.u2();
if (innerName != null && (thisClass.equals(innerClass) || thisClass.equals(outerClass))) {
+ requireNonNull(outerClass);
innerclasses.add(
new ClassFile.InnerClass(innerClass, outerClass, innerName, innerClassAccessFlags));
}
@@ -322,18 +335,18 @@ public class ClassReader {
// The runtimeVisible bit in AnnotationInfo is only used during lowering; earlier passes
// read the meta-annotations.
/* runtimeVisible= */ false,
- values.build());
+ values.buildOrThrow());
}
private ElementValue readElementValue(ConstantPoolReader constantPool) {
int tag = reader.u1();
switch (tag) {
case 'B':
- return new ConstValue(readConst(constantPool).asByte());
+ return new ConstValue(new Const.ByteValue((byte) readInt(constantPool)));
case 'C':
- return new ConstValue(readConst(constantPool).asChar());
+ return new ConstValue(new Const.CharValue((char) readInt(constantPool)));
case 'S':
- return new ConstValue(readConst(constantPool).asShort());
+ return new ConstValue(new Const.ShortValue((short) readInt(constantPool)));
case 'D':
case 'F':
case 'I':
@@ -341,11 +354,8 @@ public class ClassReader {
case 's':
return new ConstValue(readConst(constantPool));
case 'Z':
- {
- Const.Value value = readConst(constantPool);
- // boolean constants are encoded as integers
- return new ConstValue(new Const.BooleanValue(value.asInteger().value() != 0));
- }
+ // boolean constants are encoded as integers
+ return new ConstValue(new Const.BooleanValue(readInt(constantPool) != 0));
case 'e':
{
int typeNameIndex = reader.u2();
@@ -376,6 +386,10 @@ public class ClassReader {
throw new AssertionError(String.format("bad tag value %c", tag));
}
+ private int readInt(ConstantPoolReader constantPool) {
+ return ((Const.IntValue) readConst(constantPool)).value();
+ }
+
private Const.Value readConst(ConstantPoolReader constantPool) {
int constValueIndex = reader.u2();
return constantPool.constant(constValueIndex);
@@ -509,4 +523,9 @@ public class ClassReader {
}
return fields;
}
+
+ private String readTurbineTransitiveJar(ConstantPoolReader constantPool) {
+ reader.u4(); // length
+ return constantPool.utf8(reader.u2());
+ }
}
diff --git a/java/com/google/turbine/bytecode/ClassWriter.java b/java/com/google/turbine/bytecode/ClassWriter.java
index c3490ca..da4afc7 100644
--- a/java/com/google/turbine/bytecode/ClassWriter.java
+++ b/java/com/google/turbine/bytecode/ClassWriter.java
@@ -27,14 +27,10 @@ import com.google.turbine.model.Const.Value;
import java.util.List;
/** Class file writing. */
-public class ClassWriter {
+public final class ClassWriter {
private static final int MAGIC = 0xcafebabe;
private static final int MINOR_VERSION = 0;
- // use the lowest classfile version possible given the class file features
- // TODO(cushon): is there a reason to support --release?
- private static final int MAJOR_VERSION = 52;
- private static final int MODULE_MAJOR_VERSION = 53;
/** Writes a {@link ClassFile} to bytecode. */
public static byte[] writeClass(ClassFile classfile) {
@@ -79,7 +75,7 @@ public class ClassWriter {
ConstantPool pool, ByteArrayDataOutput body, List<Attribute> attributes) {
body.writeShort(attributes.size());
for (Attribute attribute : attributes) {
- new AttributeWriter(pool, body).write(attribute);
+ new AttributeWriter(pool).write(body, attribute);
}
}
@@ -119,9 +115,11 @@ public class ClassWriter {
ByteArrayDataOutput result = ByteStreams.newDataOutput();
result.writeInt(MAGIC);
result.writeShort(MINOR_VERSION);
- result.writeShort(classfile.module() != null ? MODULE_MAJOR_VERSION : MAJOR_VERSION);
+ result.writeShort(classfile.majorVersion());
writeConstantPool(pool, result);
result.write(body.toByteArray());
return result.toByteArray();
}
+
+ private ClassWriter() {}
}
diff --git a/java/com/google/turbine/bytecode/ConstantPoolReader.java b/java/com/google/turbine/bytecode/ConstantPoolReader.java
index ffcb4c3..d00ee22 100644
--- a/java/com/google/turbine/bytecode/ConstantPoolReader.java
+++ b/java/com/google/turbine/bytecode/ConstantPoolReader.java
@@ -36,6 +36,7 @@ public class ConstantPoolReader {
static final int CONSTANT_UTF8 = 1;
static final int CONSTANT_METHOD_HANDLE = 15;
static final int CONSTANT_METHOD_TYPE = 16;
+ static final int CONSTANT_DYNAMIC = 17;
static final int CONSTANT_INVOKE_DYNAMIC = 18;
static final int CONSTANT_MODULE = 19;
static final int CONSTANT_PACKAGE = 20;
@@ -88,6 +89,7 @@ public class ConstantPoolReader {
case CONSTANT_INTEGER:
case CONSTANT_FLOAT:
case CONSTANT_NAME_AND_TYPE:
+ case CONSTANT_DYNAMIC:
case CONSTANT_INVOKE_DYNAMIC:
reader.skip(4);
return 1;
diff --git a/java/com/google/turbine/bytecode/LowerAttributes.java b/java/com/google/turbine/bytecode/LowerAttributes.java
index 67ef2b4..8952dff 100644
--- a/java/com/google/turbine/bytecode/LowerAttributes.java
+++ b/java/com/google/turbine/bytecode/LowerAttributes.java
@@ -29,7 +29,7 @@ import java.util.ArrayList;
import java.util.List;
/** Lower information in {@link ClassFile} structures to attributes. */
-public class LowerAttributes {
+public final class LowerAttributes {
/** Collects the {@link Attribute}s for a {@link ClassFile}. */
static List<Attribute> classAttributes(ClassFile classfile) {
@@ -45,9 +45,39 @@ public class LowerAttributes {
if (classfile.module() != null) {
attributes.add(new Attribute.Module(classfile.module()));
}
+ if (classfile.nestHost() != null) {
+ attributes.add(new Attribute.NestHost(classfile.nestHost()));
+ }
+ if (!classfile.nestMembers().isEmpty()) {
+ attributes.add(new Attribute.NestMembers(classfile.nestMembers()));
+ }
+ if (classfile.record() != null) {
+ attributes.add(recordAttribute(classfile.record()));
+ }
+ if (!classfile.permits().isEmpty()) {
+ attributes.add(new Attribute.PermittedSubclasses(classfile.permits()));
+ }
+ if (classfile.transitiveJar() != null) {
+ attributes.add(new Attribute.TurbineTransitiveJar(classfile.transitiveJar()));
+ }
return attributes;
}
+ private static Attribute recordAttribute(ClassFile.RecordInfo record) {
+ ImmutableList.Builder<Attribute.Record.Component> components = ImmutableList.builder();
+ for (ClassFile.RecordInfo.RecordComponentInfo component : record.recordComponents()) {
+ List<Attribute> attributes = new ArrayList<>();
+ if (component.signature() != null) {
+ attributes.add(new Attribute.Signature(component.signature()));
+ }
+ addAllAnnotations(attributes, component.annotations());
+ addAllTypeAnnotations(attributes, component.typeAnnotations());
+ components.add(
+ new Attribute.Record.Component(component.name(), component.descriptor(), attributes));
+ }
+ return new Attribute.Record(components.build());
+ }
+
/** Collects the {@link Attribute}s for a {@link MethodInfo}. */
static List<Attribute> methodAttributes(ClassFile.MethodInfo method) {
List<Attribute> attributes = new ArrayList<>();
@@ -146,4 +176,6 @@ public class LowerAttributes {
attributes.add(new Attribute.RuntimeInvisibleParameterAnnotations(invisibles));
}
}
+
+ private LowerAttributes() {}
}
diff --git a/java/com/google/turbine/bytecode/package-info.java b/java/com/google/turbine/bytecode/package-info.java
new file mode 100644
index 0000000..3f0bb60
--- /dev/null
+++ b/java/com/google/turbine/bytecode/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+@org.jspecify.nullness.NullMarked
+package com.google.turbine.bytecode;
diff --git a/java/com/google/turbine/bytecode/sig/Sig.java b/java/com/google/turbine/bytecode/sig/Sig.java
index e85740f..99d98cf 100644
--- a/java/com/google/turbine/bytecode/sig/Sig.java
+++ b/java/com/google/turbine/bytecode/sig/Sig.java
@@ -18,10 +18,10 @@ package com.google.turbine.bytecode.sig;
import com.google.common.collect.ImmutableList;
import com.google.turbine.model.TurbineConstantTypeKind;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** JVMS 4.7.9.1 signatures. */
-public class Sig {
+public final class Sig {
/** A JVMS 4.7.9.1 ClassSignature. */
public static class ClassSig {
@@ -59,18 +59,18 @@ public class Sig {
public static class TyParamSig {
private final String name;
- @Nullable private final TySig classBound;
+ private final @Nullable TySig classBound;
private final ImmutableList<TySig> interfaceBounds;
- public TyParamSig(String name, TySig classBound, ImmutableList<TySig> interfaceBounds) {
+ public TyParamSig(
+ String name, @Nullable TySig classBound, ImmutableList<TySig> interfaceBounds) {
this.name = name;
this.classBound = classBound;
this.interfaceBounds = interfaceBounds;
}
/** A single class upper-bound, or {@code null}. */
- @Nullable
- public TySig classBound() {
+ public @Nullable TySig classBound() {
return classBound;
}
@@ -343,4 +343,6 @@ public class Sig {
return exceptions;
}
}
+
+ private Sig() {}
}
diff --git a/java/com/google/turbine/bytecode/sig/SigParser.java b/java/com/google/turbine/bytecode/sig/SigParser.java
index 033fa18..1bfd762 100644
--- a/java/com/google/turbine/bytecode/sig/SigParser.java
+++ b/java/com/google/turbine/bytecode/sig/SigParser.java
@@ -17,6 +17,7 @@
package com.google.turbine.bytecode.sig;
import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.turbine.bytecode.sig.Sig.ArrayTySig;
import com.google.turbine.bytecode.sig.Sig.BaseTySig;
import com.google.turbine.bytecode.sig.Sig.ClassSig;
@@ -46,6 +47,7 @@ public class SigParser {
}
/** Returns the next character and advances. */
+ @CanIgnoreReturnValue
char eat() {
return sig.charAt(idx++);
}
diff --git a/java/com/google/turbine/bytecode/sig/package-info.java b/java/com/google/turbine/bytecode/sig/package-info.java
new file mode 100644
index 0000000..c5f449e
--- /dev/null
+++ b/java/com/google/turbine/bytecode/sig/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+@org.jspecify.nullness.NullMarked
+package com.google.turbine.bytecode.sig;
diff --git a/java/com/google/turbine/deps/Dependencies.java b/java/com/google/turbine/deps/Dependencies.java
index 92193e8..3dd008c 100644
--- a/java/com/google/turbine/deps/Dependencies.java
+++ b/java/com/google/turbine/deps/Dependencies.java
@@ -51,7 +51,7 @@ import java.util.Optional;
import java.util.Set;
/** Support for Bazel jdeps dependency output. */
-public class Dependencies {
+public final class Dependencies {
/** Creates a jdeps proto for the current compilation. */
public static DepsProto.Dependencies collectDeps(
Optional<String> targetLabel, ClassPath bootclasspath, BindingResult bound, Lowered lowered) {
@@ -92,7 +92,7 @@ public class Dependencies {
.append(bound.classPathEnv());
Set<ClassSymbol> closure = new LinkedHashSet<>(lowered.symbols());
for (ClassSymbol sym : lowered.symbols()) {
- TypeBoundClass info = env.get(sym);
+ TypeBoundClass info = env.getNonNull(sym);
addAnnotations(closure, info.annotations());
for (MethodInfo method : info.methods()) {
addAnnotations(closure, method.annotations());
@@ -219,4 +219,6 @@ public class Dependencies {
// preserve the order of entries in the transitive classpath
return Collections2.filter(transitiveClasspath, Predicates.in(reduced));
}
+
+ private Dependencies() {}
}
diff --git a/java/com/google/turbine/deps/Transitive.java b/java/com/google/turbine/deps/Transitive.java
index 8b0d44d..2b8bd58 100644
--- a/java/com/google/turbine/deps/Transitive.java
+++ b/java/com/google/turbine/deps/Transitive.java
@@ -33,13 +33,14 @@ import com.google.turbine.bytecode.ClassWriter;
import com.google.turbine.model.TurbineFlag;
import java.util.LinkedHashSet;
import java.util.Set;
+import org.jspecify.nullness.Nullable;
/**
* Collects the minimal compile-time API for symbols in the supertype closure of compiled classes.
* This allows header compilations to be performed against a classpath containing only direct
* dependencies and no transitive dependencies.
*/
-public class Transitive {
+public final class Transitive {
public static ImmutableMap<String, byte[]> collectDeps(
ClassPath bootClassPath, BindingResult bound) {
@@ -54,15 +55,16 @@ public class Transitive {
// don't export symbols loaded from the bootclasspath
continue;
}
- transitive.put(sym.binaryName(), ClassWriter.writeClass(trimClass(info.classFile())));
+ transitive.put(
+ sym.binaryName(), ClassWriter.writeClass(trimClass(info.classFile(), info.jarFile())));
}
- return transitive.build();
+ return transitive.buildOrThrow();
}
/**
* Removes information from repackaged classes that will not be needed by upstream compilations.
*/
- public static ClassFile trimClass(ClassFile cf) {
+ public static ClassFile trimClass(ClassFile cf, @Nullable String jarFile) {
// drop non-constant fields
ImmutableList.Builder<FieldInfo> fields = ImmutableList.builder();
for (FieldInfo f : cf.fields()) {
@@ -80,12 +82,20 @@ public class Transitive {
innerClasses.add(i);
}
}
+ // Include the original jar file name when repackaging transitive deps. If the same transitive
+ // dep is repackaged more than once, keep the original name.
+ String transitiveJar = cf.transitiveJar();
+ if (transitiveJar == null) {
+ transitiveJar = jarFile;
+ }
return new ClassFile(
cf.access(),
+ cf.majorVersion(),
cf.name(),
cf.signature(),
cf.superName(),
cf.interfaces(),
+ cf.permits(),
// drop methods, except for annotations where we need to resolve key/value information
(cf.access() & TurbineFlag.ACC_ANNOTATION) == TurbineFlag.ACC_ANNOTATION
? cf.methods()
@@ -96,7 +106,11 @@ public class Transitive {
cf.annotations(),
innerClasses.build(),
cf.typeAnnotations(),
- /* module= */ null);
+ /* module= */ null,
+ /* nestHost= */ null,
+ /* nestMembers= */ ImmutableList.of(),
+ /* record= */ null,
+ /* transitiveJar = */ transitiveJar);
}
private static Set<ClassSymbol> superClosure(BindingResult bound) {
@@ -134,4 +148,6 @@ public class Transitive {
addSuperTypes(closure, env, i);
}
}
+
+ private Transitive() {}
}
diff --git a/java/com/google/turbine/deps/package-info.java b/java/com/google/turbine/deps/package-info.java
new file mode 100644
index 0000000..28df5a9
--- /dev/null
+++ b/java/com/google/turbine/deps/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+package com.google.turbine.deps;
diff --git a/java/com/google/turbine/diag/LineMap.java b/java/com/google/turbine/diag/LineMap.java
index 7a39aba..37d055b 100644
--- a/java/com/google/turbine/diag/LineMap.java
+++ b/java/com/google/turbine/diag/LineMap.java
@@ -17,6 +17,7 @@
package com.google.turbine.diag;
import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableRangeMap;
import com.google.common.collect.Range;
@@ -64,19 +65,22 @@ public class LineMap {
/** The zero-indexed column number of the given source position. */
public int column(int position) {
checkArgument(0 <= position && position < source.length(), "%s", position);
- return position - lines.getEntry(position).getKey().lowerEndpoint();
+ // requireNonNull is safe because `lines` covers the whole file length.
+ return position - requireNonNull(lines.getEntry(position)).getKey().lowerEndpoint();
}
/** The one-indexed line number of the given source position. */
public int lineNumber(int position) {
checkArgument(0 <= position && position < source.length(), "%s", position);
- return lines.get(position);
+ // requireNonNull is safe because `lines` covers the whole file length.
+ return requireNonNull(lines.get(position));
}
/** The one-indexed line of the given source position. */
public String line(int position) {
checkArgument(0 <= position && position < source.length(), "%s", position);
- Range<Integer> range = lines.getEntry(position).getKey();
+ // requireNonNull is safe because `lines` covers the whole file length.
+ Range<Integer> range = requireNonNull(lines.getEntry(position)).getKey();
return source.substring(range.lowerEndpoint(), range.upperEndpoint());
}
}
diff --git a/java/com/google/turbine/diag/SourceFile.java b/java/com/google/turbine/diag/SourceFile.java
index 3868252..a7c3245 100644
--- a/java/com/google/turbine/diag/SourceFile.java
+++ b/java/com/google/turbine/diag/SourceFile.java
@@ -19,6 +19,7 @@ package com.google.turbine.diag;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import java.util.Objects;
+import org.jspecify.nullness.Nullable;
/** A source file. */
public class SourceFile {
@@ -55,7 +56,7 @@ public class SourceFile {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof SourceFile)) {
return false;
}
diff --git a/java/com/google/turbine/diag/TurbineDiagnostic.java b/java/com/google/turbine/diag/TurbineDiagnostic.java
index ccbaa7f..1457868 100644
--- a/java/com/google/turbine/diag/TurbineDiagnostic.java
+++ b/java/com/google/turbine/diag/TurbineDiagnostic.java
@@ -27,7 +27,7 @@ import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.diag.TurbineError.ErrorKind;
import java.util.Objects;
import javax.tools.Diagnostic;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** A compilation error. */
public class TurbineDiagnostic {
@@ -64,6 +64,10 @@ public class TurbineDiagnostic {
return severity;
}
+ boolean isError() {
+ return severity.equals(Diagnostic.Kind.ERROR);
+ }
+
/** The diagnostic message. */
public String diagnostic() {
StringBuilder sb = new StringBuilder(path());
@@ -71,8 +75,9 @@ public class TurbineDiagnostic {
sb.append(':').append(line());
}
sb.append(": error: ");
- sb.append(message().trim()).append(System.lineSeparator());
+ sb.append(message()).append(System.lineSeparator());
if (line() != -1 && column() != -1) {
+ requireNonNull(source); // line and column imply source is non-null
sb.append(CharMatcher.breakingWhitespace().trimTrailingFrom(source.lineMap().line(position)))
.append(System.lineSeparator());
sb.append(Strings.repeat(" ", column() - 1)).append('^');
@@ -139,7 +144,7 @@ public class TurbineDiagnostic {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof TurbineDiagnostic)) {
return false;
}
@@ -155,10 +160,12 @@ public class TurbineDiagnostic {
return source != null && source.path() != null ? source.path() : "<>";
}
+ @SuppressWarnings("nullness") // position != -1 implies source is non-null
public int line() {
return position != -1 ? source.lineMap().lineNumber(position) : -1;
}
+ @SuppressWarnings("nullness") // position != -1 implies source is non-null
public int column() {
return position != -1 ? source.lineMap().column(position) + 1 : -1;
}
diff --git a/java/com/google/turbine/diag/TurbineError.java b/java/com/google/turbine/diag/TurbineError.java
index 39244b5..f839345 100644
--- a/java/com/google/turbine/diag/TurbineError.java
+++ b/java/com/google/turbine/diag/TurbineError.java
@@ -26,15 +26,17 @@ public class TurbineError extends Error {
/** A diagnostic kind. */
public enum ErrorKind {
- UNEXPECTED_INPUT("unexpected input: %c"),
+ UNEXPECTED_INPUT("unexpected input: %s"),
UNEXPECTED_IDENTIFIER("unexpected identifier '%s'"),
UNEXPECTED_EOF("unexpected end of input"),
UNTERMINATED_STRING("unterminated string literal"),
UNTERMINATED_CHARACTER_LITERAL("unterminated char literal"),
+ UNPAIRED_SURROGATE("unpaired surrogate 0x%x"),
UNTERMINATED_EXPRESSION("unterminated expression, expected ';' not found"),
INVALID_UNICODE("illegal unicode escape"),
EMPTY_CHARACTER_LITERAL("empty char literal"),
EXPECTED_TOKEN("expected token %s"),
+ EXTENDS_AFTER_IMPLEMENTS("'extends' must come before 'implements'"),
INVALID_LITERAL("invalid literal: %s"),
UNEXPECTED_TYPE_PARAMETER("unexpected type parameter %s"),
SYMBOL_NOT_FOUND("symbol not found %s"),
@@ -42,15 +44,20 @@ public class TurbineError extends Error {
TYPE_PARAMETER_QUALIFIER("type parameter used as type qualifier"),
UNEXPECTED_TOKEN("unexpected token: %s"),
INVALID_ANNOTATION_ARGUMENT("invalid annotation argument"),
+ MISSING_ANNOTATION_ARGUMENT("missing required annotation argument: %s"),
CANNOT_RESOLVE("could not resolve %s"),
EXPRESSION_ERROR("could not evaluate constant expression"),
OPERAND_TYPE("bad operand type %s"),
+ TYPE_CONVERSION("value %s of type %s cannot be converted to %s"),
CYCLIC_HIERARCHY("cycle in class hierarchy: %s"),
NOT_AN_ANNOTATION("%s is not an annotation"),
+ ANNOTATION_VALUE_NAME("expected an annotation value of the form name=value"),
NONREPEATABLE_ANNOTATION("%s is not @Repeatable"),
DUPLICATE_DECLARATION("duplicate declaration of %s"),
BAD_MODULE_INFO("unexpected declaration found in module-info"),
UNCLOSED_COMMENT("unclosed comment"),
+ UNEXPECTED_TYPE("unexpected type %s"),
+ UNEXPECTED_MODIFIER("unexpected modifier: %s"),
PROC("%s");
private final String message;
diff --git a/java/com/google/turbine/diag/TurbineLog.java b/java/com/google/turbine/diag/TurbineLog.java
index b336e25..565b9ea 100644
--- a/java/com/google/turbine/diag/TurbineLog.java
+++ b/java/com/google/turbine/diag/TurbineLog.java
@@ -18,7 +18,6 @@ package com.google.turbine.diag;
import com.google.common.collect.ImmutableList;
import com.google.turbine.diag.TurbineError.ErrorKind;
-import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.tools.Diagnostic;
@@ -26,20 +25,24 @@ import javax.tools.Diagnostic;
/** A log that collects diagnostics. */
public class TurbineLog {
- private final Set<TurbineDiagnostic> errors = new LinkedHashSet<>();
+ private final Set<TurbineDiagnostic> diagnostics = new LinkedHashSet<>();
public TurbineLogWithSource withSource(SourceFile source) {
return new TurbineLogWithSource(source);
}
+ public ImmutableList<TurbineDiagnostic> diagnostics() {
+ return ImmutableList.copyOf(diagnostics);
+ }
+
public void maybeThrow() {
if (anyErrors()) {
- throw new TurbineError(ImmutableList.copyOf(errors));
+ throw new TurbineError(diagnostics());
}
}
- private boolean anyErrors() {
- for (TurbineDiagnostic error : errors) {
+ public boolean anyErrors() {
+ for (TurbineDiagnostic error : diagnostics) {
if (error.severity().equals(Diagnostic.Kind.ERROR)) {
return true;
}
@@ -55,7 +58,7 @@ public class TurbineLog {
* code generated in later processing rounds.
*/
public boolean errorRaised() {
- for (TurbineDiagnostic error : errors) {
+ for (TurbineDiagnostic error : diagnostics) {
if (error.kind().equals(ErrorKind.PROC) && error.severity().equals(Diagnostic.Kind.ERROR)) {
return true;
}
@@ -65,17 +68,12 @@ public class TurbineLog {
/** Reset the log between annotation processing rounds. */
public void clear() {
- Iterator<TurbineDiagnostic> it = errors.iterator();
- while (it.hasNext()) {
- if (it.next().severity().equals(Diagnostic.Kind.ERROR)) {
- it.remove();
- }
- }
+ diagnostics.removeIf(TurbineDiagnostic::isError);
}
/** Reports an annotation processing diagnostic with no position information. */
public void diagnostic(Diagnostic.Kind severity, String message) {
- errors.add(TurbineDiagnostic.format(severity, ErrorKind.PROC, message));
+ diagnostics.add(TurbineDiagnostic.format(severity, ErrorKind.PROC, message));
}
/** A log for a specific source file. */
@@ -88,7 +86,7 @@ public class TurbineLog {
}
public void diagnostic(Diagnostic.Kind severity, int position, ErrorKind kind, Object... args) {
- errors.add(TurbineDiagnostic.format(severity, source, position, kind, args));
+ diagnostics.add(TurbineDiagnostic.format(severity, source, position, kind, args));
}
public void error(int position, ErrorKind kind, Object... args) {
diff --git a/java/com/google/turbine/diag/package-info.java b/java/com/google/turbine/diag/package-info.java
new file mode 100644
index 0000000..f19a95c
--- /dev/null
+++ b/java/com/google/turbine/diag/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+package com.google.turbine.diag;
diff --git a/java/com/google/turbine/lower/Lower.java b/java/com/google/turbine/lower/Lower.java
index 0f7bb90..362316d 100644
--- a/java/com/google/turbine/lower/Lower.java
+++ b/java/com/google/turbine/lower/Lower.java
@@ -18,6 +18,8 @@ package com.google.turbine.lower;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.turbine.binder.DisambiguateTypeAnnotations.groupRepeated;
+import static java.lang.Math.max;
+import static java.util.Objects.requireNonNull;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
@@ -37,6 +39,7 @@ import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo;
import com.google.turbine.binder.bound.TypeBoundClass.ParamInfo;
+import com.google.turbine.binder.bound.TypeBoundClass.RecordComponentInfo;
import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo;
import com.google.turbine.binder.bytecode.BytecodeBoundClass;
import com.google.turbine.binder.env.CompoundEnv;
@@ -66,6 +69,7 @@ import com.google.turbine.model.Const;
import com.google.turbine.model.TurbineFlag;
import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.model.TurbineVisibility;
+import com.google.turbine.options.LanguageVersion;
import com.google.turbine.type.AnnoInfo;
import com.google.turbine.type.Type;
import com.google.turbine.type.Type.ArrayTy;
@@ -83,7 +87,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** Lowering from bound classes to bytecode. */
public class Lower {
@@ -111,6 +115,7 @@ public class Lower {
/** Lowers all given classes to bytecode. */
public static Lowered lowerAll(
+ LanguageVersion languageVersion,
ImmutableMap<ClassSymbol, SourceTypeBoundClass> units,
ImmutableList<SourceModuleInfo> modules,
Env<ClassSymbol, BytecodeBoundClass> classpath) {
@@ -118,20 +123,24 @@ public class Lower {
CompoundEnv.<ClassSymbol, TypeBoundClass>of(classpath).append(new SimpleEnv<>(units));
ImmutableMap.Builder<String, byte[]> result = ImmutableMap.builder();
Set<ClassSymbol> symbols = new LinkedHashSet<>();
+ // Output Java 8 bytecode at minimum, for type annotations
+ int majorVersion = max(languageVersion.majorVersion(), 52);
for (ClassSymbol sym : units.keySet()) {
- result.put(sym.binaryName(), lower(units.get(sym), env, sym, symbols));
+ result.put(sym.binaryName(), lower(units.get(sym), env, sym, symbols, majorVersion));
}
if (modules.size() == 1) {
// single module mode: the module-info.class file is at the root
- result.put("module-info", lower(getOnlyElement(modules), env, symbols));
+ result.put("module-info", lower(getOnlyElement(modules), env, symbols, majorVersion));
} else {
// multi-module mode: the output module-info.class are in a directory corresponding to their
// package
for (SourceModuleInfo module : modules) {
- result.put(module.name().replace('.', '/') + "/module-info", lower(module, env, symbols));
+ result.put(
+ module.name().replace('.', '/') + "/module-info",
+ lower(module, env, symbols, majorVersion));
}
}
- return new Lowered(result.build(), ImmutableSet.copyOf(symbols));
+ return new Lowered(result.buildOrThrow(), ImmutableSet.copyOf(symbols));
}
/** Lowers a class to bytecode. */
@@ -139,15 +148,17 @@ public class Lower {
SourceTypeBoundClass info,
Env<ClassSymbol, TypeBoundClass> env,
ClassSymbol sym,
- Set<ClassSymbol> symbols) {
- return new Lower(env).lower(info, sym, symbols);
+ Set<ClassSymbol> symbols,
+ int majorVersion) {
+ return new Lower(env).lower(info, sym, symbols, majorVersion);
}
private static byte[] lower(
SourceModuleInfo module,
CompoundEnv<ClassSymbol, TypeBoundClass> env,
- Set<ClassSymbol> symbols) {
- return new Lower(env).lower(module, symbols);
+ Set<ClassSymbol> symbols,
+ int majorVersion) {
+ return new Lower(env).lower(module, symbols, majorVersion);
}
private final LowerSignature sig = new LowerSignature();
@@ -157,7 +168,7 @@ public class Lower {
this.env = env;
}
- private byte[] lower(SourceModuleInfo module, Set<ClassSymbol> symbols) {
+ private byte[] lower(SourceModuleInfo module, Set<ClassSymbol> symbols, int majorVersion) {
String name = "module-info";
ImmutableList<AnnotationInfo> annotations = lowerAnnotations(module.annos());
ClassFile.ModuleInfo moduleInfo = lowerModule(module);
@@ -176,16 +187,22 @@ public class Lower {
ClassFile classfile =
new ClassFile(
/* access= */ TurbineFlag.ACC_MODULE,
+ majorVersion,
name,
/* signature= */ null,
/* superClass= */ null,
/* interfaces= */ ImmutableList.of(),
+ /* permits= */ ImmutableList.of(),
/* methods= */ ImmutableList.of(),
/* fields= */ ImmutableList.of(),
annotations,
innerClasses.build(),
/* typeAnnotations= */ ImmutableList.of(),
- moduleInfo);
+ moduleInfo,
+ /* nestHost= */ null,
+ /* nestMembers= */ ImmutableList.of(),
+ /* record= */ null,
+ /* transitiveJar= */ null);
symbols.addAll(sig.classes);
return ClassWriter.writeClass(classfile);
}
@@ -233,7 +250,8 @@ public class Lower {
provides.build());
}
- private byte[] lower(SourceTypeBoundClass info, ClassSymbol sym, Set<ClassSymbol> symbols) {
+ private byte[] lower(
+ SourceTypeBoundClass info, ClassSymbol sym, Set<ClassSymbol> symbols, int majorVersion) {
int access = classAccess(info);
String name = sig.descriptor(sym);
String signature = sig.classSignature(info, env);
@@ -242,6 +260,20 @@ public class Lower {
for (ClassSymbol i : info.interfaces()) {
interfaces.add(sig.descriptor(i));
}
+ List<String> permits = new ArrayList<>();
+ for (ClassSymbol i : info.permits()) {
+ permits.add(sig.descriptor(i));
+ }
+
+ ClassFile.RecordInfo record = null;
+ if (info.kind().equals(TurbineTyKind.RECORD)) {
+ ImmutableList.Builder<ClassFile.RecordInfo.RecordComponentInfo> components =
+ ImmutableList.builder();
+ for (RecordComponentInfo component : info.components()) {
+ components.add(lowerComponent(info, component));
+ }
+ record = new ClassFile.RecordInfo(components.build());
+ }
List<ClassFile.MethodInfo> methods = new ArrayList<>();
for (MethodInfo m : info.methods()) {
@@ -265,27 +297,53 @@ public class Lower {
ImmutableList<TypeAnnotationInfo> typeAnnotations = classTypeAnnotations(info);
+ String nestHost = null;
+ ImmutableList<String> nestMembers = ImmutableList.of();
+ // nests were added in Java 11, i.e. major version 55
+ if (majorVersion >= 55) {
+ nestHost = collectNestHost(info.source(), info.owner());
+ nestMembers = nestHost == null ? collectNestMembers(info.source(), info) : ImmutableList.of();
+ }
+
ImmutableList<ClassFile.InnerClass> inners = collectInnerClasses(info.source(), sym, info);
ClassFile classfile =
new ClassFile(
access,
+ majorVersion,
name,
signature,
superName,
interfaces,
+ permits,
methods,
fields.build(),
annotations,
inners,
typeAnnotations,
- /* module= */ null);
+ /* module= */ null,
+ nestHost,
+ nestMembers,
+ record,
+ /* transitiveJar= */ null);
symbols.addAll(sig.classes);
return ClassWriter.writeClass(classfile);
}
+ private ClassFile.RecordInfo.RecordComponentInfo lowerComponent(
+ SourceTypeBoundClass info, RecordComponentInfo c) {
+ Function<TyVarSymbol, TyVarInfo> tenv = new TyVarEnv(info.typeParameterTypes());
+ String desc = SigWriter.type(sig.signature(Erasure.erase(c.type(), tenv)));
+ String signature = sig.fieldSignature(c.type());
+ ImmutableList.Builder<TypeAnnotationInfo> typeAnnotations = ImmutableList.builder();
+ lowerTypeAnnotations(
+ typeAnnotations, c.type(), TargetType.FIELD, TypeAnnotationInfo.EMPTY_TARGET);
+ return new ClassFile.RecordInfo.RecordComponentInfo(
+ c.name(), desc, signature, lowerAnnotations(c.annotations()), typeAnnotations.build());
+ }
+
private ClassFile.MethodInfo lowerMethod(final MethodInfo m, final ClassSymbol sym) {
int access = m.access();
Function<TyVarSymbol, TyVarInfo> tenv = new TyVarEnv(m.tyParams());
@@ -419,28 +477,74 @@ public class Lower {
if (info == null) {
throw TurbineError.format(source, ErrorKind.CLASS_FILE_NOT_FOUND, sym);
}
- ClassSymbol owner = env.get(sym).owner();
+ ClassSymbol owner = info.owner();
if (owner != null) {
addEnclosing(source, env, all, owner);
all.add(sym);
}
}
+ private @Nullable String collectNestHost(SourceFile source, @Nullable ClassSymbol sym) {
+ if (sym == null) {
+ return null;
+ }
+ while (true) {
+ TypeBoundClass info = env.get(sym);
+ if (info == null) {
+ throw TurbineError.format(source, ErrorKind.CLASS_FILE_NOT_FOUND, sym);
+ }
+ if (info.owner() == null) {
+ return sig.descriptor(sym);
+ }
+ sym = info.owner();
+ }
+ }
+
+ private ImmutableList<String> collectNestMembers(SourceFile source, SourceTypeBoundClass info) {
+ Set<ClassSymbol> nestMembers = new LinkedHashSet<>();
+ for (ClassSymbol child : info.children().values()) {
+ addNestMembers(source, env, nestMembers, child);
+ }
+ ImmutableList.Builder<String> result = ImmutableList.builder();
+ for (ClassSymbol nestMember : nestMembers) {
+ result.add(sig.descriptor(nestMember));
+ }
+ return result.build();
+ }
+
+ private static void addNestMembers(
+ SourceFile source,
+ Env<ClassSymbol, TypeBoundClass> env,
+ Set<ClassSymbol> nestMembers,
+ ClassSymbol sym) {
+ if (!nestMembers.add(sym)) {
+ return;
+ }
+ TypeBoundClass info = env.get(sym);
+ if (info == null) {
+ throw TurbineError.format(source, ErrorKind.CLASS_FILE_NOT_FOUND, sym);
+ }
+ for (ClassSymbol child : info.children().values()) {
+ addNestMembers(source, env, nestMembers, child);
+ }
+ }
+
/**
* Creates an inner class attribute, given an inner class that was referenced somewhere in the
* class.
*/
private ClassFile.InnerClass innerClass(
Env<ClassSymbol, TypeBoundClass> env, ClassSymbol innerSym) {
- TypeBoundClass inner = env.get(innerSym);
+ TypeBoundClass inner = env.getNonNull(innerSym);
+ // this inner class is known to have an owner
+ ClassSymbol owner = requireNonNull(inner.owner());
- String innerName = innerSym.binaryName().substring(inner.owner().binaryName().length() + 1);
+ String innerName = innerSym.binaryName().substring(owner.binaryName().length() + 1);
int access = inner.access();
access &= ~(TurbineFlag.ACC_SUPER | TurbineFlag.ACC_STRICT);
- return new ClassFile.InnerClass(
- innerSym.binaryName(), inner.owner().binaryName(), innerName, access);
+ return new ClassFile.InnerClass(innerSym.binaryName(), owner.binaryName(), innerName, access);
}
/** Updates visibility, and unsets access bits that can only be set in InnerClass. */
@@ -484,7 +588,7 @@ public class Lower {
// anything that lexically encloses the class being lowered
// must be in the same compilation unit, so we have source
// information for it
- TypeBoundClass owner = env.get((ClassSymbol) ownerSym);
+ TypeBoundClass owner = env.getNonNull((ClassSymbol) ownerSym);
return owner.typeParameterTypes().get(sym);
}
}
@@ -501,7 +605,7 @@ public class Lower {
return lowered.build();
}
- private AnnotationInfo lowerAnnotation(AnnoInfo annotation) {
+ private @Nullable AnnotationInfo lowerAnnotation(AnnoInfo annotation) {
Boolean visible = isVisible(annotation.sym());
if (visible == null) {
return null;
@@ -514,9 +618,9 @@ public class Lower {
* Returns true if the annotation is visible at runtime, false if it is not visible at runtime,
* and {@code null} if it should not be retained in bytecode.
*/
- @Nullable
- private Boolean isVisible(ClassSymbol sym) {
- RetentionPolicy retention = env.get(sym).annotationMetadata().retention();
+ private @Nullable Boolean isVisible(ClassSymbol sym) {
+ RetentionPolicy retention =
+ requireNonNull(env.getNonNull(sym).annotationMetadata()).retention();
switch (retention) {
case CLASS:
return false;
@@ -533,7 +637,7 @@ public class Lower {
for (Map.Entry<String, Const> entry : values.entrySet()) {
result.put(entry.getKey(), annotationValue(entry.getValue()));
}
- return result.build();
+ return result.buildOrThrow();
}
private ElementValue annotationValue(Const value) {
@@ -689,7 +793,7 @@ public class Lower {
private boolean isInterface(Type type, Env<ClassSymbol, TypeBoundClass> env) {
return type.tyKind() == TyKind.CLASS_TY
- && env.get(((ClassTy) type).sym()).kind() == TurbineTyKind.INTERFACE;
+ && env.getNonNull(((ClassTy) type).sym()).kind() == TurbineTyKind.INTERFACE;
}
private void lowerTypeAnnotations(
diff --git a/java/com/google/turbine/lower/LowerSignature.java b/java/com/google/turbine/lower/LowerSignature.java
index 13a7b9f..1960f8e 100644
--- a/java/com/google/turbine/lower/LowerSignature.java
+++ b/java/com/google/turbine/lower/LowerSignature.java
@@ -46,6 +46,7 @@ import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
+import org.jspecify.nullness.Nullable;
/** Translator from {@link Type}s to {@link Sig}natures. */
public class LowerSignature {
@@ -127,16 +128,14 @@ public class LowerSignature {
* Produces a method signature attribute for a generic method, or {@code null} if the signature is
* unnecessary.
*/
- public String methodSignature(
- Env<ClassSymbol, TypeBoundClass> env,
- SourceTypeBoundClass.MethodInfo method,
- ClassSymbol sym) {
+ public @Nullable String methodSignature(
+ Env<ClassSymbol, TypeBoundClass> env, TypeBoundClass.MethodInfo method, ClassSymbol sym) {
if (!needsMethodSig(sym, env, method)) {
return null;
}
ImmutableList<Sig.TyParamSig> typarams = tyParamSig(method.tyParams(), env);
ImmutableList.Builder<Sig.TySig> fparams = ImmutableList.builder();
- for (SourceTypeBoundClass.ParamInfo t : method.parameters()) {
+ for (TypeBoundClass.ParamInfo t : method.parameters()) {
if (t.synthetic()) {
continue;
}
@@ -161,22 +160,19 @@ public class LowerSignature {
}
private boolean needsMethodSig(
- ClassSymbol sym, Env<ClassSymbol, TypeBoundClass> env, SourceTypeBoundClass.MethodInfo m) {
- if ((env.get(sym).access() & TurbineFlag.ACC_ENUM) == TurbineFlag.ACC_ENUM
+ ClassSymbol sym, Env<ClassSymbol, TypeBoundClass> env, TypeBoundClass.MethodInfo m) {
+ if ((env.getNonNull(sym).access() & TurbineFlag.ACC_ENUM) == TurbineFlag.ACC_ENUM
&& m.name().equals("<init>")) {
// JDK-8024694: javac always expects signature attribute for enum constructors
return true;
}
- if ((m.access() & TurbineFlag.ACC_SYNTH_CTOR) == TurbineFlag.ACC_SYNTH_CTOR) {
- return false;
- }
if (!m.tyParams().isEmpty()) {
return true;
}
if (m.returnType() != null && needsSig(m.returnType())) {
return true;
}
- for (SourceTypeBoundClass.ParamInfo t : m.parameters()) {
+ for (TypeBoundClass.ParamInfo t : m.parameters()) {
if (t.synthetic()) {
continue;
}
@@ -196,16 +192,13 @@ public class LowerSignature {
* Produces a class signature attribute for a generic class, or {@code null} if the signature is
* unnecessary.
*/
- public String classSignature(SourceTypeBoundClass info, Env<ClassSymbol, TypeBoundClass> env) {
+ public @Nullable String classSignature(
+ SourceTypeBoundClass info, Env<ClassSymbol, TypeBoundClass> env) {
if (!classNeedsSig(info)) {
return null;
}
ImmutableList<Sig.TyParamSig> typarams = tyParamSig(info.typeParameterTypes(), env);
-
- ClassTySig xtnd = null;
- if (info.superClassType() != null) {
- xtnd = classTySig((ClassTy) info.superClassType());
- }
+ ClassTySig xtnd = classTySig((ClassTy) info.superClassType());
ImmutableList.Builder<ClassTySig> impl = ImmutableList.builder();
for (Type i : info.interfaceTypes()) {
impl.add(classTySig((ClassTy) i));
@@ -217,7 +210,7 @@ public class LowerSignature {
/**
* A field signature, or {@code null} if the descriptor provides all necessary type information.
*/
- public String fieldSignature(Type type) {
+ public @Nullable String fieldSignature(Type type) {
return needsSig(type) ? SigWriter.type(signature(type)) : null;
}
@@ -262,14 +255,14 @@ public class LowerSignature {
private ImmutableList<Sig.TyParamSig> tyParamSig(
Map<TyVarSymbol, TyVarInfo> px, Env<ClassSymbol, TypeBoundClass> env) {
ImmutableList.Builder<Sig.TyParamSig> result = ImmutableList.builder();
- for (Map.Entry<TyVarSymbol, SourceTypeBoundClass.TyVarInfo> entry : px.entrySet()) {
+ for (Map.Entry<TyVarSymbol, TyVarInfo> entry : px.entrySet()) {
result.add(tyParamSig(entry.getKey(), entry.getValue(), env));
}
return result.build();
}
private Sig.TyParamSig tyParamSig(
- TyVarSymbol sym, SourceTypeBoundClass.TyVarInfo info, Env<ClassSymbol, TypeBoundClass> env) {
+ TyVarSymbol sym, TyVarInfo info, Env<ClassSymbol, TypeBoundClass> env) {
String identifier = sym.name();
Sig.TySig cbound = null;
@@ -297,7 +290,7 @@ public class LowerSignature {
private boolean isInterface(Type type, Env<ClassSymbol, TypeBoundClass> env) {
return type.tyKind() == TyKind.CLASS_TY
- && env.get(((ClassTy) type).sym()).kind() == TurbineTyKind.INTERFACE;
+ && env.getNonNull(((ClassTy) type).sym()).kind() == TurbineTyKind.INTERFACE;
}
public String descriptor(ClassSymbol sym) {
diff --git a/java/com/google/turbine/lower/package-info.java b/java/com/google/turbine/lower/package-info.java
new file mode 100644
index 0000000..f5c54fc
--- /dev/null
+++ b/java/com/google/turbine/lower/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+package com.google.turbine.lower;
diff --git a/java/com/google/turbine/main/Main.java b/java/com/google/turbine/main/Main.java
index 1e60ae6..da97bcd 100644
--- a/java/com/google/turbine/main/Main.java
+++ b/java/com/google/turbine/main/Main.java
@@ -24,6 +24,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.hash.Hashing;
import com.google.common.io.MoreFiles;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.turbine.binder.Binder;
import com.google.turbine.binder.Binder.BindingResult;
import com.google.turbine.binder.Binder.Statistics;
@@ -62,6 +63,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
+import java.util.OptionalInt;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@@ -70,7 +72,7 @@ import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
/** Main entry point for the turbine CLI. */
-public class Main {
+public final class Main {
private static final int BUFFER_SIZE = 65536;
@@ -126,10 +128,12 @@ public class Main {
}
}
- public static void compile(String[] args) throws IOException {
- compile(TurbineOptionsParser.parse(Arrays.asList(args)));
+ @CanIgnoreReturnValue
+ public static Result compile(String[] args) throws IOException {
+ return compile(TurbineOptionsParser.parse(Arrays.asList(args)));
}
+ @CanIgnoreReturnValue
public static Result compile(TurbineOptions options) throws IOException {
usage(options);
@@ -190,14 +194,16 @@ public class Main {
|| options.output().isPresent()
|| options.outputManifest().isPresent()) {
// TODO(cushon): parallelize
- Lowered lowered = Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv());
+ Lowered lowered =
+ Lower.lowerAll(
+ options.languageVersion(), bound.units(), bound.modules(), bound.classPathEnv());
if (options.outputDeps().isPresent()) {
DepsProto.Dependencies deps =
Dependencies.collectDeps(options.targetLabel(), bootclasspath, bound, lowered);
- try (OutputStream os =
- new BufferedOutputStream(
- Files.newOutputStream(Paths.get(options.outputDeps().get())))) {
+ Path path = Paths.get(options.outputDeps().get());
+ Files.createDirectories(path.getParent());
+ try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(path))) {
deps.writeTo(os);
}
}
@@ -255,10 +261,12 @@ public class Main {
units,
ClassPathBinder.bindClasspath(toPaths(classpath)),
Processing.initializeProcessors(
+ /* sourceVersion= */ options.languageVersion().sourceVersion(),
/* javacopts= */ options.javacOpts(),
- /* processorPath= */ options.processorPath(),
/* processorNames= */ options.processors(),
- /* builtinProcessors= */ options.builtinProcessors()),
+ Processing.processorLoader(
+ /* processorPath= */ options.processorPath(),
+ /* builtinProcessors= */ options.builtinProcessors())),
bootclasspath,
/* moduleVersion=*/ Optional.empty());
}
@@ -277,18 +285,18 @@ public class Main {
private static ClassPath bootclasspath(TurbineOptions options) throws IOException {
// if both --release and --bootclasspath are specified, --release wins
- if (options.release().isPresent() && options.system().isPresent()) {
+ OptionalInt release = options.languageVersion().release();
+ if (release.isPresent() && options.system().isPresent()) {
throw new UsageException("expected at most one of --release and --system");
}
- if (options.release().isPresent()) {
- String release = options.release().get();
- if (release.equals(JAVA_SPECIFICATION_VERSION.value())) {
+ if (release.isPresent()) {
+ if (release.getAsInt() == Integer.parseInt(JAVA_SPECIFICATION_VERSION.value())) {
// if --release matches the host JDK, use its jimage instead of ct.sym
return JimageClassBinder.bindDefault();
}
// ... otherwise, search ct.sym for a matching release
- ClassPath bootclasspath = CtSymClassBinder.bind(release);
+ ClassPath bootclasspath = CtSymClassBinder.bind(release.getAsInt());
if (bootclasspath == null) {
throw new UsageException("not a supported release: " + release);
}
@@ -332,6 +340,14 @@ public class Main {
return;
}
Path path = Paths.get(options.gensrcOutput().get());
+ if (Files.isDirectory(path)) {
+ for (SourceFile source : generatedSources.values()) {
+ Path to = path.resolve(source.path());
+ Files.createDirectories(to.getParent());
+ Files.writeString(to, source.source());
+ }
+ return;
+ }
try (OutputStream os = Files.newOutputStream(path);
BufferedOutputStream bos = new BufferedOutputStream(os, BUFFER_SIZE);
JarOutputStream jos = new JarOutputStream(bos)) {
@@ -349,6 +365,14 @@ public class Main {
return;
}
Path path = Paths.get(options.resourceOutput().get());
+ if (Files.isDirectory(path)) {
+ for (Map.Entry<String, byte[]> resource : generatedResources.entrySet()) {
+ Path to = path.resolve(resource.getKey());
+ Files.createDirectories(to.getParent());
+ Files.write(to, resource.getValue());
+ }
+ return;
+ }
try (OutputStream os = Files.newOutputStream(path);
BufferedOutputStream bos = new BufferedOutputStream(os, BUFFER_SIZE);
JarOutputStream jos = new JarOutputStream(bos)) {
@@ -465,4 +489,6 @@ public class Main {
}
return result.build();
}
+
+ private Main() {}
}
diff --git a/java/com/google/turbine/main/package-info.java b/java/com/google/turbine/main/package-info.java
new file mode 100644
index 0000000..71735f2
--- /dev/null
+++ b/java/com/google/turbine/main/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+package com.google.turbine.main;
diff --git a/java/com/google/turbine/model/Const.java b/java/com/google/turbine/model/Const.java
index ed4b072..bd90f59 100644
--- a/java/com/google/turbine/model/Const.java
+++ b/java/com/google/turbine/model/Const.java
@@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.escape.SourceCodeEscapers;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.AnnotationValueVisitor;
+import org.jspecify.nullness.Nullable;
/**
* Compile-time constant expressions, including literals of primitive or String type, class
@@ -32,7 +33,7 @@ public abstract class Const {
public abstract int hashCode();
@Override
- public abstract boolean equals(Object obj);
+ public abstract boolean equals(@Nullable Object obj);
@Override
public abstract String toString();
@@ -64,42 +65,6 @@ public abstract class Const {
public Kind kind() {
return Kind.PRIMITIVE;
}
-
- public IntValue asInteger() {
- throw new ConstCastError(constantTypeKind(), TurbineConstantTypeKind.INT);
- }
-
- public FloatValue asFloat() {
- throw new ConstCastError(constantTypeKind(), TurbineConstantTypeKind.FLOAT);
- }
-
- public DoubleValue asDouble() {
- throw new ConstCastError(constantTypeKind(), TurbineConstantTypeKind.DOUBLE);
- }
-
- public LongValue asLong() {
- throw new ConstCastError(constantTypeKind(), TurbineConstantTypeKind.LONG);
- }
-
- public BooleanValue asBoolean() {
- throw new ConstCastError(constantTypeKind(), TurbineConstantTypeKind.BOOLEAN);
- }
-
- public StringValue asString() {
- throw new ConstCastError(constantTypeKind(), TurbineConstantTypeKind.STRING);
- }
-
- public CharValue asChar() {
- throw new ConstCastError(constantTypeKind(), TurbineConstantTypeKind.CHAR);
- }
-
- public ShortValue asShort() {
- throw new ConstCastError(constantTypeKind(), TurbineConstantTypeKind.SHORT);
- }
-
- public ByteValue asByte() {
- throw new ConstCastError(constantTypeKind(), TurbineConstantTypeKind.BYTE);
- }
}
/** A boolean literal value. */
@@ -135,22 +100,12 @@ public abstract class Const {
}
@Override
- public BooleanValue asBoolean() {
- return this;
- }
-
- @Override
- public StringValue asString() {
- return new StringValue(String.valueOf(value));
- }
-
- @Override
public int hashCode() {
return Boolean.hashCode(value);
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof BooleanValue && value == ((BooleanValue) obj).value();
}
}
@@ -189,52 +144,12 @@ public abstract class Const {
}
@Override
- public IntValue asInteger() {
- return this;
- }
-
- @Override
- public ByteValue asByte() {
- return new ByteValue((byte) value);
- }
-
- @Override
- public LongValue asLong() {
- return new LongValue((long) value);
- }
-
- @Override
- public CharValue asChar() {
- return new CharValue((char) value);
- }
-
- @Override
- public ShortValue asShort() {
- return new ShortValue((short) value);
- }
-
- @Override
- public DoubleValue asDouble() {
- return new DoubleValue((double) value);
- }
-
- @Override
- public FloatValue asFloat() {
- return new FloatValue((float) value);
- }
-
- @Override
- public StringValue asString() {
- return new StringValue(String.valueOf(value));
- }
-
- @Override
public int hashCode() {
return Integer.hashCode(value);
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof IntValue && value == ((IntValue) obj).value;
}
}
@@ -272,52 +187,12 @@ public abstract class Const {
}
@Override
- public IntValue asInteger() {
- return new IntValue((int) value);
- }
-
- @Override
- public ByteValue asByte() {
- return new ByteValue((byte) value);
- }
-
- @Override
- public LongValue asLong() {
- return this;
- }
-
- @Override
- public CharValue asChar() {
- return new CharValue((char) value);
- }
-
- @Override
- public ShortValue asShort() {
- return new ShortValue((short) value);
- }
-
- @Override
- public DoubleValue asDouble() {
- return new DoubleValue((double) value);
- }
-
- @Override
- public FloatValue asFloat() {
- return new FloatValue((float) value);
- }
-
- @Override
- public StringValue asString() {
- return new StringValue(String.valueOf(value));
- }
-
- @Override
public int hashCode() {
return Long.hashCode(value);
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof LongValue && value == ((LongValue) obj).value;
}
}
@@ -355,52 +230,12 @@ public abstract class Const {
}
@Override
- public IntValue asInteger() {
- return new IntValue((int) value);
- }
-
- @Override
- public ByteValue asByte() {
- return new ByteValue((byte) value);
- }
-
- @Override
- public LongValue asLong() {
- return new LongValue((long) value);
- }
-
- @Override
- public CharValue asChar() {
- return this;
- }
-
- @Override
- public ShortValue asShort() {
- return new ShortValue((short) value);
- }
-
- @Override
- public DoubleValue asDouble() {
- return new DoubleValue((double) value);
- }
-
- @Override
- public FloatValue asFloat() {
- return new FloatValue((float) value);
- }
-
- @Override
- public StringValue asString() {
- return new StringValue(String.valueOf(value));
- }
-
- @Override
public int hashCode() {
return Character.hashCode(value);
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof CharValue && value == ((CharValue) obj).value;
}
}
@@ -441,52 +276,12 @@ public abstract class Const {
}
@Override
- public IntValue asInteger() {
- return new IntValue((int) value);
- }
-
- @Override
- public ByteValue asByte() {
- return new ByteValue((byte) value);
- }
-
- @Override
- public LongValue asLong() {
- return new LongValue((long) value);
- }
-
- @Override
- public CharValue asChar() {
- return new CharValue((char) value);
- }
-
- @Override
- public ShortValue asShort() {
- return new ShortValue((short) value);
- }
-
- @Override
- public DoubleValue asDouble() {
- return new DoubleValue((double) value);
- }
-
- @Override
- public FloatValue asFloat() {
- return this;
- }
-
- @Override
- public StringValue asString() {
- return new StringValue(String.valueOf(value));
- }
-
- @Override
public int hashCode() {
return Float.hashCode(value);
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof FloatValue && value == ((FloatValue) obj).value;
}
}
@@ -533,52 +328,12 @@ public abstract class Const {
}
@Override
- public IntValue asInteger() {
- return new IntValue((int) value);
- }
-
- @Override
- public ByteValue asByte() {
- return new ByteValue((byte) value);
- }
-
- @Override
- public LongValue asLong() {
- return new LongValue((long) value);
- }
-
- @Override
- public CharValue asChar() {
- return new CharValue((char) value);
- }
-
- @Override
- public ShortValue asShort() {
- return new ShortValue((short) value);
- }
-
- @Override
- public DoubleValue asDouble() {
- return this;
- }
-
- @Override
- public FloatValue asFloat() {
- return new FloatValue((float) value);
- }
-
- @Override
- public StringValue asString() {
- return new StringValue(String.valueOf(value));
- }
-
- @Override
public int hashCode() {
return Double.hashCode(value);
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof DoubleValue && value == ((DoubleValue) obj).value;
}
}
@@ -616,17 +371,12 @@ public abstract class Const {
}
@Override
- public StringValue asString() {
- return this;
- }
-
- @Override
public int hashCode() {
return value.hashCode();
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof StringValue && value.equals(((StringValue) obj).value);
}
}
@@ -664,52 +414,12 @@ public abstract class Const {
}
@Override
- public IntValue asInteger() {
- return new IntValue((int) value);
- }
-
- @Override
- public ByteValue asByte() {
- return new ByteValue((byte) value);
- }
-
- @Override
- public LongValue asLong() {
- return new LongValue((long) value);
- }
-
- @Override
- public CharValue asChar() {
- return new CharValue((char) value);
- }
-
- @Override
- public ShortValue asShort() {
- return this;
- }
-
- @Override
- public DoubleValue asDouble() {
- return new DoubleValue((double) value);
- }
-
- @Override
- public FloatValue asFloat() {
- return new FloatValue((float) value);
- }
-
- @Override
- public StringValue asString() {
- return new StringValue(String.valueOf(value));
- }
-
- @Override
public int hashCode() {
return Short.hashCode(value);
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof ShortValue && value == ((ShortValue) obj).value;
}
}
@@ -738,52 +448,12 @@ public abstract class Const {
}
@Override
- public IntValue asInteger() {
- return new IntValue((int) value);
- }
-
- @Override
- public ByteValue asByte() {
- return this;
- }
-
- @Override
- public LongValue asLong() {
- return new LongValue((long) value);
- }
-
- @Override
- public CharValue asChar() {
- return new CharValue((char) value);
- }
-
- @Override
- public ShortValue asShort() {
- return new ShortValue((short) value);
- }
-
- @Override
- public DoubleValue asDouble() {
- return new DoubleValue((double) value);
- }
-
- @Override
- public FloatValue asFloat() {
- return new FloatValue((float) value);
- }
-
- @Override
- public StringValue asString() {
- return new StringValue(String.valueOf(value));
- }
-
- @Override
public int hashCode() {
return Byte.hashCode(value);
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof ByteValue && value == ((ByteValue) obj).value;
}
@@ -822,7 +492,7 @@ public abstract class Const {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof ArrayInitValue && elements.equals(((ArrayInitValue) obj).elements);
}
diff --git a/java/com/google/turbine/model/TurbineElementType.java b/java/com/google/turbine/model/TurbineElementType.java
index a68df3a..a7debf3 100644
--- a/java/com/google/turbine/model/TurbineElementType.java
+++ b/java/com/google/turbine/model/TurbineElementType.java
@@ -28,5 +28,6 @@ public enum TurbineElementType {
PARAMETER,
TYPE,
TYPE_PARAMETER,
- TYPE_USE
+ TYPE_USE,
+ RECORD_COMPONENT
}
diff --git a/java/com/google/turbine/model/TurbineFlag.java b/java/com/google/turbine/model/TurbineFlag.java
index 48e88e7..3e68a5e 100644
--- a/java/com/google/turbine/model/TurbineFlag.java
+++ b/java/com/google/turbine/model/TurbineFlag.java
@@ -22,7 +22,7 @@ package com.google.turbine.model;
* <p>See tables 4.1-A, 4.5-A, 4.6-A, and 4.7.6-A in JVMS 4:
* https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
*/
-public class TurbineFlag {
+public final class TurbineFlag {
public static final int ACC_PUBLIC = 0x0001;
public static final int ACC_PRIVATE = 0x0002;
public static final int ACC_PROTECTED = 0x0004;
@@ -54,4 +54,12 @@ public class TurbineFlag {
/** Synthetic constructors (e.g. of inner classes and enums). */
public static final int ACC_SYNTH_CTOR = 1 << 18;
+
+ public static final int ACC_SEALED = 1 << 19;
+ public static final int ACC_NON_SEALED = 1 << 20;
+
+ /** Compact record constructor. */
+ public static final int ACC_COMPACT_CTOR = 1 << 21;
+
+ private TurbineFlag() {}
}
diff --git a/java/com/google/turbine/model/TurbineTyKind.java b/java/com/google/turbine/model/TurbineTyKind.java
index 6b49f50..b61d6c9 100644
--- a/java/com/google/turbine/model/TurbineTyKind.java
+++ b/java/com/google/turbine/model/TurbineTyKind.java
@@ -21,5 +21,6 @@ public enum TurbineTyKind {
CLASS,
INTERFACE,
ENUM,
- ANNOTATION
+ ANNOTATION,
+ RECORD
}
diff --git a/java/com/google/turbine/model/package-info.java b/java/com/google/turbine/model/package-info.java
new file mode 100644
index 0000000..a1e3873
--- /dev/null
+++ b/java/com/google/turbine/model/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+package com.google.turbine.model;
diff --git a/java/com/google/turbine/options/LanguageVersion.java b/java/com/google/turbine/options/LanguageVersion.java
new file mode 100644
index 0000000..e2b0ea7
--- /dev/null
+++ b/java/com/google/turbine/options/LanguageVersion.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.google.turbine.options;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.primitives.Ints;
+import java.util.Iterator;
+import java.util.OptionalInt;
+import javax.lang.model.SourceVersion;
+
+/**
+ * The language version being compiled, corresponding to javac's {@code -source}, {@code -target},
+ * and {@code --release} flags.
+ */
+@AutoValue
+public abstract class LanguageVersion {
+
+ /** The source version. */
+ public abstract int source();
+
+ /** The target version. */
+ public abstract int target();
+
+ /**
+ * The release version.
+ *
+ * <p>If set, system APIs will be resolved from the host JDK's ct.sym instead of using the
+ * provided {@code --bootclasspath}.
+ */
+ public abstract OptionalInt release();
+
+ /** The class file major version corresponding to the {@link #target}. */
+ public int majorVersion() {
+ return target() + 44;
+ }
+
+ public SourceVersion sourceVersion() {
+ try {
+ return SourceVersion.valueOf("RELEASE_" + source());
+ } catch (IllegalArgumentException unused) {
+ throw new IllegalArgumentException("invalid -source version: " + source());
+ }
+ }
+
+ private static LanguageVersion create(int source, int target, OptionalInt release) {
+ return new AutoValue_LanguageVersion(source, target, release);
+ }
+
+ /** The default language version. Currently Java 8. */
+ public static LanguageVersion createDefault() {
+ return create(DEFAULT, DEFAULT, OptionalInt.empty());
+ }
+
+ private static final int DEFAULT = 8;
+
+ /** Returns the effective {@code LanguageVersion} for the given list of javac options. */
+ public static LanguageVersion fromJavacopts(ImmutableList<String> javacopts) {
+ int sourceVersion = DEFAULT;
+ int targetVersion = DEFAULT;
+ OptionalInt release = OptionalInt.empty();
+ Iterator<String> it = javacopts.iterator();
+ while (it.hasNext()) {
+ String option = it.next();
+ switch (option) {
+ case "-source":
+ case "--source":
+ if (!it.hasNext()) {
+ throw new IllegalArgumentException(option + " requires an argument");
+ }
+ sourceVersion = parseVersion(it.next());
+ release = OptionalInt.empty();
+ break;
+ case "-target":
+ case "--target":
+ if (!it.hasNext()) {
+ throw new IllegalArgumentException(option + " requires an argument");
+ }
+ targetVersion = parseVersion(it.next());
+ release = OptionalInt.empty();
+ break;
+ case "--release":
+ if (!it.hasNext()) {
+ throw new IllegalArgumentException(option + " requires an argument");
+ }
+ String value = it.next();
+ Integer n = Ints.tryParse(value);
+ if (n == null) {
+ throw new IllegalArgumentException("invalid --release version: " + value);
+ }
+ release = OptionalInt.of(n);
+ sourceVersion = n;
+ targetVersion = n;
+ break;
+ default:
+ break;
+ }
+ }
+ return create(sourceVersion, targetVersion, release);
+ }
+
+ private static int parseVersion(String value) {
+ boolean hasPrefix = value.startsWith("1.");
+ Integer version = Ints.tryParse(hasPrefix ? value.substring("1.".length()) : value);
+ if (version == null || !isValidVersion(version, hasPrefix)) {
+ throw new IllegalArgumentException("invalid -source version: " + value);
+ }
+ return version;
+ }
+
+ private static boolean isValidVersion(int version, boolean hasPrefix) {
+ if (version < 5) {
+ // the earliest source version supported by JDK 8 is Java 5
+ return false;
+ }
+ if (hasPrefix && version > 10) {
+ // javac supports legacy `1.*` version numbers for source versions up to Java 10
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/java/com/google/turbine/options/TurbineOptions.java b/java/com/google/turbine/options/TurbineOptions.java
index 4dcc408..5cd9a61 100644
--- a/java/com/google/turbine/options/TurbineOptions.java
+++ b/java/com/google/turbine/options/TurbineOptions.java
@@ -20,7 +20,7 @@ import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.Optional;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** Header compilation options. */
@AutoValue
@@ -61,8 +61,8 @@ public abstract class TurbineOptions {
/** Paths to compilation bootclasspath artifacts. */
public abstract ImmutableSet<String> bootClassPath();
- /** The target platform version. */
- public abstract Optional<String> release();
+ /** The language version. */
+ public abstract LanguageVersion languageVersion();
/** The target platform's system modules. */
public abstract Optional<String> system();
@@ -70,17 +70,6 @@ public abstract class TurbineOptions {
/** The output jar. */
public abstract Optional<String> output();
- /**
- * The output jar.
- *
- * @deprecated use {@link #output} instead.
- */
- @Deprecated
- @Nullable
- public String outputFile() {
- return output().orElse(null);
- }
-
/** Paths to annotation processor artifacts. */
public abstract ImmutableList<String> processorPath();
@@ -149,6 +138,7 @@ public abstract class TurbineOptions {
.setDirectJars(ImmutableList.of())
.setDepsArtifacts(ImmutableList.of())
.addAllJavacOpts(ImmutableList.of())
+ .setLanguageVersion(LanguageVersion.createDefault())
.setReducedClasspathMode(ReducedClasspathMode.NONE)
.setHelp(false)
.setFullClasspathLength(0)
@@ -160,56 +150,20 @@ public abstract class TurbineOptions {
public abstract static class Builder {
public abstract Builder setOutput(String output);
- /** @deprecated use {@link #setClassPath(ImmutableList)} instead. */
- @Deprecated
- public Builder addClassPathEntries(Iterable<String> sources) {
- return setClassPath(ImmutableList.copyOf(sources));
- }
-
public abstract Builder setClassPath(ImmutableList<String> classPath);
public abstract Builder setBootClassPath(ImmutableList<String> bootClassPath);
- /** @deprecated use {@link #setBootClassPath(ImmutableList)} instead. */
- @Deprecated
- public Builder addBootClassPathEntries(Iterable<String> sources) {
- return setBootClassPath(ImmutableList.copyOf(sources));
- }
-
- public abstract Builder setRelease(String release);
+ public abstract Builder setLanguageVersion(LanguageVersion languageVersion);
public abstract Builder setSystem(String system);
public abstract Builder setSources(ImmutableList<String> sources);
- /** @deprecated use {@link #setSources(ImmutableList)} instead. */
- @Deprecated
- public Builder addSources(Iterable<String> sources) {
- return setSources(ImmutableList.copyOf(sources));
- }
-
- /** @deprecated use {@link #setProcessorPath(ImmutableList)} instead. */
- @Deprecated
- public Builder addProcessorPathEntries(Iterable<String> processorPath) {
- return setProcessorPath(ImmutableList.copyOf(processorPath));
- }
-
public abstract Builder setProcessorPath(ImmutableList<String> processorPath);
- /** @deprecated use {@link #setProcessors(ImmutableList)} instead. */
- @Deprecated
- public Builder addProcessors(Iterable<String> processors) {
- return setProcessors(ImmutableList.copyOf(processors));
- }
-
public abstract Builder setProcessors(ImmutableList<String> processors);
- /** @deprecated use {@link #setBuiltinProcessors(ImmutableList)} instead. */
- @Deprecated
- public Builder addBuiltinProcessors(Iterable<String> builtinProcessors) {
- return setBuiltinProcessors(ImmutableList.copyOf(builtinProcessors));
- }
-
public abstract Builder setBuiltinProcessors(ImmutableList<String> builtinProcessors);
public abstract Builder setSourceJars(ImmutableList<String> sourceJars);
@@ -222,12 +176,6 @@ public abstract class TurbineOptions {
public abstract Builder setInjectingRuleKind(String injectingRuleKind);
- /** @deprecated use {@link #setDepsArtifacts(ImmutableList)} instead. */
- @Deprecated
- public Builder addAllDepsArtifacts(Iterable<String> depsArtifacts) {
- return setDepsArtifacts(ImmutableList.copyOf(depsArtifacts));
- }
-
public abstract Builder setDepsArtifacts(ImmutableList<String> depsArtifacts);
public abstract Builder setHelp(boolean help);
@@ -241,12 +189,6 @@ public abstract class TurbineOptions {
public abstract Builder setReducedClasspathMode(ReducedClasspathMode reducedClasspathMode);
- /** @deprecated use {@link #setDirectJars(ImmutableList)} instead. */
- @Deprecated
- public Builder addDirectJars(Iterable<String> directJars) {
- return setDirectJars(ImmutableList.copyOf(directJars));
- }
-
public abstract Builder setDirectJars(ImmutableList<String> jars);
public abstract Builder setProfile(String profile);
@@ -261,4 +203,11 @@ public abstract class TurbineOptions {
public abstract TurbineOptions build();
}
+
+ // TODO(b/188833569): remove when AutoValue adds @Nullable to Object if its on the classpath
+ @Override
+ public abstract boolean equals(@Nullable Object other);
+
+ @Override
+ public abstract int hashCode();
}
diff --git a/java/com/google/turbine/options/TurbineOptionsParser.java b/java/com/google/turbine/options/TurbineOptionsParser.java
index 17d4bf6..e68a546 100644
--- a/java/com/google/turbine/options/TurbineOptionsParser.java
+++ b/java/com/google/turbine/options/TurbineOptionsParser.java
@@ -17,7 +17,6 @@
package com.google.turbine.options;
import static com.google.common.base.Preconditions.checkArgument;
-import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
@@ -29,11 +28,9 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.Deque;
-import java.util.Iterator;
-import org.checkerframework.checker.nullness.qual.Nullable;
/** A command line options parser for {@link TurbineOptions}. */
-public class TurbineOptionsParser {
+public final class TurbineOptionsParser {
/**
* Parses command line options into {@link TurbineOptions}, expanding any {@code @params} files.
@@ -57,17 +54,17 @@ public class TurbineOptionsParser {
private static void parse(TurbineOptions.Builder builder, Deque<String> argumentDeque) {
while (!argumentDeque.isEmpty()) {
- String next = argumentDeque.pollFirst();
+ String next = argumentDeque.removeFirst();
switch (next) {
case "--output":
- builder.setOutput(readOne(argumentDeque));
+ builder.setOutput(readOne(next, argumentDeque));
break;
case "--source_jars":
builder.setSourceJars(readList(argumentDeque));
break;
case "--temp_dir":
// TODO(cushon): remove this when Bazel no longer passes the flag
- readOne(argumentDeque);
+ readOne(next, argumentDeque);
break;
case "--processors":
builder.setProcessors(readList(argumentDeque));
@@ -84,27 +81,23 @@ public class TurbineOptionsParser {
case "--bootclasspath":
builder.setBootClassPath(readList(argumentDeque));
break;
- case "--release":
- builder.setRelease(readOne(argumentDeque));
- break;
case "--system":
- builder.setSystem(readOne(argumentDeque));
+ builder.setSystem(readOne(next, argumentDeque));
break;
case "--javacopts":
- {
- ImmutableList<String> javacopts = readJavacopts(argumentDeque);
- setReleaseFromJavacopts(builder, javacopts);
- builder.addAllJavacOpts(javacopts);
- break;
- }
+ ImmutableList<String> javacOpts = readJavacopts(argumentDeque);
+ builder.setLanguageVersion(LanguageVersion.fromJavacopts(javacOpts));
+ builder.addAllJavacOpts(javacOpts);
+ break;
case "--sources":
builder.setSources(readList(argumentDeque));
break;
+ case "--output_deps_proto":
case "--output_deps":
- builder.setOutputDeps(readOne(argumentDeque));
+ builder.setOutputDeps(readOne(next, argumentDeque));
break;
case "--output_manifest_proto":
- builder.setOutputManifest(readOne(argumentDeque));
+ builder.setOutputManifest(readOne(next, argumentDeque));
break;
case "--direct_dependencies":
builder.setDirectJars(readList(argumentDeque));
@@ -113,10 +106,10 @@ public class TurbineOptionsParser {
builder.setDepsArtifacts(readList(argumentDeque));
break;
case "--target_label":
- builder.setTargetLabel(readOne(argumentDeque));
+ builder.setTargetLabel(readOne(next, argumentDeque));
break;
case "--injecting_rule_kind":
- builder.setInjectingRuleKind(readOne(argumentDeque));
+ builder.setInjectingRuleKind(readOne(next, argumentDeque));
break;
case "--javac_fallback":
case "--nojavac_fallback":
@@ -129,26 +122,37 @@ public class TurbineOptionsParser {
builder.setReducedClasspathMode(ReducedClasspathMode.NONE);
break;
case "--reduce_classpath_mode":
- builder.setReducedClasspathMode(ReducedClasspathMode.valueOf(readOne(argumentDeque)));
+ builder.setReducedClasspathMode(
+ ReducedClasspathMode.valueOf(readOne(next, argumentDeque)));
break;
case "--full_classpath_length":
- builder.setFullClasspathLength(Integer.parseInt(readOne(argumentDeque)));
+ builder.setFullClasspathLength(Integer.parseInt(readOne(next, argumentDeque)));
break;
case "--reduced_classpath_length":
- builder.setReducedClasspathLength(Integer.parseInt(readOne(argumentDeque)));
+ builder.setReducedClasspathLength(Integer.parseInt(readOne(next, argumentDeque)));
break;
case "--profile":
- builder.setProfile(readOne(argumentDeque));
+ builder.setProfile(readOne(next, argumentDeque));
break;
+ case "--generated_sources_output":
case "--gensrc_output":
- builder.setGensrcOutput(readOne(argumentDeque));
+ builder.setGensrcOutput(readOne(next, argumentDeque));
break;
case "--resource_output":
- builder.setResourceOutput(readOne(argumentDeque));
+ builder.setResourceOutput(readOne(next, argumentDeque));
break;
case "--help":
builder.setHelp(true);
break;
+ case "--experimental_fix_deps_tool":
+ case "--strict_java_deps":
+ case "--native_header_output":
+ // accepted (and ignored) for compatibility with JavaBuilder command lines
+ readOne(next, argumentDeque);
+ break;
+ case "--compress_jar":
+ // accepted (and ignored) for compatibility with JavaBuilder command lines
+ break;
default:
throw new IllegalArgumentException("unknown option: " + next);
}
@@ -182,28 +186,29 @@ public class TurbineOptionsParser {
if (!Files.exists(paramsPath)) {
throw new AssertionError("params file does not exist: " + paramsPath);
}
- expandParamsFiles(
- argumentDeque, ARG_SPLITTER.split(new String(Files.readAllBytes(paramsPath), UTF_8)));
+ expandParamsFiles(argumentDeque, ARG_SPLITTER.split(Files.readString(paramsPath)));
} else {
argumentDeque.addLast(arg);
}
}
}
- /** Returns the value of an option, or {@code null}. */
- @Nullable
- private static String readOne(Deque<String> argumentDeque) {
- if (argumentDeque.isEmpty() || argumentDeque.peekFirst().startsWith("-")) {
- return null;
+ /**
+ * Returns the value of an option, or throws {@link IllegalArgumentException} if the value is not
+ * present.
+ */
+ private static String readOne(String flag, Deque<String> argumentDeque) {
+ if (argumentDeque.isEmpty() || argumentDeque.getFirst().startsWith("-")) {
+ throw new IllegalArgumentException("missing required argument for: " + flag);
}
- return argumentDeque.pollFirst();
+ return argumentDeque.removeFirst();
}
/** Returns a list of option values. */
private static ImmutableList<String> readList(Deque<String> argumentDeque) {
ImmutableList.Builder<String> result = ImmutableList.builder();
- while (!argumentDeque.isEmpty() && !argumentDeque.peekFirst().startsWith("--")) {
- result.add(argumentDeque.pollFirst());
+ while (!argumentDeque.isEmpty() && !argumentDeque.getFirst().startsWith("--")) {
+ result.add(argumentDeque.removeFirst());
}
return result.build();
}
@@ -215,7 +220,7 @@ public class TurbineOptionsParser {
private static ImmutableList<String> readJavacopts(Deque<String> argumentDeque) {
ImmutableList.Builder<String> result = ImmutableList.builder();
while (!argumentDeque.isEmpty()) {
- String arg = argumentDeque.pollFirst();
+ String arg = argumentDeque.removeFirst();
if (arg.equals("--")) {
return result.build();
}
@@ -224,17 +229,5 @@ public class TurbineOptionsParser {
throw new IllegalArgumentException("javacopts should be terminated by `--`");
}
- /**
- * Parses the given javacopts for {@code --release}, and if found sets turbine's {@code --release}
- * flag.
- */
- private static void setReleaseFromJavacopts(
- TurbineOptions.Builder builder, ImmutableList<String> javacopts) {
- Iterator<String> it = javacopts.iterator();
- while (it.hasNext()) {
- if (it.next().equals("--release") && it.hasNext()) {
- builder.setRelease(it.next());
- }
- }
- }
+ private TurbineOptionsParser() {}
}
diff --git a/java/com/google/turbine/options/package-info.java b/java/com/google/turbine/options/package-info.java
new file mode 100644
index 0000000..45bad5e
--- /dev/null
+++ b/java/com/google/turbine/options/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@org.jspecify.nullness.NullMarked
+package com.google.turbine.options;
diff --git a/java/com/google/turbine/parse/ConstExpressionParser.java b/java/com/google/turbine/parse/ConstExpressionParser.java
index e49d51c..8b7466f 100644
--- a/java/com/google/turbine/parse/ConstExpressionParser.java
+++ b/java/com/google/turbine/parse/ConstExpressionParser.java
@@ -25,13 +25,14 @@ import com.google.turbine.diag.TurbineError.ErrorKind;
import com.google.turbine.model.Const;
import com.google.turbine.model.TurbineConstantTypeKind;
import com.google.turbine.tree.Tree;
+import com.google.turbine.tree.Tree.AnnoExpr;
import com.google.turbine.tree.Tree.ClassLiteral;
import com.google.turbine.tree.Tree.ClassTy;
import com.google.turbine.tree.Tree.Expression;
import com.google.turbine.tree.Tree.Ident;
import com.google.turbine.tree.TurbineOperatorKind;
import java.util.Optional;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** A parser for compile-time constant expressions. */
public class ConstExpressionParser {
@@ -40,13 +41,13 @@ public class ConstExpressionParser {
private int position;
private final Lexer lexer;
- public ConstExpressionParser(Lexer lexer, Token token) {
+ public ConstExpressionParser(Lexer lexer, Token token, int position) {
this.lexer = lexer;
this.token = token;
- this.position = lexer.position();
+ this.position = position;
}
- private static TurbineOperatorKind operator(Token token) {
+ private static @Nullable TurbineOperatorKind operator(Token token) {
switch (token) {
case ASSIGN:
// TODO(cushon): only allow in annotations?
@@ -96,7 +97,7 @@ public class ConstExpressionParser {
}
}
- private Tree.@Nullable Expression primary(boolean negate) {
+ private @Nullable Expression primary(boolean negate) {
switch (token) {
case INT_LITERAL:
return finishLiteral(TurbineConstantTypeKind.INT, negate);
@@ -107,13 +108,19 @@ public class ConstExpressionParser {
case FLOAT_LITERAL:
return finishLiteral(TurbineConstantTypeKind.FLOAT, negate);
case TRUE:
- eat();
- return new Tree.Literal(
- position, TurbineConstantTypeKind.BOOLEAN, new Const.BooleanValue(true));
+ {
+ int pos = position;
+ eat();
+ return new Tree.Literal(
+ pos, TurbineConstantTypeKind.BOOLEAN, new Const.BooleanValue(true));
+ }
case FALSE:
- eat();
- return new Tree.Literal(
- position, TurbineConstantTypeKind.BOOLEAN, new Const.BooleanValue(false));
+ {
+ int pos = position;
+ eat();
+ return new Tree.Literal(
+ pos, TurbineConstantTypeKind.BOOLEAN, new Const.BooleanValue(false));
+ }
case CHAR_LITERAL:
return finishLiteral(TurbineConstantTypeKind.CHAR, negate);
case STRING_LITERAL:
@@ -169,7 +176,7 @@ public class ConstExpressionParser {
return finishClassLiteral(position, new Tree.PrimTy(position, ImmutableList.of(), type));
}
- private Tree.Expression maybeCast() {
+ private Expression maybeCast() {
eat();
switch (token) {
case BOOLEAN:
@@ -201,8 +208,8 @@ public class ConstExpressionParser {
}
}
- private Tree.Expression notCast() {
- Tree.Expression expr = expression(null);
+ private @Nullable Expression notCast() {
+ Expression expr = expression(null);
if (expr == null) {
return null;
}
@@ -222,13 +229,16 @@ public class ConstExpressionParser {
case NOT:
case TILDE:
case IDENT:
- return new Tree.TypeCast(
- position, asClassTy(cvar.position(), cvar.name()), primary(false));
+ Expression expression = primary(false);
+ if (expression == null) {
+ throw error(ErrorKind.EXPRESSION_ERROR);
+ }
+ return new Tree.TypeCast(position, asClassTy(cvar.position(), cvar.name()), expression);
default:
- return expr;
+ return new Tree.Paren(position, expr);
}
} else {
- return expr;
+ return new Tree.Paren(position, expr);
}
}
@@ -245,7 +255,7 @@ public class ConstExpressionParser {
position = lexer.position();
}
- private Tree.Expression arrayInitializer(int pos) {
+ private @Nullable Expression arrayInitializer(int pos) {
if (token == Token.RBRACE) {
eat();
return new Tree.ArrayInit(pos, ImmutableList.<Tree.Expression>of());
@@ -258,7 +268,7 @@ public class ConstExpressionParser {
eat();
break OUTER;
}
- Tree.Expression item = expression(null);
+ Expression item = expression(null);
if (item == null) {
return null;
}
@@ -278,7 +288,7 @@ public class ConstExpressionParser {
}
/** Finish hex, decimal, octal, and binary integer literals (see JLS 3.10.1). */
- private Tree.Expression finishLiteral(TurbineConstantTypeKind kind, boolean negate) {
+ private Expression finishLiteral(TurbineConstantTypeKind kind, boolean negate) {
int pos = position;
String text = ident().value();
Const.Value value;
@@ -381,7 +391,8 @@ public class ConstExpressionParser {
if (neg) {
text = text.substring(1);
}
- for (char c : text.toCharArray()) {
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
int digit;
if ('0' <= c && c <= '9') {
digit = c - '0';
@@ -402,9 +413,9 @@ public class ConstExpressionParser {
return r;
}
- private Tree.Expression unaryRest(TurbineOperatorKind op) {
+ private @Nullable Expression unaryRest(TurbineOperatorKind op) {
boolean negate = op == TurbineOperatorKind.NEG;
- Tree.Expression expr = primary(negate);
+ Expression expr = primary(negate);
if (expr == null) {
return null;
}
@@ -421,14 +432,11 @@ public class ConstExpressionParser {
return new Tree.Unary(position, expr, op);
}
- private Tree.@Nullable Expression qualIdent() {
+ private @Nullable Expression qualIdent() {
int pos = position;
ImmutableList.Builder<Ident> bits = ImmutableList.builder();
bits.add(ident());
eat();
- if (token == Token.LBRACK) {
- return finishClassLiteral(pos, asClassTy(pos, bits.build()));
- }
while (token == Token.DOT) {
eat();
switch (token) {
@@ -444,6 +452,9 @@ public class ConstExpressionParser {
}
eat();
}
+ if (token == Token.LBRACK) {
+ return finishClassLiteral(pos, asClassTy(pos, bits.build()));
+ }
return new Tree.ConstVarName(pos, bits.build());
}
@@ -451,7 +462,7 @@ public class ConstExpressionParser {
return new Ident(lexer.position(), lexer.stringValue());
}
- private Expression finishClassLiteral(int pos, Tree.Type type) {
+ private @Nullable Expression finishClassLiteral(int pos, Tree.Type type) {
while (token == Token.LBRACK) {
eat();
if (token != Token.RBRACK) {
@@ -471,8 +482,8 @@ public class ConstExpressionParser {
return new ClassLiteral(pos, type);
}
- public Tree.Expression expression() {
- Tree.Expression result = expression(null);
+ public @Nullable Expression expression() {
+ Expression result = expression(null);
switch (token) {
case EOF:
case SEMI:
@@ -485,15 +496,15 @@ public class ConstExpressionParser {
}
}
- private Tree.Expression expression(TurbineOperatorKind.Precedence prec) {
- Tree.Expression term1 = primary(false);
+ private @Nullable Expression expression(TurbineOperatorKind.Precedence prec) {
+ Expression term1 = primary(false);
if (term1 == null) {
return null;
}
return expression(term1, prec);
}
- private Tree.Expression expression(Tree.Expression term1, TurbineOperatorKind.Precedence prec) {
+ private @Nullable Expression expression(Expression term1, TurbineOperatorKind.Precedence prec) {
while (true) {
if (token == Token.EOF) {
return term1;
@@ -506,12 +517,20 @@ public class ConstExpressionParser {
return term1;
}
eat();
- if (op == TurbineOperatorKind.TERNARY) {
- term1 = ternary(term1);
- } else if (op == TurbineOperatorKind.ASSIGN) {
- term1 = assign(term1, op);
- } else {
- term1 = new Tree.Binary(position, term1, expression(op.prec()), op);
+ switch (op) {
+ case TERNARY:
+ term1 = ternary(term1);
+ break;
+ case ASSIGN:
+ term1 = assign(term1, op);
+ break;
+ default:
+ int pos = position;
+ Expression term2 = expression(op.prec());
+ if (term2 == null) {
+ return null;
+ }
+ term1 = new Tree.Binary(pos, term1, term2, op);
}
if (term1 == null) {
return null;
@@ -519,7 +538,7 @@ public class ConstExpressionParser {
}
}
- private Tree.Expression assign(Tree.Expression term1, TurbineOperatorKind op) {
+ private @Nullable Expression assign(Expression term1, TurbineOperatorKind op) {
if (!(term1 instanceof Tree.ConstVarName)) {
return null;
}
@@ -528,15 +547,15 @@ public class ConstExpressionParser {
return null;
}
Ident name = getOnlyElement(names);
- Tree.Expression rhs = expression(op.prec());
+ Expression rhs = expression(op.prec());
if (rhs == null) {
return null;
}
return new Tree.Assign(term1.position(), name, rhs);
}
- private Tree.Expression ternary(Tree.Expression term1) {
- Tree.Expression thenExpr = expression(TurbineOperatorKind.Precedence.TERNARY);
+ private @Nullable Expression ternary(Expression term1) {
+ Expression thenExpr = expression(TurbineOperatorKind.Precedence.TERNARY);
if (thenExpr == null) {
return null;
}
@@ -544,30 +563,31 @@ public class ConstExpressionParser {
return null;
}
eat();
- Tree.Expression elseExpr = expression();
+ Expression elseExpr = expression();
if (elseExpr == null) {
return null;
}
return new Tree.Conditional(position, term1, thenExpr, elseExpr);
}
- private Tree.Expression castTail(TurbineConstantTypeKind ty) {
+ private @Nullable Expression castTail(TurbineConstantTypeKind ty) {
if (token != Token.RPAREN) {
return null;
}
eat();
- Tree.Expression rhs = primary(false);
+ Expression rhs = primary(false);
if (rhs == null) {
return null;
}
return new Tree.TypeCast(position, new Tree.PrimTy(position, ImmutableList.of(), ty), rhs);
}
- private Tree.@Nullable AnnoExpr annotation() {
+ private @Nullable AnnoExpr annotation() {
if (token != Token.AT) {
throw new AssertionError();
}
eat();
+ int pos = position;
Tree.ConstVarName constVarName = (Tree.ConstVarName) qualIdent();
if (constVarName == null) {
return null;
@@ -577,10 +597,10 @@ public class ConstExpressionParser {
if (token == Token.LPAREN) {
eat();
while (token != Token.RPAREN) {
- int pos = position;
- Tree.Expression expression = expression();
+ int argPos = position;
+ Expression expression = expression();
if (expression == null) {
- throw TurbineError.format(lexer.source(), pos, ErrorKind.INVALID_ANNOTATION_ARGUMENT);
+ throw TurbineError.format(lexer.source(), argPos, ErrorKind.INVALID_ANNOTATION_ARGUMENT);
}
args.add(expression);
if (token != Token.COMMA) {
@@ -592,11 +612,19 @@ public class ConstExpressionParser {
eat();
}
}
- return new Tree.AnnoExpr(position, new Tree.Anno(position, name, args.build()));
+ return new Tree.AnnoExpr(pos, new Tree.Anno(pos, name, args.build()));
}
@CheckReturnValue
private TurbineError error(ErrorKind kind, Object... args) {
return TurbineError.format(lexer.source(), lexer.position(), kind, args);
}
+
+ public int f() {
+ return helper(1, 2);
+ }
+
+ private int helper(int x, int y) {
+ return x + y;
+ }
}
diff --git a/java/com/google/turbine/parse/Parser.java b/java/com/google/turbine/parse/Parser.java
index 4a090b3..c370ad8 100644
--- a/java/com/google/turbine/parse/Parser.java
+++ b/java/com/google/turbine/parse/Parser.java
@@ -17,8 +17,10 @@
package com.google.turbine.parse;
import static com.google.turbine.parse.Token.COMMA;
+import static com.google.turbine.parse.Token.IDENT;
import static com.google.turbine.parse.Token.INTERFACE;
import static com.google.turbine.parse.Token.LPAREN;
+import static com.google.turbine.parse.Token.MINUS;
import static com.google.turbine.parse.Token.RPAREN;
import static com.google.turbine.parse.Token.SEMI;
import static com.google.turbine.tree.TurbineModifier.PROTECTED;
@@ -27,7 +29,7 @@ import static com.google.turbine.tree.TurbineModifier.VARARGS;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
-import com.google.errorprone.annotations.CheckReturnValue;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.turbine.diag.SourceFile;
import com.google.turbine.diag.TurbineError;
import com.google.turbine.diag.TurbineError.ErrorKind;
@@ -63,7 +65,7 @@ import java.util.Deque;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/**
* A parser for the subset of Java required for header compilation.
@@ -186,6 +188,26 @@ public class Parser {
case IDENT:
{
Ident ident = ident();
+ if (ident.value().equals("record")) {
+ next();
+ decls.add(recordDeclaration(access, annos.build()));
+ access = EnumSet.noneOf(TurbineModifier.class);
+ annos = ImmutableList.builder();
+ break;
+ }
+ if (ident.value().equals("sealed")) {
+ next();
+ access.add(TurbineModifier.SEALED);
+ break;
+ }
+ if (ident.value().equals("non")) {
+ int start = position;
+ next();
+ eatNonSealed(start);
+ next();
+ access.add(TurbineModifier.NON_SEALED);
+ break;
+ }
if (access.isEmpty()
&& (ident.value().equals("module") || ident.value().equals("open"))) {
boolean open = false;
@@ -209,11 +231,68 @@ public class Parser {
}
}
+ // Handle the hypenated pseudo-keyword 'non-sealed'.
+ //
+ // This will need to be updated to handle other hyphenated keywords if when/they are introduced.
+ private void eatNonSealed(int start) {
+ eat(Token.MINUS);
+ if (token != IDENT) {
+ throw error(token);
+ }
+ if (!ident().value().equals("sealed")) {
+ throw error(token);
+ }
+ if (position != start + "non-".length()) {
+ throw error(token);
+ }
+ }
+
private void next() {
token = lexer.next();
position = lexer.position();
}
+ private TyDecl recordDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) {
+ String javadoc = lexer.javadoc();
+ int pos = position;
+ Ident name = eatIdent();
+ ImmutableList<TyParam> typarams;
+ if (token == Token.LT) {
+ typarams = typarams();
+ } else {
+ typarams = ImmutableList.of();
+ }
+ ImmutableList.Builder<VarDecl> formals = ImmutableList.builder();
+ if (token == Token.LPAREN) {
+ next();
+ formalParams(formals, EnumSet.noneOf(TurbineModifier.class));
+ eat(Token.RPAREN);
+ }
+ ImmutableList.Builder<ClassTy> interfaces = ImmutableList.builder();
+ if (token == Token.IMPLEMENTS) {
+ next();
+ do {
+ interfaces.add(classty());
+ } while (maybe(Token.COMMA));
+ }
+ eat(Token.LBRACE);
+ ImmutableList<Tree> members = classMembers();
+ eat(Token.RBRACE);
+ return new TyDecl(
+ pos,
+ access,
+ annos,
+ name,
+ typarams,
+ Optional.<ClassTy>empty(),
+ interfaces.build(),
+ /* permits= */ ImmutableList.of(),
+ members,
+ formals.build(),
+ TurbineTyKind.RECORD,
+ javadoc);
+ }
+
private TyDecl interfaceDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) {
String javadoc = lexer.javadoc();
eat(Token.INTERFACE);
@@ -232,6 +311,15 @@ public class Parser {
interfaces.add(classty());
} while (maybe(Token.COMMA));
}
+ ImmutableList.Builder<ClassTy> permits = ImmutableList.builder();
+ if (token == Token.IDENT) {
+ if (ident().value().equals("permits")) {
+ eat(Token.IDENT);
+ do {
+ permits.add(classty());
+ } while (maybe(Token.COMMA));
+ }
+ }
eat(Token.LBRACE);
ImmutableList<Tree> members = classMembers();
eat(Token.RBRACE);
@@ -243,7 +331,9 @@ public class Parser {
typarams,
Optional.<ClassTy>empty(),
interfaces.build(),
+ permits.build(),
members,
+ ImmutableList.of(),
TurbineTyKind.INTERFACE,
javadoc);
}
@@ -264,7 +354,9 @@ public class Parser {
ImmutableList.<TyParam>of(),
Optional.<ClassTy>empty(),
ImmutableList.<ClassTy>of(),
+ ImmutableList.of(),
members,
+ ImmutableList.of(),
TurbineTyKind.ANNOTATION,
javadoc);
}
@@ -293,7 +385,9 @@ public class Parser {
ImmutableList.<TyParam>of(),
Optional.<ClassTy>empty(),
interfaces.build(),
+ ImmutableList.of(),
members,
+ ImmutableList.of(),
TurbineTyKind.ENUM,
javadoc);
}
@@ -519,7 +613,24 @@ public class Parser {
interfaces.add(classty());
} while (maybe(Token.COMMA));
}
- eat(Token.LBRACE);
+ ImmutableList.Builder<ClassTy> permits = ImmutableList.builder();
+ if (token == Token.IDENT) {
+ if (ident().value().equals("permits")) {
+ eat(Token.IDENT);
+ do {
+ permits.add(classty());
+ } while (maybe(Token.COMMA));
+ }
+ }
+ switch (token) {
+ case LBRACE:
+ next();
+ break;
+ case EXTENDS:
+ throw error(ErrorKind.EXTENDS_AFTER_IMPLEMENTS);
+ default:
+ throw error(ErrorKind.EXPECTED_TOKEN, Token.LBRACE);
+ }
ImmutableList<Tree> members = classMembers();
eat(Token.RBRACE);
return new TyDecl(
@@ -530,7 +641,9 @@ public class Parser {
tyParams,
Optional.ofNullable(xtnds),
interfaces.build(),
+ permits.build(),
members,
+ ImmutableList.of(),
TurbineTyKind.CLASS,
javadoc);
}
@@ -605,6 +718,29 @@ public class Parser {
}
case IDENT:
+ Ident ident = ident();
+ if (ident.value().equals("non")) {
+ int pos = position;
+ next();
+ if (token != MINUS) {
+ acc.addAll(member(access, annos.build(), ImmutableList.of(), pos, ident));
+ access = EnumSet.noneOf(TurbineModifier.class);
+ annos = ImmutableList.builder();
+ } else {
+ eatNonSealed(pos);
+ next();
+ access.add(TurbineModifier.NON_SEALED);
+ }
+ break;
+ }
+ if (ident.value().equals("record")) {
+ eat(IDENT);
+ acc.add(recordDeclaration(access, annos.build()));
+ access = EnumSet.noneOf(TurbineModifier.class);
+ annos = ImmutableList.builder();
+ break;
+ }
+ // fall through
case BOOLEAN:
case BYTE:
case SHORT:
@@ -688,88 +824,118 @@ public class Parser {
return memberRest(pos, access, annos, typaram, result, name);
}
case IDENT:
+ int pos = position;
+ Ident ident = eatIdent();
+ return member(access, annos, typaram, pos, ident);
+ default:
+ throw error(token);
+ }
+ }
+
+ private ImmutableList<Tree> member(
+ EnumSet<TurbineModifier> access,
+ ImmutableList<Anno> annos,
+ ImmutableList<TyParam> typaram,
+ int pos,
+ Ident ident) {
+ Type result;
+ Ident name;
+ switch (token) {
+ case LPAREN:
{
- int pos = position;
- Ident ident = eatIdent();
- switch (token) {
- case LPAREN:
- {
- name = ident;
- return ImmutableList.of(methodRest(pos, access, annos, typaram, null, name));
- }
- case IDENT:
- {
- result =
- new ClassTy(
- position,
- Optional.<ClassTy>empty(),
- ident,
- ImmutableList.<Type>of(),
- ImmutableList.of());
- pos = position;
- name = eatIdent();
- return memberRest(pos, access, annos, typaram, result, name);
- }
- case AT:
- case LBRACK:
- {
- result =
- new ClassTy(
- position,
- Optional.<ClassTy>empty(),
- ident,
- ImmutableList.<Type>of(),
- ImmutableList.of());
- result = maybeDims(maybeAnnos(), result);
- break;
- }
- case LT:
- {
- result =
- new ClassTy(
- position, Optional.<ClassTy>empty(), ident, tyargs(), ImmutableList.of());
- result = maybeDims(maybeAnnos(), result);
- break;
- }
- case DOT:
- result =
- new ClassTy(
- position,
- Optional.<ClassTy>empty(),
- ident,
- ImmutableList.<Type>of(),
- ImmutableList.of());
- break;
- default:
- throw error(token);
- }
- if (result == null) {
- throw error(token);
- }
- if (token == Token.DOT) {
- next();
- // TODO(cushon): is this cast OK?
- result = classty((ClassTy) result);
- }
- result = maybeDims(maybeAnnos(), result);
+ name = ident;
+ return ImmutableList.of(methodRest(pos, access, annos, typaram, null, name));
+ }
+ case LBRACE:
+ {
+ dropBlocks();
+ name = new Ident(position, CTOR_NAME);
+ String javadoc = lexer.javadoc();
+ access.add(TurbineModifier.COMPACT_CTOR);
+ return ImmutableList.<Tree>of(
+ new MethDecl(
+ pos,
+ access,
+ annos,
+ typaram,
+ /* ret= */ Optional.empty(),
+ name,
+ /* params= */ ImmutableList.of(),
+ /* exntys= */ ImmutableList.of(),
+ /* defaultValue= */ Optional.empty(),
+ javadoc));
+ }
+ case IDENT:
+ {
+ result =
+ new ClassTy(
+ position,
+ Optional.<ClassTy>empty(),
+ ident,
+ ImmutableList.<Type>of(),
+ ImmutableList.of());
pos = position;
name = eatIdent();
- switch (token) {
- case LPAREN:
- return ImmutableList.of(methodRest(pos, access, annos, typaram, result, name));
- case LBRACK:
- case SEMI:
- case ASSIGN:
- case COMMA:
- {
- if (!typaram.isEmpty()) {
- throw error(ErrorKind.UNEXPECTED_TYPE_PARAMETER, typaram);
- }
- return fieldRest(pos, access, annos, result, name);
- }
- default:
- throw error(token);
+ return memberRest(pos, access, annos, typaram, result, name);
+ }
+ case AT:
+ case LBRACK:
+ {
+ result =
+ new ClassTy(
+ position,
+ Optional.<ClassTy>empty(),
+ ident,
+ ImmutableList.<Type>of(),
+ ImmutableList.of());
+ result = maybeDims(maybeAnnos(), result);
+ break;
+ }
+ case LT:
+ {
+ result =
+ new ClassTy(position, Optional.<ClassTy>empty(), ident, tyargs(), ImmutableList.of());
+ result = maybeDims(maybeAnnos(), result);
+ break;
+ }
+ case DOT:
+ result =
+ new ClassTy(
+ position,
+ Optional.<ClassTy>empty(),
+ ident,
+ ImmutableList.<Type>of(),
+ ImmutableList.of());
+ break;
+
+ default:
+ throw error(token);
+ }
+ if (result == null) {
+ throw error(token);
+ }
+ if (token == Token.DOT) {
+ next();
+ if (!result.kind().equals(Kind.CLASS_TY)) {
+ throw error(token);
+ }
+ result = classty((ClassTy) result);
+ }
+ result = maybeDims(maybeAnnos(), result);
+ pos = position;
+ name = eatIdent();
+ switch (token) {
+ case LPAREN:
+ return ImmutableList.of(methodRest(pos, access, annos, typaram, result, name));
+ case LBRACK:
+ case SEMI:
+ case ASSIGN:
+ case COMMA:
+ {
+ if (!typaram.isEmpty()) {
+ throw error(ErrorKind.UNEXPECTED_TYPE_PARAMETER, typaram);
}
+ return fieldRest(pos, access, annos, result, name);
}
default:
throw error(token);
@@ -840,7 +1006,8 @@ public class Parser {
Type ty = baseTy;
ty = parser.extraDims(ty);
// TODO(cushon): skip more fields that are definitely non-const
- ConstExpressionParser constExpressionParser = new ConstExpressionParser(lexer, lexer.next());
+ ConstExpressionParser constExpressionParser =
+ new ConstExpressionParser(lexer, lexer.next(), lexer.position());
expressionStart = lexer.position();
Expression init = constExpressionParser.expression();
if (init != null && init.kind() == Tree.Kind.ARRAY_INIT) {
@@ -885,7 +1052,8 @@ public class Parser {
break;
case DEFAULT:
{
- ConstExpressionParser cparser = new ConstExpressionParser(lexer, lexer.next());
+ ConstExpressionParser cparser =
+ new ConstExpressionParser(lexer, lexer.next(), lexer.position());
Tree expr = cparser.expression();
token = cparser.token;
if (expr == null && token == Token.AT) {
@@ -1359,7 +1527,7 @@ public class Parser {
if (token == Token.LPAREN) {
eat(LPAREN);
while (token != RPAREN) {
- ConstExpressionParser cparser = new ConstExpressionParser(lexer, token);
+ ConstExpressionParser cparser = new ConstExpressionParser(lexer, token, position);
Expression arg = cparser.expression();
if (arg == null) {
throw error(ErrorKind.INVALID_ANNOTATION_ARGUMENT);
@@ -1395,6 +1563,7 @@ public class Parser {
next();
}
+ @CanIgnoreReturnValue
private boolean maybe(Token kind) {
if (token == kind) {
next();
@@ -1403,7 +1572,6 @@ public class Parser {
return false;
}
- @CheckReturnValue
TurbineError error(Token token) {
switch (token) {
case IDENT:
@@ -1415,7 +1583,6 @@ public class Parser {
}
}
- @CheckReturnValue
private TurbineError error(ErrorKind kind, Object... args) {
return TurbineError.format(
lexer.source(),
diff --git a/java/com/google/turbine/parse/StreamLexer.java b/java/com/google/turbine/parse/StreamLexer.java
index 2e20c26..2348385 100644
--- a/java/com/google/turbine/parse/StreamLexer.java
+++ b/java/com/google/turbine/parse/StreamLexer.java
@@ -22,6 +22,7 @@ import static com.google.turbine.parse.UnicodeEscapePreprocessor.ASCII_SUB;
import com.google.turbine.diag.SourceFile;
import com.google.turbine.diag.TurbineError;
import com.google.turbine.diag.TurbineError.ErrorKind;
+import org.jspecify.nullness.Nullable;
/** A {@link Lexer} that streams input from a {@link UnicodeEscapePreprocessor}. */
public class StreamLexer implements Lexer {
@@ -29,7 +30,7 @@ public class StreamLexer implements Lexer {
private final UnicodeEscapePreprocessor reader;
/** The current input character. */
- private char ch;
+ private int ch;
/** The start position of the current token. */
private int position;
@@ -65,7 +66,7 @@ public class StreamLexer implements Lexer {
}
@Override
- public String javadoc() {
+ public @Nullable String javadoc() {
String result = javadoc;
javadoc = null;
if (result == null) {
@@ -353,7 +354,7 @@ public class StreamLexer implements Lexer {
eat();
return Token.ELLIPSIS;
} else {
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
}
}
case '0':
@@ -384,7 +385,7 @@ public class StreamLexer implements Lexer {
case '\'':
throw error(ErrorKind.EMPTY_CHARACTER_LITERAL);
default:
- value = ch;
+ value = (char) ch;
eat();
}
if (ch == '\'') {
@@ -419,7 +420,7 @@ public class StreamLexer implements Lexer {
}
// falls through
default:
- sb.append(ch);
+ sb.appendCodePoint(ch);
eat();
continue STRING;
}
@@ -430,7 +431,7 @@ public class StreamLexer implements Lexer {
// TODO(cushon): the style guide disallows non-ascii identifiers
return identifier();
}
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
}
}
}
@@ -511,7 +512,7 @@ public class StreamLexer implements Lexer {
}
}
default:
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
}
}
@@ -623,7 +624,7 @@ public class StreamLexer implements Lexer {
eat();
break;
default:
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
}
OUTER:
while (true) {
@@ -658,7 +659,7 @@ public class StreamLexer implements Lexer {
case '9':
continue OUTER;
default:
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
}
}
case 'A':
@@ -695,7 +696,7 @@ public class StreamLexer implements Lexer {
if ('0' <= ch && ch <= '9') {
eat();
} else {
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
}
OUTER:
while (true) {
@@ -707,7 +708,7 @@ public class StreamLexer implements Lexer {
if ('0' <= ch && ch <= '9') {
continue OUTER;
} else {
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
}
case '0':
case '1':
@@ -746,7 +747,7 @@ public class StreamLexer implements Lexer {
eat();
break;
default:
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
}
OUTER:
while (true) {
@@ -760,7 +761,7 @@ public class StreamLexer implements Lexer {
case '1':
continue OUTER;
default:
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
}
case '0':
case '1':
@@ -798,7 +799,7 @@ public class StreamLexer implements Lexer {
eat();
break;
default:
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
}
OUTER:
while (true) {
@@ -818,7 +819,7 @@ public class StreamLexer implements Lexer {
case '7':
continue OUTER;
default:
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
}
case '0':
case '1':
@@ -992,7 +993,7 @@ public class StreamLexer implements Lexer {
}
case '/':
// handled with comments
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
case '%':
eat();
@@ -1011,7 +1012,7 @@ public class StreamLexer implements Lexer {
return Token.XOR;
}
default:
- throw error(ErrorKind.UNEXPECTED_INPUT, ch);
+ throw inputError();
}
}
@@ -1141,6 +1142,12 @@ public class StreamLexer implements Lexer {
}
}
+ private TurbineError inputError() {
+ return error(
+ ErrorKind.UNEXPECTED_INPUT,
+ Character.isBmpCodePoint(ch) ? Character.toString((char) ch) : String.format("U+%X", ch));
+ }
+
private TurbineError error(ErrorKind kind, Object... args) {
return TurbineError.format(reader.source(), reader.position(), kind, args);
}
diff --git a/java/com/google/turbine/parse/Token.java b/java/com/google/turbine/parse/Token.java
index 7d20beb..ec214a5 100644
--- a/java/com/google/turbine/parse/Token.java
+++ b/java/com/google/turbine/parse/Token.java
@@ -23,8 +23,8 @@ public enum Token {
RPAREN(")"),
LBRACE("{"),
RBRACE("}"),
- LBRACK("<"),
- RBRACK(">"),
+ LBRACK("["),
+ RBRACK("]"),
EOF("<eof>"),
SEMI(";"),
COMMA(","),
diff --git a/java/com/google/turbine/parse/UnicodeEscapePreprocessor.java b/java/com/google/turbine/parse/UnicodeEscapePreprocessor.java
index 3f38561..4146ca5 100644
--- a/java/com/google/turbine/parse/UnicodeEscapePreprocessor.java
+++ b/java/com/google/turbine/parse/UnicodeEscapePreprocessor.java
@@ -30,7 +30,7 @@ public class UnicodeEscapePreprocessor {
private final String input;
private int idx = 0;
- private char ch;
+ private int ch;
private boolean evenLeadingSlashes = true;
public UnicodeEscapePreprocessor(SourceFile source) {
@@ -49,7 +49,7 @@ public class UnicodeEscapePreprocessor {
}
/** Returns the next unescaped Unicode input character. */
- public char next() {
+ public int next() {
eat();
if (ch == '\\' && evenLeadingSlashes) {
unicodeEscape();
@@ -88,7 +88,7 @@ public class UnicodeEscapePreprocessor {
}
/** Consumes a hex digit. */
- private int hexDigit(char d) {
+ private int hexDigit(int d) {
switch (d) {
case '0':
case '1':
@@ -130,8 +130,20 @@ public class UnicodeEscapePreprocessor {
* it terminates the input avoids some bounds checks in the lexer.
*/
private void eat() {
- ch = done() ? ASCII_SUB : input.charAt(idx);
+ char hi = done() ? ASCII_SUB : input.charAt(idx);
idx++;
+ if (!Character.isHighSurrogate(hi)) {
+ ch = hi;
+ return;
+ }
+ if (done()) {
+ throw error(ErrorKind.UNPAIRED_SURROGATE, (int) hi);
+ }
+ char lo = input.charAt(idx++);
+ if (!Character.isLowSurrogate(lo)) {
+ throw error(ErrorKind.UNPAIRED_SURROGATE, (int) hi);
+ }
+ ch = Character.toCodePoint(hi, lo);
}
public SourceFile source() {
diff --git a/java/com/google/turbine/parse/VariableInitializerParser.java b/java/com/google/turbine/parse/VariableInitializerParser.java
index 4ad9272..7f4d40e 100644
--- a/java/com/google/turbine/parse/VariableInitializerParser.java
+++ b/java/com/google/turbine/parse/VariableInitializerParser.java
@@ -40,10 +40,10 @@ import java.util.List;
* <p>That handles everything except multi-variable declarations (int x = 1, y = 2;), which in
* hindsight were probably a mistake. Multi-variable declarations contain a list of name and
* initializer pairs separated by commas. The initializer expressions may also contain commas, so
- * it's non-trivial to split on initializer boundaries. For example, consider `int x = a < b, c =
- * d;`. We can't tell looking at the prefix `a < b, c` whether that's a less-than expression
- * followed by another initializer, or the start of a generic type: `a<b, c>.foo()`. Distinguishing
- * between these cases requires arbitrary lookahead.
+ * it's non-trivial to split on initializer boundaries. For example, consider {@code int x = a < b,
+ * c = d;}. We can't tell looking at the prefix {@code a < b, c} whether that's a less-than
+ * expression followed by another initializer, or the start of a generic type: {@code a<b, c>.foo(}.
+ * Distinguishing between these cases requires arbitrary lookahead.
*
* <p>The preprocessor seems to be operationally correct. It's possible there are edge cases that it
* doesn't handle, but it's extremely rare for compile-time constant multi-variable declarations to
@@ -330,6 +330,8 @@ public class VariableInitializerParser {
depth--;
next();
break;
+ case EOF:
+ throw error(ErrorKind.UNEXPECTED_EOF);
default:
next();
break;
diff --git a/java/com/google/turbine/parse/package-info.java b/java/com/google/turbine/parse/package-info.java
new file mode 100644
index 0000000..ace7dcf
--- /dev/null
+++ b/java/com/google/turbine/parse/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+package com.google.turbine.parse;
diff --git a/java/com/google/turbine/processing/ModelFactory.java b/java/com/google/turbine/processing/ModelFactory.java
index 9b782cd..160d5ae 100644
--- a/java/com/google/turbine/processing/ModelFactory.java
+++ b/java/com/google/turbine/processing/ModelFactory.java
@@ -29,6 +29,7 @@ import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo;
import com.google.turbine.binder.bound.TypeBoundClass.ParamInfo;
+import com.google.turbine.binder.bound.TypeBoundClass.RecordComponentInfo;
import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo;
import com.google.turbine.binder.env.CompoundEnv;
import com.google.turbine.binder.env.Env;
@@ -40,6 +41,7 @@ import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.binder.sym.MethodSymbol;
import com.google.turbine.binder.sym.PackageSymbol;
import com.google.turbine.binder.sym.ParamSymbol;
+import com.google.turbine.binder.sym.RecordComponentSymbol;
import com.google.turbine.binder.sym.Symbol;
import com.google.turbine.binder.sym.TyVarSymbol;
import com.google.turbine.model.TurbineConstantTypeKind;
@@ -48,6 +50,7 @@ import com.google.turbine.processing.TurbineElement.TurbineFieldElement;
import com.google.turbine.processing.TurbineElement.TurbineNoTypeElement;
import com.google.turbine.processing.TurbineElement.TurbinePackageElement;
import com.google.turbine.processing.TurbineElement.TurbineParameterElement;
+import com.google.turbine.processing.TurbineElement.TurbineRecordComponentElement;
import com.google.turbine.processing.TurbineElement.TurbineTypeElement;
import com.google.turbine.processing.TurbineElement.TurbineTypeParameterElement;
import com.google.turbine.processing.TurbineTypeMirror.TurbineArrayType;
@@ -110,6 +113,8 @@ public class ModelFactory {
private final Map<MethodSymbol, TurbineExecutableElement> methodCache = new HashMap<>();
private final Map<ClassSymbol, TurbineTypeElement> classCache = new HashMap<>();
private final Map<ParamSymbol, TurbineParameterElement> paramCache = new HashMap<>();
+ private final Map<RecordComponentSymbol, TurbineRecordComponentElement> recordComponentCache =
+ new HashMap<>();
private final Map<TyVarSymbol, TurbineTypeParameterElement> tyParamCache = new HashMap<>();
private final Map<PackageSymbol, TurbinePackageElement> packageCache = new HashMap<>();
@@ -230,6 +235,8 @@ public class ModelFactory {
return fieldElement((FieldSymbol) symbol);
case PARAMETER:
return parameterElement((ParamSymbol) symbol);
+ case RECORD_COMPONENT:
+ return recordComponentElement((RecordComponentSymbol) symbol);
case PACKAGE:
return packageElement((PackageSymbol) symbol);
case MODULE:
@@ -263,6 +270,11 @@ public class ModelFactory {
return paramCache.computeIfAbsent(sym, k -> new TurbineParameterElement(this, sym));
}
+ VariableElement recordComponentElement(RecordComponentSymbol sym) {
+ return recordComponentCache.computeIfAbsent(
+ sym, k -> new TurbineRecordComponentElement(this, sym));
+ }
+
TurbineTypeParameterElement typeParameterElement(TyVarSymbol sym) {
return tyParamCache.computeIfAbsent(sym, k -> new TurbineTypeParameterElement(this, sym));
}
@@ -330,6 +342,16 @@ public class ModelFactory {
return null;
}
+ RecordComponentInfo getRecordComponentInfo(RecordComponentSymbol sym) {
+ TypeBoundClass info = getSymbol(sym.owner());
+ for (RecordComponentInfo component : info.components()) {
+ if (component.sym().equals(sym)) {
+ return component;
+ }
+ }
+ return null;
+ }
+
FieldInfo getFieldInfo(FieldSymbol symbol) {
TypeBoundClass info = getSymbol(symbol.owner());
requireNonNull(info, symbol.owner().toString());
@@ -370,6 +392,8 @@ public class ModelFactory {
return ((FieldSymbol) sym).owner();
case PARAMETER:
return ((ParamSymbol) sym).owner().owner();
+ case RECORD_COMPONENT:
+ return ((RecordComponentSymbol) sym).owner();
case PACKAGE:
case MODULE:
throw new IllegalArgumentException(sym.toString());
diff --git a/java/com/google/turbine/processing/TurbineAnnotationMirror.java b/java/com/google/turbine/processing/TurbineAnnotationMirror.java
index 5ea3de1..f99d211 100644
--- a/java/com/google/turbine/processing/TurbineAnnotationMirror.java
+++ b/java/com/google/turbine/processing/TurbineAnnotationMirror.java
@@ -18,6 +18,7 @@ package com.google.turbine.processing;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getLast;
+import static java.util.Objects.requireNonNull;
import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
@@ -44,6 +45,7 @@ import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ErrorType;
import javax.lang.model.type.TypeMirror;
+import org.jspecify.nullness.Nullable;
/**
* An implementation of {@link AnnotationMirror} and {@link AnnotationValue} backed by {@link
@@ -104,7 +106,7 @@ class TurbineAnnotationMirror implements TurbineAnnotationValueMirror, Annotatio
checkState(m.parameters().isEmpty());
result.put(m.name(), m);
}
- return result.build();
+ return result.buildOrThrow();
}
});
this.elementValues =
@@ -115,11 +117,16 @@ class TurbineAnnotationMirror implements TurbineAnnotationValueMirror, Annotatio
ImmutableMap.Builder<ExecutableElement, AnnotationValue> result =
ImmutableMap.builder();
for (Map.Entry<String, Const> value : anno.values().entrySet()) {
+ // requireNonNull is safe because `elements` contains an entry for every method.
+ // Any element values pairs without a corresponding method in the annotation
+ // definition are weeded out in ConstEvaluator.evaluateAnnotation, and don't
+ // appear in the AnnoInfo.
+ MethodInfo methodInfo = requireNonNull(elements.get().get(value.getKey()));
result.put(
- factory.executableElement(elements.get().get(value.getKey()).sym()),
+ factory.executableElement(methodInfo.sym()),
annotationValue(factory, value.getValue()));
}
- return result.build();
+ return result.buildOrThrow();
}
});
this.elementValuesWithDefaults =
@@ -150,7 +157,7 @@ class TurbineAnnotationMirror implements TurbineAnnotationValueMirror, Annotatio
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineAnnotationMirror
&& anno.equals(((TurbineAnnotationMirror) obj).anno);
}
@@ -336,7 +343,7 @@ class TurbineAnnotationMirror implements TurbineAnnotationValueMirror, Annotatio
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbinePrimitiveConstant
&& value.equals(((TurbinePrimitiveConstant) obj).value);
}
diff --git a/java/com/google/turbine/processing/TurbineAnnotationProxy.java b/java/com/google/turbine/processing/TurbineAnnotationProxy.java
index c39f310..967ead9 100644
--- a/java/com/google/turbine/processing/TurbineAnnotationProxy.java
+++ b/java/com/google/turbine/processing/TurbineAnnotationProxy.java
@@ -17,6 +17,7 @@
package com.google.turbine.processing;
import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
import com.google.turbine.binder.bound.EnumConstantValue;
import com.google.turbine.binder.bound.TurbineAnnotationValue;
@@ -131,14 +132,15 @@ class TurbineAnnotationProxy implements InvocationHandler {
private static Object constArrayValue(
Class<?> returnType, ModelFactory factory, ClassLoader loader, ArrayInitValue value) {
- if (returnType.getComponentType().equals(Class.class)) {
+ Class<?> componentType = requireNonNull(returnType.getComponentType());
+ if (componentType.equals(Class.class)) {
List<TypeMirror> result = new ArrayList<>();
for (Const element : value.elements()) {
result.add(factory.asTypeMirror(((TurbineClassValue) element).type()));
}
throw new MirroredTypesException(result);
}
- Object result = Array.newInstance(returnType.getComponentType(), value.elements().size());
+ Object result = Array.newInstance(componentType, value.elements().size());
int idx = 0;
for (Const element : value.elements()) {
Object v = constValue(returnType, factory, loader, element);
diff --git a/java/com/google/turbine/processing/TurbineElement.java b/java/com/google/turbine/processing/TurbineElement.java
index c22a442..95f0f42 100644
--- a/java/com/google/turbine/processing/TurbineElement.java
+++ b/java/com/google/turbine/processing/TurbineElement.java
@@ -21,6 +21,7 @@ import static java.util.Objects.requireNonNull;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -32,6 +33,7 @@ import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo;
import com.google.turbine.binder.bound.TypeBoundClass.ParamInfo;
+import com.google.turbine.binder.bound.TypeBoundClass.RecordComponentInfo;
import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo;
import com.google.turbine.binder.lookup.PackageScope;
import com.google.turbine.binder.sym.ClassSymbol;
@@ -39,6 +41,7 @@ import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.binder.sym.MethodSymbol;
import com.google.turbine.binder.sym.PackageSymbol;
import com.google.turbine.binder.sym.ParamSymbol;
+import com.google.turbine.binder.sym.RecordComponentSymbol;
import com.google.turbine.binder.sym.Symbol;
import com.google.turbine.binder.sym.TyVarSymbol;
import com.google.turbine.diag.TurbineError;
@@ -46,7 +49,7 @@ import com.google.turbine.diag.TurbineError.ErrorKind;
import com.google.turbine.model.Const;
import com.google.turbine.model.Const.ArrayInitValue;
import com.google.turbine.model.TurbineFlag;
-import com.google.turbine.model.TurbineTyKind;
+import com.google.turbine.tree.Tree;
import com.google.turbine.tree.Tree.MethDecl;
import com.google.turbine.tree.Tree.TyDecl;
import com.google.turbine.tree.Tree.VarDecl;
@@ -79,9 +82,10 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** An {@link Element} implementation backed by a {@link Symbol}. */
+@SuppressWarnings("nullness") // TODO(cushon): Address nullness diagnostics.
public abstract class TurbineElement implements Element {
public abstract Symbol sym();
@@ -92,7 +96,7 @@ public abstract class TurbineElement implements Element {
public abstract int hashCode();
@Override
- public abstract boolean equals(Object obj);
+ public abstract boolean equals(@Nullable Object obj);
protected final ModelFactory factory;
private final Supplier<ImmutableList<AnnotationMirror>> annotationMirrors;
@@ -158,7 +162,8 @@ public abstract class TurbineElement implements Element {
continue;
}
if (anno.sym().equals(metadata.repeatable())) {
- ArrayInitValue arrayValue = (ArrayInitValue) anno.values().get("value");
+ // requireNonNull is safe because java.lang.annotation.Repeatable declares `value`.
+ ArrayInitValue arrayValue = (ArrayInitValue) requireNonNull(anno.values().get("value"));
for (Const element : arrayValue.elements()) {
result.add(
TurbineAnnotationProxy.create(
@@ -258,15 +263,21 @@ public abstract class TurbineElement implements Element {
switch (info.kind()) {
case CLASS:
case ENUM:
+ case RECORD:
if (info.superclass() != null) {
return factory.asTypeMirror(info.superClassType());
}
if (info instanceof SourceTypeBoundClass) {
- // support simple name for stuff that doesn't exist
+ // support simple names for stuff that doesn't exist
TyDecl decl = ((SourceTypeBoundClass) info).decl();
if (decl.xtnds().isPresent()) {
- return factory.asTypeMirror(
- ErrorTy.create(decl.xtnds().get().name().value()));
+ ArrayDeque<Tree.Ident> flat = new ArrayDeque<>();
+ for (Tree.ClassTy curr = decl.xtnds().get();
+ curr != null;
+ curr = curr.base().orElse(null)) {
+ flat.addFirst(curr.name());
+ }
+ return factory.asTypeMirror(ErrorTy.create(flat));
}
}
return factory.noType();
@@ -369,10 +380,21 @@ public abstract class TurbineElement implements Element {
return ElementKind.ENUM;
case ANNOTATION:
return ElementKind.ANNOTATION_TYPE;
+ case RECORD:
+ return RECORD.get();
}
throw new AssertionError(info.kind());
}
+ private static final Supplier<ElementKind> RECORD =
+ Suppliers.memoize(
+ new Supplier<ElementKind>() {
+ @Override
+ public ElementKind get() {
+ return ElementKind.valueOf("RECORD");
+ }
+ });
+
@Override
public Set<Modifier> getModifiers() {
return asModifierSet(ModifierOwner.TYPE, infoNonNull().access() & ~TurbineFlag.ACC_SUPER);
@@ -420,6 +442,9 @@ public abstract class TurbineElement implements Element {
public ImmutableList<Element> get() {
TypeBoundClass info = infoNonNull();
ImmutableList.Builder<Element> result = ImmutableList.builder();
+ for (RecordComponentInfo component : info.components()) {
+ result.add(factory.recordComponentElement(component.sym()));
+ }
for (FieldInfo field : info.fields()) {
result.add(factory.fieldElement(field.sym()));
}
@@ -458,7 +483,7 @@ public abstract class TurbineElement implements Element {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineTypeElement && sym.equals(((TurbineTypeElement) obj).sym);
}
@@ -546,7 +571,7 @@ public abstract class TurbineElement implements Element {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineTypeParameterElement
&& sym.equals(((TurbineTypeParameterElement) obj).sym);
}
@@ -567,8 +592,7 @@ public abstract class TurbineElement implements Element {
}
});
- @Nullable
- private TyVarInfo info() {
+ private @Nullable TyVarInfo info() {
return info.get();
}
@@ -680,7 +704,7 @@ public abstract class TurbineElement implements Element {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineExecutableElement
&& sym.equals(((TurbineExecutableElement) obj).sym);
}
@@ -785,18 +809,12 @@ public abstract class TurbineElement implements Element {
@Override
public ElementKind getKind() {
- return info().name().equals("<init>") ? ElementKind.CONSTRUCTOR : ElementKind.METHOD;
+ return sym.name().equals("<init>") ? ElementKind.CONSTRUCTOR : ElementKind.METHOD;
}
@Override
public Set<Modifier> getModifiers() {
- int access = info().access();
- if (factory.getSymbol(info().sym().owner()).kind() == TurbineTyKind.INTERFACE) {
- if ((access & (TurbineFlag.ACC_ABSTRACT | TurbineFlag.ACC_STATIC)) == 0) {
- access |= TurbineFlag.ACC_DEFAULT;
- }
- }
- return asModifierSet(ModifierOwner.METHOD, access);
+ return asModifierSet(ModifierOwner.METHOD, info().access());
}
@Override
@@ -834,7 +852,7 @@ public abstract class TurbineElement implements Element {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineFieldElement && sym.equals(((TurbineFieldElement) obj).sym);
}
@@ -1032,6 +1050,7 @@ public abstract class TurbineElement implements Element {
public List<TurbineTypeElement> getEnclosedElements() {
ImmutableSet.Builder<TurbineTypeElement> result = ImmutableSet.builder();
PackageScope scope = factory.tli().lookupPackage(Splitter.on('/').split(sym.binaryName()));
+ requireNonNull(scope); // the current package exists
for (ClassSymbol key : scope.classes()) {
if (key.binaryName().contains("$") && factory.getSymbol(key).owner() != null) {
// Skip member classes: only top-level classes are enclosed by the package.
@@ -1067,7 +1086,7 @@ public abstract class TurbineElement implements Element {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbinePackageElement && sym.equals(((TurbinePackageElement) obj).sym);
}
@@ -1112,7 +1131,7 @@ public abstract class TurbineElement implements Element {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineParameterElement
&& sym.equals(((TurbineParameterElement) obj).sym);
}
@@ -1198,6 +1217,120 @@ public abstract class TurbineElement implements Element {
}
}
+ /** A {@link VariableElement} implementation for a record info. */
+ static class TurbineRecordComponentElement extends TurbineElement implements VariableElement {
+
+ @Override
+ public RecordComponentSymbol sym() {
+ return sym;
+ }
+
+ @Override
+ public String javadoc() {
+ return null;
+ }
+
+ @Override
+ public int hashCode() {
+ return sym.hashCode();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ return obj instanceof TurbineRecordComponentElement
+ && sym.equals(((TurbineRecordComponentElement) obj).sym);
+ }
+
+ private final RecordComponentSymbol sym;
+
+ private final Supplier<RecordComponentInfo> info =
+ memoize(
+ new Supplier<RecordComponentInfo>() {
+ @Override
+ public RecordComponentInfo get() {
+ return factory.getRecordComponentInfo(sym);
+ }
+ });
+
+ @Nullable
+ RecordComponentInfo info() {
+ return info.get();
+ }
+
+ public TurbineRecordComponentElement(ModelFactory factory, RecordComponentSymbol sym) {
+ super(factory);
+ this.sym = sym;
+ }
+
+ @Override
+ public Object getConstantValue() {
+ return null;
+ }
+
+ private final Supplier<TypeMirror> type =
+ memoize(
+ new Supplier<TypeMirror>() {
+ @Override
+ public TypeMirror get() {
+ return factory.asTypeMirror(info().type());
+ }
+ });
+
+ @Override
+ public TypeMirror asType() {
+ return type.get();
+ }
+
+ @Override
+ public ElementKind getKind() {
+ return RECORD_COMPONENT.get();
+ }
+
+ private static final Supplier<ElementKind> RECORD_COMPONENT =
+ Suppliers.memoize(
+ new Supplier<ElementKind>() {
+ @Override
+ public ElementKind get() {
+ return ElementKind.valueOf("RECORD_COMPONENT");
+ }
+ });
+
+ @Override
+ public Set<Modifier> getModifiers() {
+ return asModifierSet(ModifierOwner.PARAMETER, info().access());
+ }
+
+ @Override
+ public Name getSimpleName() {
+ return new TurbineName(sym.name());
+ }
+
+ @Override
+ public Element getEnclosingElement() {
+ return factory.typeElement(sym.owner());
+ }
+
+ @Override
+ public List<? extends Element> getEnclosedElements() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public <R, P> R accept(ElementVisitor<R, P> v, P p) {
+ return v.visitVariable(this, p);
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(sym.name());
+ }
+
+ @Override
+ protected ImmutableList<AnnoInfo> annos() {
+ return info().annotations();
+ }
+ }
+
static class TurbineNoTypeElement implements TypeElement {
private final ModelFactory factory;
diff --git a/java/com/google/turbine/processing/TurbineElements.java b/java/com/google/turbine/processing/TurbineElements.java
index 9da210e..b5fd7f4 100644
--- a/java/com/google/turbine/processing/TurbineElements.java
+++ b/java/com/google/turbine/processing/TurbineElements.java
@@ -29,6 +29,7 @@ import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.binder.sym.PackageSymbol;
import com.google.turbine.binder.sym.Symbol;
import com.google.turbine.model.Const;
+import com.google.turbine.model.TurbineFlag;
import com.google.turbine.model.TurbineVisibility;
import com.google.turbine.processing.TurbineElement.TurbineExecutableElement;
import com.google.turbine.processing.TurbineElement.TurbineFieldElement;
@@ -52,8 +53,10 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
+import org.jspecify.nullness.Nullable;
/** An implementation of {@link Elements} backed by turbine's {@link Element}. */
+@SuppressWarnings("nullness") // TODO(cushon): Address nullness diagnostics.
public class TurbineElements implements Elements {
private final ModelFactory factory;
@@ -131,7 +134,7 @@ public class TurbineElements implements Elements {
if (!(element instanceof TurbineElement)) {
throw new IllegalArgumentException(element.toString());
}
- for (AnnoInfo a : ((TurbineTypeElement) element).annos()) {
+ for (AnnoInfo a : ((TurbineElement) element).annos()) {
if (a.sym().equals(ClassSymbol.DEPRECATED)) {
return true;
}
@@ -265,8 +268,8 @@ public class TurbineElements implements Elements {
}
/**
- * Returns true if an element with the given {@code visibility} and located in package {@from} is
- * visible to elements in package {@code to}.
+ * Returns true if an element with the given {@code visibility} and located in package {@code
+ * from} is visible to elements in package {@code to}.
*/
private static boolean isVisible(
PackageSymbol from, PackageSymbol to, TurbineVisibility visibility) {
@@ -289,7 +292,89 @@ public class TurbineElements implements Elements {
@Override
public boolean hides(Element hider, Element hidden) {
- throw new UnsupportedOperationException();
+ if (!(hider instanceof TurbineElement)) {
+ throw new IllegalArgumentException(hider.toString());
+ }
+ if (!(hidden instanceof TurbineElement)) {
+ throw new IllegalArgumentException(hidden.toString());
+ }
+ return hides((TurbineElement) hider, (TurbineElement) hidden);
+ }
+
+ private boolean hides(TurbineElement hider, TurbineElement hidden) {
+ if (!hider.sym().symKind().equals(hidden.sym().symKind())) {
+ return false;
+ }
+ if (!hider.getSimpleName().equals(hidden.getSimpleName())) {
+ return false;
+ }
+ if (hider.sym().equals(hidden.sym())) {
+ return false;
+ }
+ if (!isVisibleForHiding(hider, hidden)) {
+ return false;
+ }
+ if (hider.sym().symKind().equals(Symbol.Kind.METHOD)) {
+ int access = ((TurbineExecutableElement) hider).info().access();
+ if ((access & TurbineFlag.ACC_STATIC) != TurbineFlag.ACC_STATIC) {
+ return false;
+ }
+ // Static interface methods shouldn't be able to hide static methods in super-interfaces,
+ // but include them anyways for bug-compatibility with javac, see:
+ // https://bugs.openjdk.java.net/browse/JDK-8275746
+ if (!types.isSubsignature(
+ (TurbineExecutableType) hider.asType(), (TurbineExecutableType) hidden.asType())) {
+ return false;
+ }
+ }
+ Element containingHider = containingClass(hider);
+ Element containingHidden = containingClass(hidden);
+ if (containingHider == null || containingHidden == null) {
+ return false;
+ }
+ if (!types.isSubtype(containingHider.asType(), containingHidden.asType())) {
+ return false;
+ }
+ return true;
+ }
+
+ private static @Nullable Element containingClass(TurbineElement element) {
+ Element enclosing = element.getEnclosingElement();
+ if (enclosing == null) {
+ return null;
+ }
+ if (!isClassOrInterface(enclosing.getKind())) {
+ // The immediately enclosing element of a field or method is a class. For classes, annotation
+ // processing only deals with top-level and nested (but not local or anonymous) classes,
+ // so the immediately enclosing element is either an enclosing class or a package symbol.
+ return null;
+ }
+ return enclosing;
+ }
+
+ private static boolean isClassOrInterface(ElementKind kind) {
+ return kind.isClass() || kind.isInterface();
+ }
+
+ private static boolean isVisibleForHiding(TurbineElement hider, TurbineElement hidden) {
+ int access;
+ switch (hidden.sym().symKind()) {
+ case CLASS:
+ access = ((TurbineTypeElement) hidden).info().access();
+ break;
+ case FIELD:
+ access = ((TurbineFieldElement) hidden).info().access();
+ break;
+ case METHOD:
+ access = ((TurbineExecutableElement) hidden).info().access();
+ break;
+ default:
+ return false;
+ }
+ return isVisible(
+ packageSymbol(asSymbol(hider)),
+ packageSymbol(asSymbol(hidden)),
+ TurbineVisibility.fromAccess(access));
}
@Override
diff --git a/java/com/google/turbine/processing/TurbineFiler.java b/java/com/google/turbine/processing/TurbineFiler.java
index 186eb7f..45cdc22 100644
--- a/java/com/google/turbine/processing/TurbineFiler.java
+++ b/java/com/google/turbine/processing/TurbineFiler.java
@@ -18,6 +18,7 @@ package com.google.turbine.processing;
import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
@@ -361,7 +362,7 @@ public class TurbineFiler implements Filer {
@Override
public URI toUri() {
try {
- return loader.getResource(path).toURI();
+ return requireNonNull(loader.getResource(path)).toURI();
} catch (URISyntaxException e) {
throw new AssertionError(e);
}
diff --git a/java/com/google/turbine/processing/TurbineMessager.java b/java/com/google/turbine/processing/TurbineMessager.java
index 9c333b2..8e78b8b 100644
--- a/java/com/google/turbine/processing/TurbineMessager.java
+++ b/java/com/google/turbine/processing/TurbineMessager.java
@@ -42,7 +42,7 @@ import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.tools.Diagnostic;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** Turbine's implementation of {@link Messager}. */
public class TurbineMessager implements Messager {
@@ -103,8 +103,7 @@ public class TurbineMessager implements Messager {
* Returns the {@link SourceFile} that contains the declaration of the given {@link Symbol}, or
* {@code null} if the symbol was not compiled from source.
*/
- @Nullable
- private SourceFile getSource(Symbol sym) {
+ private @Nullable SourceFile getSource(Symbol sym) {
ClassSymbol encl = ModelFactory.enclosingClass(sym);
TypeBoundClass info = factory.getSymbol(encl);
if (!(info instanceof SourceTypeBoundClass)) {
@@ -129,6 +128,10 @@ public class TurbineMessager implements Messager {
return fieldPosition((FieldSymbol) sym);
case PARAMETER:
return paramPosition((ParamSymbol) sym);
+ case RECORD_COMPONENT:
+ // javac doesn't seem to provide diagnostic positions for record components, so we don't
+ // either
+ return -1;
case MODULE:
case PACKAGE:
break;
diff --git a/java/com/google/turbine/processing/TurbineName.java b/java/com/google/turbine/processing/TurbineName.java
index 584b1b1..5232491 100644
--- a/java/com/google/turbine/processing/TurbineName.java
+++ b/java/com/google/turbine/processing/TurbineName.java
@@ -19,6 +19,7 @@ package com.google.turbine.processing;
import static java.util.Objects.requireNonNull;
import javax.lang.model.element.Name;
+import org.jspecify.nullness.Nullable;
/** An implementation of {@link Name} backed by a {@link CharSequence}. */
public class TurbineName implements Name {
@@ -61,7 +62,7 @@ public class TurbineName implements Name {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineName && contentEquals(((TurbineName) obj).name);
}
}
diff --git a/java/com/google/turbine/processing/TurbineProcessingEnvironment.java b/java/com/google/turbine/processing/TurbineProcessingEnvironment.java
index 726d075..4f32033 100644
--- a/java/com/google/turbine/processing/TurbineProcessingEnvironment.java
+++ b/java/com/google/turbine/processing/TurbineProcessingEnvironment.java
@@ -24,9 +24,9 @@ import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
-/** Turbine's {@link ProcessingEnvironment). */
+/** Turbine's {@link ProcessingEnvironment}. */
public class TurbineProcessingEnvironment implements ProcessingEnvironment {
private final Filer filer;
diff --git a/java/com/google/turbine/processing/TurbineTypeMirror.java b/java/com/google/turbine/processing/TurbineTypeMirror.java
index e94672c..4cd8ba1 100644
--- a/java/com/google/turbine/processing/TurbineTypeMirror.java
+++ b/java/com/google/turbine/processing/TurbineTypeMirror.java
@@ -58,6 +58,7 @@ 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 org.jspecify.nullness.Nullable;
/** A {@link TypeMirror} implementation backed by a {@link Type}. */
public abstract class TurbineTypeMirror implements TypeMirror {
@@ -165,7 +166,7 @@ public abstract class TurbineTypeMirror implements TypeMirror {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineDeclaredType && type.equals(((TurbineDeclaredType) obj).type);
}
@@ -377,7 +378,7 @@ public abstract class TurbineTypeMirror implements TypeMirror {
}
@Override
- public boolean equals(Object other) {
+ public boolean equals(@Nullable Object other) {
return other instanceof TurbinePackageType
&& symbol.equals(((TurbinePackageType) other).symbol);
}
@@ -421,7 +422,7 @@ public abstract class TurbineTypeMirror implements TypeMirror {
}
@Override
- public boolean equals(Object other) {
+ public boolean equals(@Nullable Object other) {
return other instanceof TurbineNoType;
}
@@ -473,7 +474,7 @@ public abstract class TurbineTypeMirror implements TypeMirror {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineTypeVariable && type.equals(((TurbineTypeVariable) obj).type);
}
@@ -566,7 +567,7 @@ public abstract class TurbineTypeMirror implements TypeMirror {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineWildcardType && type.equals(((TurbineWildcardType) obj).type);
}
@@ -607,7 +608,7 @@ public abstract class TurbineTypeMirror implements TypeMirror {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineIntersectionType
&& type.equals(((TurbineIntersectionType) obj).type);
}
@@ -670,7 +671,7 @@ public abstract class TurbineTypeMirror implements TypeMirror {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof NullType;
}
@@ -711,7 +712,7 @@ public abstract class TurbineTypeMirror implements TypeMirror {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
return obj instanceof TurbineExecutableType
&& type.equals(((TurbineExecutableType) obj).type);
}
diff --git a/java/com/google/turbine/processing/TurbineTypes.java b/java/com/google/turbine/processing/TurbineTypes.java
index f65f921..d2068dd 100644
--- a/java/com/google/turbine/processing/TurbineTypes.java
+++ b/java/com/google/turbine/processing/TurbineTypes.java
@@ -29,6 +29,7 @@ import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.binder.sym.MethodSymbol;
import com.google.turbine.binder.sym.ParamSymbol;
+import com.google.turbine.binder.sym.RecordComponentSymbol;
import com.google.turbine.binder.sym.Symbol;
import com.google.turbine.binder.sym.TyVarSymbol;
import com.google.turbine.model.TurbineConstantTypeKind;
@@ -68,9 +69,10 @@ import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.Types;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** An implementation of {@link Types} backed by turbine's {@link TypeMirror}. */
+@SuppressWarnings("nullness") // TODO(cushon): Address nullness diagnostics.
public class TurbineTypes implements Types {
private final ModelFactory factory;
@@ -217,8 +219,15 @@ public class TurbineTypes implements Types {
if (bounds.isEmpty()) {
return true;
}
- ClassTy first = (ClassTy) bounds.get(0);
- return factory.getSymbol(first.sym()).kind().equals(TurbineTyKind.INTERFACE);
+ Type bound = bounds.get(0);
+ switch (bound.tyKind()) {
+ case TY_VAR:
+ return false;
+ case CLASS_TY:
+ return factory.getSymbol(((ClassTy) bound).sym()).kind().equals(TurbineTyKind.INTERFACE);
+ default:
+ throw new AssertionError(bound.tyKind());
+ }
}
private boolean isSameWildType(WildTy a, Type other) {
@@ -364,8 +373,8 @@ public class TurbineTypes implements Types {
}
private boolean isTyVarSubtype(TyVar a, Type b, boolean strict) {
- if (b.tyKind() == TyKind.TY_VAR) {
- return a.sym().equals(((TyVar) b).sym());
+ if (b.tyKind() == TyKind.TY_VAR && a.sym().equals(((TyVar) b).sym())) {
+ return true;
}
TyVarInfo tyVarInfo = factory.getTyVarInfo(a.sym());
return isSubtype(tyVarInfo.upperBound(), b, strict);
@@ -520,11 +529,12 @@ public class TurbineTypes implements Types {
}
/**
- * Given two parameterizations of the same {@link SimpleClassTy}, {@code a} and {@code b}, teturns
+ * Given two parameterizations of the same {@link SimpleClassTy}, {@code a} and {@code b}, returns
* true if the type arguments of {@code a} are pairwise contained by the type arguments of {@code
* b}.
*
- * @see {@link #contains} and JLS 4.5.1.
+ * @see #contains
+ * @see "JLS 4.5.1"
*/
private boolean tyArgsContains(SimpleClassTy a, SimpleClassTy b, boolean strict) {
verify(a.sym().equals(b.sym()));
@@ -624,8 +634,7 @@ public class TurbineTypes implements Types {
* Returns a mapping that can be used to adapt the signature 'b' to the type parameters of 'a', or
* {@code null} if no such mapping exists.
*/
- @Nullable
- private static ImmutableMap<TyVarSymbol, Type> getMapping(MethodTy a, MethodTy b) {
+ private static @Nullable ImmutableMap<TyVarSymbol, Type> getMapping(MethodTy a, MethodTy b) {
if (a.tyParams().size() != b.tyParams().size()) {
return null;
}
@@ -637,15 +646,14 @@ public class TurbineTypes implements Types {
TyVarSymbol t = bx.next();
mapping.put(t, TyVar.create(s, ImmutableList.of()));
}
- return mapping.build();
+ return mapping.buildOrThrow();
}
/**
* Returns a map from formal type parameters to their arguments for a given class type, or an
* empty map for non-parameterized types, or {@code null} for raw types.
*/
- @Nullable
- private ImmutableMap<TyVarSymbol, Type> getMapping(ClassTy ty) {
+ private @Nullable ImmutableMap<TyVarSymbol, Type> getMapping(ClassTy ty) {
ImmutableMap.Builder<TyVarSymbol, Type> mapping = ImmutableMap.builder();
for (SimpleClassTy s : ty.classes()) {
TypeBoundClass info = factory.getSymbol(s.sym());
@@ -659,7 +667,7 @@ public class TurbineTypes implements Types {
}
verify(!bx.hasNext());
}
- return mapping.build();
+ return mapping.buildOrThrow();
}
@Override
@@ -825,7 +833,7 @@ public class TurbineTypes implements Types {
@Override
public List<? extends TypeMirror> directSupertypes(TypeMirror m) {
- return factory.asTypeMirrors(directSupertypes(asTurbineType(m)));
+ return factory.asTypeMirrors(deannotate(directSupertypes(asTurbineType(m))));
}
public ImmutableList<Type> directSupertypes(Type t) {
@@ -882,7 +890,12 @@ public class TurbineTypes implements Types {
@Override
public TypeMirror erasure(TypeMirror typeMirror) {
- return factory.asTypeMirror(erasure(asTurbineType(typeMirror)));
+ Type t = erasure(asTurbineType(typeMirror));
+ if (t.tyKind() == TyKind.CLASS_TY) {
+ // bug-parity with javac
+ t = deannotate(t);
+ }
+ return factory.asTypeMirror(t);
}
private Type erasure(Type type) {
@@ -896,6 +909,50 @@ public class TurbineTypes implements Types {
});
}
+ /**
+ * Remove some type annotation metadata for bug-compatibility with javac, which does this
+ * inconsistently (see https://bugs.openjdk.java.net/browse/JDK-8042981).
+ */
+ private static Type deannotate(Type ty) {
+ switch (ty.tyKind()) {
+ case CLASS_TY:
+ return deannotateClassTy((Type.ClassTy) ty);
+ case ARRAY_TY:
+ return deannotateArrayTy((Type.ArrayTy) ty);
+ case TY_VAR:
+ case INTERSECTION_TY:
+ case WILD_TY:
+ case METHOD_TY:
+ case PRIM_TY:
+ case VOID_TY:
+ case ERROR_TY:
+ case NONE_TY:
+ return ty;
+ }
+ throw new AssertionError(ty.tyKind());
+ }
+
+ private static ImmutableList<Type> deannotate(ImmutableList<Type> types) {
+ ImmutableList.Builder<Type> result = ImmutableList.builder();
+ for (Type type : types) {
+ result.add(deannotate(type));
+ }
+ return result.build();
+ }
+
+ private static Type.ArrayTy deannotateArrayTy(Type.ArrayTy ty) {
+ return ArrayTy.create(deannotate(ty.elementType()), /* annos= */ ImmutableList.of());
+ }
+
+ public static Type.ClassTy deannotateClassTy(Type.ClassTy ty) {
+ ImmutableList.Builder<Type.ClassTy.SimpleClassTy> classes = ImmutableList.builder();
+ for (Type.ClassTy.SimpleClassTy c : ty.classes()) {
+ classes.add(
+ SimpleClassTy.create(c.sym(), deannotate(c.targs()), /* annos= */ ImmutableList.of()));
+ }
+ return ClassTy.create(classes.build());
+ }
+
@Override
public TypeElement boxedClass(PrimitiveType p) {
return factory.typeElement(boxedClass(((PrimTy) asTurbineType(p)).primkind()));
@@ -1082,6 +1139,8 @@ public class TurbineTypes implements Types {
return ((FieldSymbol) symbol).owner();
case PARAMETER:
return ((ParamSymbol) symbol).owner().owner();
+ case RECORD_COMPONENT:
+ return ((RecordComponentSymbol) symbol).owner();
case MODULE:
case PACKAGE:
throw new IllegalArgumentException(symbol.symKind().toString());
diff --git a/java/com/google/turbine/processing/package-info.java b/java/com/google/turbine/processing/package-info.java
new file mode 100644
index 0000000..abf6732
--- /dev/null
+++ b/java/com/google/turbine/processing/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+package com.google.turbine.processing;
diff --git a/java/com/google/turbine/tree/Pretty.java b/java/com/google/turbine/tree/Pretty.java
index b693a42..4ebc04f 100644
--- a/java/com/google/turbine/tree/Pretty.java
+++ b/java/com/google/turbine/tree/Pretty.java
@@ -20,6 +20,8 @@ import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.tree.Tree.Anno;
import com.google.turbine.tree.Tree.ClassLiteral;
import com.google.turbine.tree.Tree.Ident;
@@ -33,9 +35,10 @@ import com.google.turbine.tree.Tree.ModUses;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.jspecify.nullness.Nullable;
/** A pretty-printer for {@link Tree}s. */
-public class Pretty implements Tree.Visitor<Void, Void> {
+public class Pretty implements Tree.Visitor<@Nullable Void, @Nullable Void> {
static String pretty(Tree tree) {
Pretty pretty = new Pretty();
@@ -60,6 +63,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
newLine = true;
}
+ @CanIgnoreReturnValue
Pretty append(char c) {
if (c == '\n') {
newLine = true;
@@ -71,6 +75,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
return this;
}
+ @CanIgnoreReturnValue
Pretty append(String s) {
if (newLine) {
sb.append(Strings.repeat(" ", indent * 2));
@@ -81,13 +86,13 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitIdent(Ident ident, Void input) {
+ public @Nullable Void visitIdent(Ident ident, @Nullable Void input) {
sb.append(ident.value());
return null;
}
@Override
- public Void visitWildTy(Tree.WildTy wildTy, Void input) {
+ public @Nullable Void visitWildTy(Tree.WildTy wildTy, @Nullable Void input) {
printAnnos(wildTy.annos());
append('?');
if (wildTy.lower().isPresent()) {
@@ -102,7 +107,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitArrTy(Tree.ArrTy arrTy, Void input) {
+ public @Nullable Void visitArrTy(Tree.ArrTy arrTy, @Nullable Void input) {
arrTy.elem().accept(this, null);
if (!arrTy.annos().isEmpty()) {
append(' ');
@@ -113,19 +118,19 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitPrimTy(Tree.PrimTy primTy, Void input) {
+ public @Nullable Void visitPrimTy(Tree.PrimTy primTy, @Nullable Void input) {
append(primTy.tykind().toString());
return null;
}
@Override
- public Void visitVoidTy(Tree.VoidTy primTy, Void input) {
+ public @Nullable Void visitVoidTy(Tree.VoidTy voidTy, @Nullable Void input) {
append("void");
return null;
}
@Override
- public Void visitClassTy(Tree.ClassTy classTy, Void input) {
+ public @Nullable Void visitClassTy(Tree.ClassTy classTy, @Nullable Void input) {
if (classTy.base().isPresent()) {
classTy.base().get().accept(this, null);
append('.');
@@ -148,13 +153,19 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitLiteral(Tree.Literal literal, Void input) {
+ public @Nullable Void visitLiteral(Tree.Literal literal, @Nullable Void input) {
append(literal.value().toString());
return null;
}
@Override
- public Void visitTypeCast(Tree.TypeCast typeCast, Void input) {
+ public @Nullable Void visitParen(Tree.Paren paren, @Nullable Void input) {
+ paren.expr().accept(this, null);
+ return null;
+ }
+
+ @Override
+ public @Nullable Void visitTypeCast(Tree.TypeCast typeCast, @Nullable Void input) {
append('(');
typeCast.ty().accept(this, null);
append(") ");
@@ -163,7 +174,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitUnary(Tree.Unary unary, Void input) {
+ public @Nullable Void visitUnary(Tree.Unary unary, @Nullable Void input) {
switch (unary.op()) {
case POST_INCR:
case POST_DECR:
@@ -186,37 +197,42 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitBinary(Tree.Binary binary, Void input) {
+ public @Nullable Void visitBinary(Tree.Binary binary, @Nullable Void input) {
append('(');
- binary.lhs().accept(this, null);
- append(" " + binary.op() + " ");
- binary.rhs().accept(this, null);
+ boolean first = true;
+ for (Tree child : binary.children()) {
+ if (!first) {
+ append(" ").append(binary.op().toString()).append(" ");
+ }
+ child.accept(this, null);
+ first = false;
+ }
append(')');
return null;
}
@Override
- public Void visitConstVarName(Tree.ConstVarName constVarName, Void input) {
+ public @Nullable Void visitConstVarName(Tree.ConstVarName constVarName, @Nullable Void input) {
append(Joiner.on('.').join(constVarName.name()));
return null;
}
@Override
- public Void visitClassLiteral(ClassLiteral classLiteral, Void input) {
- classLiteral.accept(this, input);
+ public @Nullable Void visitClassLiteral(ClassLiteral classLiteral, @Nullable Void input) {
+ classLiteral.type().accept(this, input);
append(".class");
return null;
}
@Override
- public Void visitAssign(Tree.Assign assign, Void input) {
+ public @Nullable Void visitAssign(Tree.Assign assign, @Nullable Void input) {
append(assign.name().value()).append(" = ");
assign.expr().accept(this, null);
return null;
}
@Override
- public Void visitConditional(Tree.Conditional conditional, Void input) {
+ public @Nullable Void visitConditional(Tree.Conditional conditional, @Nullable Void input) {
append("(");
conditional.cond().accept(this, null);
append(" ? ");
@@ -228,7 +244,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitArrayInit(Tree.ArrayInit arrayInit, Void input) {
+ public @Nullable Void visitArrayInit(Tree.ArrayInit arrayInit, @Nullable Void input) {
append('{');
boolean first = true;
for (Tree.Expression e : arrayInit.exprs()) {
@@ -243,7 +259,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitCompUnit(Tree.CompUnit compUnit, Void input) {
+ public @Nullable Void visitCompUnit(Tree.CompUnit compUnit, @Nullable Void input) {
if (compUnit.pkg().isPresent()) {
compUnit.pkg().get().accept(this, null);
printLine();
@@ -263,7 +279,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitImportDecl(Tree.ImportDecl importDecl, Void input) {
+ public @Nullable Void visitImportDecl(Tree.ImportDecl importDecl, @Nullable Void input) {
append("import ");
if (importDecl.stat()) {
append("static ");
@@ -277,7 +293,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitVarDecl(Tree.VarDecl varDecl, Void input) {
+ public @Nullable Void visitVarDecl(Tree.VarDecl varDecl, @Nullable Void input) {
printVarDecl(varDecl);
append(';');
return null;
@@ -302,7 +318,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitMethDecl(Tree.MethDecl methDecl, Void input) {
+ public @Nullable Void visitMethDecl(Tree.MethDecl methDecl, @Nullable Void input) {
for (Tree.Anno anno : methDecl.annos()) {
anno.accept(this, null);
printLine();
@@ -361,7 +377,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitAnno(Tree.Anno anno, Void input) {
+ public @Nullable Void visitAnno(Tree.Anno anno, @Nullable Void input) {
append('@');
append(Joiner.on('.').join(anno.name()));
if (!anno.args().isEmpty()) {
@@ -380,7 +396,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitTyDecl(Tree.TyDecl tyDecl, Void input) {
+ public @Nullable Void visitTyDecl(Tree.TyDecl tyDecl, @Nullable Void input) {
for (Tree.Anno anno : tyDecl.annos()) {
anno.accept(this, null);
printLine();
@@ -399,6 +415,9 @@ public class Pretty implements Tree.Visitor<Void, Void> {
case ANNOTATION:
append("@interface");
break;
+ case RECORD:
+ append("record");
+ break;
}
append(' ').append(tyDecl.name().value());
if (!tyDecl.typarams().isEmpty()) {
@@ -413,6 +432,18 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
append('>');
}
+ if (tyDecl.tykind().equals(TurbineTyKind.RECORD)) {
+ append("(");
+ boolean first = true;
+ for (Tree.VarDecl c : tyDecl.components()) {
+ if (!first) {
+ append(", ");
+ }
+ printVarDecl(c);
+ first = false;
+ }
+ append(")");
+ }
if (tyDecl.xtnds().isPresent()) {
append(" extends ");
tyDecl.xtnds().get().accept(this, null);
@@ -428,6 +459,17 @@ public class Pretty implements Tree.Visitor<Void, Void> {
first = false;
}
}
+ if (!tyDecl.permits().isEmpty()) {
+ append(" permits ");
+ boolean first = true;
+ for (Tree.ClassTy t : tyDecl.permits()) {
+ if (!first) {
+ append(", ");
+ }
+ t.accept(this, null);
+ first = false;
+ }
+ }
append(" {").append('\n');
indent++;
switch (tyDecl.tykind()) {
@@ -491,6 +533,8 @@ public class Pretty implements Tree.Visitor<Void, Void> {
case TRANSIENT:
case DEFAULT:
case TRANSITIVE:
+ case SEALED:
+ case NON_SEALED:
append(mod.toString()).append(' ');
break;
case ACC_SUPER:
@@ -500,13 +544,14 @@ public class Pretty implements Tree.Visitor<Void, Void> {
case ACC_ANNOTATION:
case ACC_SYNTHETIC:
case ACC_BRIDGE:
+ case COMPACT_CTOR:
break;
}
}
}
@Override
- public Void visitTyParam(Tree.TyParam tyParam, Void input) {
+ public @Nullable Void visitTyParam(Tree.TyParam tyParam, @Nullable Void input) {
printAnnos(tyParam.annos());
append(tyParam.name().value());
if (!tyParam.bounds().isEmpty()) {
@@ -524,7 +569,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitPkgDecl(Tree.PkgDecl pkgDecl, Void input) {
+ public @Nullable Void visitPkgDecl(Tree.PkgDecl pkgDecl, @Nullable Void input) {
for (Tree.Anno anno : pkgDecl.annos()) {
anno.accept(this, null);
printLine();
@@ -534,7 +579,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitModDecl(ModDecl modDecl, Void input) {
+ public @Nullable Void visitModDecl(ModDecl modDecl, @Nullable Void input) {
for (Tree.Anno anno : modDecl.annos()) {
anno.accept(this, null);
printLine();
@@ -554,7 +599,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitModRequires(ModRequires modRequires, Void input) {
+ public @Nullable Void visitModRequires(ModRequires modRequires, @Nullable Void input) {
append("requires ");
printModifiers(modRequires.mods());
append(modRequires.moduleName());
@@ -564,7 +609,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitModExports(ModExports modExports, Void input) {
+ public @Nullable Void visitModExports(ModExports modExports, @Nullable Void input) {
append("exports ");
append(modExports.packageName().replace('/', '.'));
if (!modExports.moduleNames().isEmpty()) {
@@ -586,7 +631,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitModOpens(ModOpens modOpens, Void input) {
+ public @Nullable Void visitModOpens(ModOpens modOpens, @Nullable Void input) {
append("opens ");
append(modOpens.packageName().replace('/', '.'));
if (!modOpens.moduleNames().isEmpty()) {
@@ -608,7 +653,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitModUses(ModUses modUses, Void input) {
+ public @Nullable Void visitModUses(ModUses modUses, @Nullable Void input) {
append("uses ");
append(Joiner.on('.').join(modUses.typeName()));
append(";");
@@ -617,7 +662,7 @@ public class Pretty implements Tree.Visitor<Void, Void> {
}
@Override
- public Void visitModProvides(ModProvides modProvides, Void input) {
+ public @Nullable Void visitModProvides(ModProvides modProvides, @Nullable Void input) {
append("provides ");
append(Joiner.on('.').join(modProvides.typeName()));
if (!modProvides.implNames().isEmpty()) {
diff --git a/java/com/google/turbine/tree/Tree.java b/java/com/google/turbine/tree/Tree.java
index d36c3ab..f7917b9 100644
--- a/java/com/google/turbine/tree/Tree.java
+++ b/java/com/google/turbine/tree/Tree.java
@@ -25,15 +25,19 @@ import com.google.turbine.diag.SourceFile;
import com.google.turbine.model.Const;
import com.google.turbine.model.TurbineConstantTypeKind;
import com.google.turbine.model.TurbineTyKind;
+import java.util.ArrayDeque;
+import java.util.Deque;
import java.util.Optional;
import java.util.Set;
+import org.jspecify.nullness.Nullable;
/** An AST node. */
public abstract class Tree {
public abstract Kind kind();
- public abstract <I, O> O accept(Visitor<I, O> visitor, I input);
+ public abstract <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input);
private final int position;
@@ -59,6 +63,7 @@ public abstract class Tree {
VOID_TY,
CLASS_TY,
LITERAL,
+ PAREN,
TYPE_CAST,
UNARY,
BINARY,
@@ -101,7 +106,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitIdent(this, input);
}
@@ -154,7 +160,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitWildTy(this, input);
}
@@ -192,7 +199,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitArrTy(this, input);
}
@@ -221,7 +229,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitPrimTy(this, input);
}
@@ -240,7 +249,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitVoidTy(this, input);
}
@@ -273,7 +283,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitClassTy(this, input);
}
@@ -314,7 +325,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitLiteral(this, input);
}
@@ -327,6 +339,31 @@ public abstract class Tree {
}
}
+ /** A JLS 15.8.5 parenthesized expression. */
+ public static class Paren extends Expression {
+ private final Expression expr;
+
+ public Paren(int position, Expression expr) {
+ super(position);
+ this.expr = expr;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.PAREN;
+ }
+
+ @Override
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
+ return visitor.visitParen(this, input);
+ }
+
+ public Expression expr() {
+ return expr;
+ }
+ }
+
/** A JLS 15.16 cast expression. */
public static class TypeCast extends Expression {
private final Type ty;
@@ -344,7 +381,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitTypeCast(this, input);
}
@@ -374,7 +412,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitUnary(this, input);
}
@@ -406,16 +445,29 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitBinary(this, input);
}
- public Expression lhs() {
- return lhs;
- }
-
- public Expression rhs() {
- return rhs;
+ public Iterable<Expression> children() {
+ ImmutableList.Builder<Expression> children = ImmutableList.builder();
+ Deque<Expression> stack = new ArrayDeque<>();
+ stack.addFirst(rhs);
+ stack.addFirst(lhs);
+ while (!stack.isEmpty()) {
+ Expression curr = stack.removeFirst();
+ if (curr.kind().equals(Kind.BINARY)) {
+ Binary b = ((Binary) curr);
+ if (b.op().equals(op())) {
+ stack.addFirst(b.rhs);
+ stack.addFirst(b.lhs);
+ continue;
+ }
+ }
+ children.add(curr);
+ }
+ return children.build();
}
public TurbineOperatorKind op() {
@@ -438,7 +490,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitConstVarName(this, input);
}
@@ -463,7 +516,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitClassLiteral(this, input);
}
@@ -489,7 +543,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitAssign(this, input);
}
@@ -521,7 +576,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitConditional(this, input);
}
@@ -553,7 +609,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitArrayInit(this, input);
}
@@ -591,7 +648,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitCompUnit(this, input);
}
@@ -635,7 +693,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitImportDecl(this, input);
}
@@ -661,7 +720,7 @@ public abstract class Tree {
private final Tree ty;
private final Ident name;
private final Optional<Expression> init;
- private final String javadoc;
+ private final @Nullable String javadoc;
public VarDecl(
int position,
@@ -670,7 +729,7 @@ public abstract class Tree {
Tree ty,
Ident name,
Optional<Expression> init,
- String javadoc) {
+ @Nullable String javadoc) {
super(position);
this.mods = ImmutableSet.copyOf(mods);
this.annos = annos;
@@ -686,7 +745,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitVarDecl(this, input);
}
@@ -714,7 +774,7 @@ public abstract class Tree {
* A javadoc comment, excluding the opening and closing delimiters but including all interior
* characters and whitespace.
*/
- public String javadoc() {
+ public @Nullable String javadoc() {
return javadoc;
}
}
@@ -760,7 +820,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitMethDecl(this, input);
}
@@ -821,7 +882,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitAnno(this, input);
}
@@ -858,7 +920,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitAnno(value, input);
}
}
@@ -871,9 +934,11 @@ public abstract class Tree {
private final ImmutableList<TyParam> typarams;
private final Optional<ClassTy> xtnds;
private final ImmutableList<ClassTy> impls;
+ private final ImmutableList<ClassTy> permits;
private final ImmutableList<Tree> members;
+ private final ImmutableList<VarDecl> components;
private final TurbineTyKind tykind;
- private final String javadoc;
+ private final @Nullable String javadoc;
public TyDecl(
int position,
@@ -883,9 +948,11 @@ public abstract class Tree {
ImmutableList<TyParam> typarams,
Optional<ClassTy> xtnds,
ImmutableList<ClassTy> impls,
+ ImmutableList<ClassTy> permits,
ImmutableList<Tree> members,
+ ImmutableList<VarDecl> components,
TurbineTyKind tykind,
- String javadoc) {
+ @Nullable String javadoc) {
super(position);
this.mods = ImmutableSet.copyOf(mods);
this.annos = annos;
@@ -893,7 +960,9 @@ public abstract class Tree {
this.typarams = typarams;
this.xtnds = xtnds;
this.impls = impls;
+ this.permits = permits;
this.members = members;
+ this.components = components;
this.tykind = tykind;
this.javadoc = javadoc;
}
@@ -904,7 +973,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitTyDecl(this, input);
}
@@ -932,10 +1002,18 @@ public abstract class Tree {
return impls;
}
+ public ImmutableList<ClassTy> permits() {
+ return permits;
+ }
+
public ImmutableList<Tree> members() {
return members;
}
+ public ImmutableList<VarDecl> components() {
+ return components;
+ }
+
public TurbineTyKind tykind() {
return tykind;
}
@@ -943,7 +1021,7 @@ public abstract class Tree {
* A javadoc comment, excluding the opening and closing delimiters but including all interior
* characters and whitespace.
*/
- public String javadoc() {
+ public @Nullable String javadoc() {
return javadoc;
}
}
@@ -968,7 +1046,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitTyParam(this, input);
}
@@ -1002,7 +1081,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitPkgDecl(this, input);
}
@@ -1058,7 +1138,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitModDecl(this, input);
}
}
@@ -1094,7 +1175,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitModRequires(this, input);
}
@@ -1130,7 +1212,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitModExports(this, input);
}
@@ -1180,7 +1263,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitModOpens(this, input);
}
@@ -1210,7 +1294,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitModUses(this, input);
}
@@ -1249,7 +1334,8 @@ public abstract class Tree {
}
@Override
- public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ public <I extends @Nullable Object, O extends @Nullable Object> O accept(
+ Visitor<I, O> visitor, I input) {
return visitor.visitModProvides(this, input);
}
@@ -1260,7 +1346,7 @@ public abstract class Tree {
}
/** A visitor for {@link Tree}s. */
- public interface Visitor<I, O> {
+ public interface Visitor<I extends @Nullable Object, O extends @Nullable Object> {
O visitIdent(Ident ident, I input);
O visitWildTy(WildTy visitor, I input);
@@ -1275,6 +1361,8 @@ public abstract class Tree {
O visitLiteral(Literal literal, I input);
+ O visitParen(Paren unary, I input);
+
O visitTypeCast(TypeCast typeCast, I input);
O visitUnary(Unary unary, I input);
diff --git a/java/com/google/turbine/tree/TurbineModifier.java b/java/com/google/turbine/tree/TurbineModifier.java
index 35dc11c..2bfe53e 100644
--- a/java/com/google/turbine/tree/TurbineModifier.java
+++ b/java/com/google/turbine/tree/TurbineModifier.java
@@ -45,7 +45,10 @@ public enum TurbineModifier {
ACC_SYNTHETIC(TurbineFlag.ACC_SYNTHETIC),
ACC_BRIDGE(TurbineFlag.ACC_BRIDGE),
DEFAULT(TurbineFlag.ACC_DEFAULT),
- TRANSITIVE(TurbineFlag.ACC_TRANSITIVE);
+ TRANSITIVE(TurbineFlag.ACC_TRANSITIVE),
+ SEALED(TurbineFlag.ACC_SEALED),
+ NON_SEALED(TurbineFlag.ACC_NON_SEALED),
+ COMPACT_CTOR(TurbineFlag.ACC_COMPACT_CTOR);
private final int flag;
@@ -59,6 +62,6 @@ public enum TurbineModifier {
@Override
public String toString() {
- return name().toLowerCase(ENGLISH);
+ return name().replace('_', '-').toLowerCase(ENGLISH);
}
}
diff --git a/java/com/google/turbine/tree/package-info.java b/java/com/google/turbine/tree/package-info.java
new file mode 100644
index 0000000..2803c67
--- /dev/null
+++ b/java/com/google/turbine/tree/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+@org.jspecify.nullness.NullMarked
+package com.google.turbine.tree;
diff --git a/java/com/google/turbine/type/AnnoInfo.java b/java/com/google/turbine/type/AnnoInfo.java
index ff902b3..d42af5c 100644
--- a/java/com/google/turbine/type/AnnoInfo.java
+++ b/java/com/google/turbine/type/AnnoInfo.java
@@ -29,6 +29,7 @@ import com.google.turbine.tree.Tree.Anno;
import com.google.turbine.tree.Tree.Expression;
import java.util.Map;
import java.util.Objects;
+import org.jspecify.nullness.Nullable;
/** An annotation use. */
public class AnnoInfo {
@@ -84,7 +85,7 @@ public class AnnoInfo {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof AnnoInfo)) {
return false;
}
diff --git a/java/com/google/turbine/type/Type.java b/java/com/google/turbine/type/Type.java
index daba2ae..085346a 100644
--- a/java/com/google/turbine/type/Type.java
+++ b/java/com/google/turbine/type/Type.java
@@ -32,7 +32,7 @@ import com.google.turbine.tree.Tree;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/** JLS 4 types. */
public interface Type {
@@ -194,7 +194,7 @@ public interface Type {
}
@Override
- public final boolean equals(Object obj) {
+ public final boolean equals(@Nullable Object obj) {
if (!(obj instanceof ClassTy)) {
return false;
}
@@ -246,11 +246,14 @@ public interface Type {
@Override
public final String toString() {
StringBuilder sb = new StringBuilder();
- for (AnnoInfo anno : annos()) {
- sb.append(anno);
+ sb.append(elementType());
+ if (!annos().isEmpty()) {
sb.append(' ');
+ for (AnnoInfo anno : annos()) {
+ sb.append(anno);
+ sb.append(' ');
+ }
}
- sb.append(elementType());
sb.append("[]");
return sb.toString();
}
@@ -488,8 +491,7 @@ public interface Type {
public abstract Type returnType();
/** The type of the receiver parameter (see JLS 8.4.1). */
- @Nullable
- public abstract Type receiverType();
+ public abstract @Nullable Type receiverType();
public abstract ImmutableList<Type> parameters();
@@ -574,7 +576,7 @@ public interface Type {
}
@Override
- public final boolean equals(Object other) {
+ public final boolean equals(@Nullable Object other) {
// The name associated with an error type is context for use in diagnostics or by annotations
// processors. Two error types with the same name don't necessarily represent the same type.
diff --git a/java/com/google/turbine/type/package-info.java b/java/com/google/turbine/type/package-info.java
new file mode 100644
index 0000000..2329130
--- /dev/null
+++ b/java/com/google/turbine/type/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+package com.google.turbine.type;
diff --git a/java/com/google/turbine/types/Canonicalize.java b/java/com/google/turbine/types/Canonicalize.java
index 22df069..f944bb5 100644
--- a/java/com/google/turbine/types/Canonicalize.java
+++ b/java/com/google/turbine/types/Canonicalize.java
@@ -16,6 +16,8 @@
package com.google.turbine.types;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
import com.google.turbine.binder.bound.TypeBoundClass;
@@ -44,7 +46,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
/**
* Canonicalizes qualified type names so qualifiers are always the declaring class of the qualified
@@ -208,7 +210,8 @@ public class Canonicalize {
return ClassTy.create(ImmutableList.of(ty));
}
ImmutableList.Builder<ClassTy.SimpleClassTy> simples = ImmutableList.builder();
- ClassSymbol owner = getInfo(ty.sym()).owner();
+ // this inner class is known to have an owner
+ ClassSymbol owner = requireNonNull(getInfo(ty.sym()).owner());
if (owner.equals(base.sym())) {
// if the canonical prefix is the owner the next symbol in the qualified name,
// the type is already in canonical form
@@ -281,7 +284,7 @@ public class Canonicalize {
}
/** Instantiates a type argument using the given mapping. */
- private static Type instantiate(Map<TyVarSymbol, Type> mapping, Type type) {
+ private static @Nullable Type instantiate(Map<TyVarSymbol, Type> mapping, Type type) {
if (type == null) {
return null;
}
@@ -328,7 +331,8 @@ public class Canonicalize {
for (SimpleClassTy simple : type.classes()) {
ImmutableList.Builder<Type> args = ImmutableList.builder();
for (Type arg : simple.targs()) {
- args.add(instantiate(mapping, arg));
+ // result is non-null if arg is
+ args.add(requireNonNull(instantiate(mapping, arg)));
}
simples.add(SimpleClassTy.create(simple.sym(), args.build(), simple.annos()));
}
@@ -339,8 +343,7 @@ public class Canonicalize {
* Returns the type variable symbol for a concrete type argument whose type is a type variable
* reference, or else {@code null}.
*/
- @Nullable
- private static TyVarSymbol tyVarSym(Type type) {
+ private static @Nullable TyVarSymbol tyVarSym(Type type) {
if (type.tyKind() == TyKind.TY_VAR) {
return ((TyVar) type).sym();
}
diff --git a/java/com/google/turbine/types/Deannotate.java b/java/com/google/turbine/types/Deannotate.java
new file mode 100644
index 0000000..1edb11f
--- /dev/null
+++ b/java/com/google/turbine/types/Deannotate.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.google.turbine.types;
+
+import com.google.common.collect.ImmutableList;
+import com.google.turbine.type.Type;
+
+/** Removes type annotation metadata. */
+public class Deannotate {
+ public static Type deannotate(Type ty) {
+ switch (ty.tyKind()) {
+ case CLASS_TY:
+ return deannotateClassTy((Type.ClassTy) ty);
+ case ARRAY_TY:
+ return Type.ArrayTy.create(
+ deannotate(((Type.ArrayTy) ty).elementType()), ImmutableList.of());
+ case INTERSECTION_TY:
+ return Type.IntersectionTy.create(deannotate(((Type.IntersectionTy) ty).bounds()));
+ case WILD_TY:
+ return deannotateWildTy((Type.WildTy) ty);
+ case METHOD_TY:
+ return deannotateMethodTy((Type.MethodTy) ty);
+ case PRIM_TY:
+ return Type.PrimTy.create(((Type.PrimTy) ty).primkind(), ImmutableList.of());
+ case TY_VAR:
+ return Type.TyVar.create(((Type.TyVar) ty).sym(), ImmutableList.of());
+ case VOID_TY:
+ case ERROR_TY:
+ case NONE_TY:
+ return ty;
+ }
+ throw new AssertionError(ty.tyKind());
+ }
+
+ private static ImmutableList<Type> deannotate(ImmutableList<Type> types) {
+ ImmutableList.Builder<Type> result = ImmutableList.builder();
+ for (Type type : types) {
+ result.add(deannotate(type));
+ }
+ return result.build();
+ }
+
+ public static Type.ClassTy deannotateClassTy(Type.ClassTy ty) {
+ ImmutableList.Builder<Type.ClassTy.SimpleClassTy> classes = ImmutableList.builder();
+ for (Type.ClassTy.SimpleClassTy c : ty.classes()) {
+ classes.add(
+ Type.ClassTy.SimpleClassTy.create(c.sym(), deannotate(c.targs()), ImmutableList.of()));
+ }
+ return Type.ClassTy.create(classes.build());
+ }
+
+ private static Type deannotateWildTy(Type.WildTy ty) {
+ switch (ty.boundKind()) {
+ case NONE:
+ return Type.WildUnboundedTy.create(ImmutableList.of());
+ case LOWER:
+ return Type.WildLowerBoundedTy.create(ty.bound(), ImmutableList.of());
+ case UPPER:
+ return Type.WildUpperBoundedTy.create(ty.bound(), ImmutableList.of());
+ }
+ throw new AssertionError(ty.boundKind());
+ }
+
+ private static Type deannotateMethodTy(Type.MethodTy ty) {
+ return Type.MethodTy.create(
+ ty.tyParams(),
+ deannotate(ty.returnType()),
+ ty.receiverType() != null ? deannotate(ty.receiverType()) : null,
+ deannotate(ty.parameters()),
+ deannotate(ty.thrown()));
+ }
+
+ private Deannotate() {}
+}
diff --git a/java/com/google/turbine/types/Erasure.java b/java/com/google/turbine/types/Erasure.java
index 9042897..4b6fbc1 100644
--- a/java/com/google/turbine/types/Erasure.java
+++ b/java/com/google/turbine/types/Erasure.java
@@ -19,7 +19,6 @@ package com.google.turbine.types;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
-import com.google.turbine.binder.bound.SourceTypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo;
import com.google.turbine.binder.sym.TyVarSymbol;
import com.google.turbine.type.Type;
@@ -32,8 +31,8 @@ import com.google.turbine.type.Type.TyVar;
import com.google.turbine.type.Type.WildTy;
/** Generic type erasure. */
-public class Erasure {
- public static Type erase(Type ty, Function<TyVarSymbol, SourceTypeBoundClass.TyVarInfo> tenv) {
+public final class Erasure {
+ public static Type erase(Type ty, Function<TyVarSymbol, TyVarInfo> tenv) {
switch (ty.tyKind()) {
case CLASS_TY:
return eraseClassTy((Type.ClassTy) ty);
@@ -70,14 +69,12 @@ public class Erasure {
return ty.bounds().isEmpty() ? ClassTy.OBJECT : erase(ty.bounds().get(0), tenv);
}
- private static Type eraseTyVar(
- TyVar ty, Function<TyVarSymbol, SourceTypeBoundClass.TyVarInfo> tenv) {
- SourceTypeBoundClass.TyVarInfo info = tenv.apply(ty.sym());
+ private static Type eraseTyVar(TyVar ty, Function<TyVarSymbol, TyVarInfo> tenv) {
+ TyVarInfo info = tenv.apply(ty.sym());
return erase(info.upperBound(), tenv);
}
- private static Type.ArrayTy eraseArrayTy(
- Type.ArrayTy ty, Function<TyVarSymbol, SourceTypeBoundClass.TyVarInfo> tenv) {
+ private static Type.ArrayTy eraseArrayTy(Type.ArrayTy ty, Function<TyVarSymbol, TyVarInfo> tenv) {
return ArrayTy.create(erase(ty.elementType(), tenv), ty.annos());
}
@@ -112,4 +109,6 @@ public class Erasure {
erase(ty.parameters(), tenv),
erase(ty.thrown(), tenv));
}
+
+ private Erasure() {}
}
diff --git a/java/com/google/turbine/types/package-info.java b/java/com/google/turbine/types/package-info.java
new file mode 100644
index 0000000..fd541d7
--- /dev/null
+++ b/java/com/google/turbine/types/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+package com.google.turbine.types;
diff --git a/java/com/google/turbine/zip/Zip.java b/java/com/google/turbine/zip/Zip.java
index 48d4697..fa0f0e0 100644
--- a/java/com/google/turbine/zip/Zip.java
+++ b/java/com/google/turbine/zip/Zip.java
@@ -71,7 +71,7 @@ import java.util.zip.ZipException;
* header is present only if ENDTOT in EOCD header is 0xFFFF.
* </ul>
*/
-public class Zip {
+public final class Zip {
static final int ZIP64_ENDSIG = 0x06064b50;
@@ -335,4 +335,6 @@ public class Zip {
&& (buf.get(index + 2) == i)
&& (buf.get(index + 3) == j);
}
+
+ private Zip() {}
}
diff --git a/java/com/google/turbine/zip/package-info.java b/java/com/google/turbine/zip/package-info.java
new file mode 100644
index 0000000..069e5e1
--- /dev/null
+++ b/java/com/google/turbine/zip/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@com.google.errorprone.annotations.CheckReturnValue
+package com.google.turbine.zip;
diff --git a/javatests/com/google/turbine/binder/BinderErrorTest.java b/javatests/com/google/turbine/binder/BinderErrorTest.java
index 15b54eb..6766470 100644
--- a/javatests/com/google/turbine/binder/BinderErrorTest.java
+++ b/javatests/com/google/turbine/binder/BinderErrorTest.java
@@ -18,7 +18,7 @@ package com.google.turbine.binder;
import static com.google.common.truth.Truth.assertThat;
import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
@@ -93,6 +93,9 @@ public class BinderErrorTest {
"<>:2: error: could not resolve element foo() in Anno", //
"@Anno(foo=100, bar=200) class Test {}",
" ^",
+ "<>:2: error: could not resolve element bar() in Anno", //
+ "@Anno(foo=100, bar=200) class Test {}",
+ " ^",
},
},
{
@@ -558,9 +561,12 @@ public class BinderErrorTest {
"class T {}",
},
{
- "<>:7: error: could not resolve B", //
+ "<>:7: error: could not resolve B",
"@One.A(b = {@B})",
" ^",
+ "<>:7: error: could not evaluate constant expression",
+ "@One.A(b = {@B})",
+ " ^",
},
},
{
@@ -700,6 +706,258 @@ public class BinderErrorTest {
" ^",
},
},
+ {
+ {
+ "import java.util.List;",
+ "class T {", //
+ " List<int> xs = new ArrayList<>();",
+ "}",
+ },
+ {
+ "<>:3: error: unexpected type int", //
+ " List<int> xs = new ArrayList<>();",
+ " ^",
+ },
+ },
+ {
+ {
+ "@interface A {",
+ " int[] xs() default {};",
+ "}",
+ "@A(xs = Object.class)",
+ "class T {",
+ "}",
+ },
+ {
+ "<>:4: error: could not evaluate constant expression",
+ "@A(xs = Object.class)",
+ " ^",
+ },
+ },
+ {
+ {
+ "package foobar;",
+ "import java.lang.annotation.Retention;",
+ "@Retention",
+ "@interface Test {}",
+ },
+ {
+ "<>:3: error: missing required annotation argument: value", //
+ "@Retention",
+ "^",
+ },
+ },
+ {
+ {
+ "interface Test {", //
+ " static final void f() {}",
+ "}",
+ },
+ {
+ "<>:2: error: unexpected modifier: final", //
+ " static final void f() {}",
+ " ^",
+ },
+ },
+ {
+ {
+ "package foobar;",
+ "import java.lang.annotation.Retention;",
+ "@Retention",
+ "@Retention",
+ "@interface Test {}",
+ },
+ {
+ "<>:3: error: missing required annotation argument: value",
+ "@Retention",
+ "^",
+ },
+ },
+ {
+ {
+ "import java.util.List;", //
+ "class Test {",
+ " @interface A {}",
+ " void f(List<@NoSuch int> xs) {}",
+ "}",
+ },
+ {
+ "<>:4: error: could not resolve NoSuch",
+ " void f(List<@NoSuch int> xs) {}",
+ " ^",
+ "<>:4: error: unexpected type int",
+ " void f(List<@NoSuch int> xs) {}",
+ " ^",
+ },
+ },
+ {
+ {
+ "@interface B {}",
+ "@interface A {",
+ " B[] value() default @B;",
+ "}",
+ "interface C {}",
+ "@A(value = @C)",
+ "class T {}",
+ },
+ {
+ "<>:6: error: C is not an annotation", //
+ "@A(value = @C)",
+ " ^",
+ },
+ },
+ {
+ {
+ "@interface A {",
+ " boolean x();",
+ " boolean value();",
+ "}",
+ "@A(x = true, false)",
+ "class T {}",
+ },
+ {
+ "<>:5: error: expected an annotation value of the form name=value",
+ "@A(x = true, false)",
+ " ^",
+ },
+ },
+ {
+ {
+ "@interface A {",
+ " boolean value();",
+ "}",
+ "class B {",
+ " static final String X = \"hello\";",
+ "}",
+ "@A(B.X)",
+ "class T {}",
+ },
+ {
+ "<>:7: error: value \"hello\" of type String cannot be converted to boolean",
+ "@A(B.X)",
+ " ^",
+ },
+ },
+ {
+ {
+ "class T {", //
+ " public static final boolean b = true == 42;",
+ "}",
+ },
+ {
+ "<>:2: error: value 42 of type int cannot be converted to boolean",
+ " public static final boolean b = true == 42;",
+ " ^",
+ },
+ },
+ {
+ {
+ "class T {", //
+ " public static final byte b = (byte) \"hello\";",
+ "}",
+ },
+ {
+ "<>:2: error: value \"hello\" of type String cannot be converted to byte",
+ " public static final byte b = (byte) \"hello\";",
+ " ^",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " public static final char c = (char) \"hello\";",
+ "}",
+ },
+ {
+ "<>:2: error: value \"hello\" of type String cannot be converted to char",
+ " public static final char c = (char) \"hello\";",
+ " ^",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " public static final short s = (short) \"hello\";",
+ "}",
+ },
+ {
+ "<>:2: error: value \"hello\" of type String cannot be converted to short",
+ " public static final short s = (short) \"hello\";",
+ " ^",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " public static final int i = (int) \"hello\";",
+ "}",
+ },
+ {
+ "<>:2: error: value \"hello\" of type String cannot be converted to int",
+ " public static final int i = (int) \"hello\";",
+ " ^",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " public static final long l = (long) \"hello\";",
+ "}",
+ },
+ {
+ "<>:2: error: value \"hello\" of type String cannot be converted to long",
+ " public static final long l = (long) \"hello\";",
+ " ^",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " public static final float f = (float) \"hello\";",
+ "}",
+ },
+ {
+ "<>:2: error: value \"hello\" of type String cannot be converted to float",
+ " public static final float f = (float) \"hello\";",
+ " ^",
+ }
+ },
+ {
+ {
+ "class T {", //
+ " public static final double d = (double) \"hello\";",
+ "}",
+ },
+ {
+ "<>:2: error: value \"hello\" of type String cannot be converted to double",
+ " public static final double d = (double) \"hello\";",
+ " ^",
+ },
+ },
+ {
+ {
+ "class T {", //
+ " public static final boolean X = \"1\" == 2;",
+ "}",
+ },
+ {
+ "<>:2: error: value 2 of type int cannot be converted to String",
+ " public static final boolean X = \"1\" == 2;",
+ " ^",
+ },
+ },
+ {
+ {
+ "class T {", //
+ " public static final boolean X = \"1\" != 2;",
+ "}",
+ },
+ {
+ "<>:2: error: value 2 of type int cannot be converted to String",
+ " public static final boolean X = \"1\" != 2;",
+ " ^",
+ },
+ },
};
return Arrays.asList((Object[][]) testCases);
}
@@ -714,17 +972,18 @@ public class BinderErrorTest {
@Test
public void test() throws Exception {
- try {
- Binder.bind(
- ImmutableList.of(parseLines(source)),
- ClassPathBinder.bindClasspath(ImmutableList.of()),
- TURBINE_BOOTCLASSPATH,
- /* moduleVersion=*/ Optional.empty())
- .units();
- fail(Joiner.on('\n').join(source));
- } catch (TurbineError e) {
- assertThat(e).hasMessageThat().isEqualTo(lines(expected));
- }
+ TurbineError e =
+ assertThrows(
+ Joiner.on('\n').join(source),
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ ImmutableList.of(parseLines(source)),
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.empty())
+ .units());
+ assertThat(e).hasMessageThat().isEqualTo(lines(expected));
}
@SupportedAnnotationTypes("*")
@@ -744,22 +1003,23 @@ public class BinderErrorTest {
// exercise error reporting with annotation enabled, which should be identical
@Test
public void testWithProcessors() throws Exception {
- try {
- Binder.bind(
- ImmutableList.of(parseLines(source)),
- ClassPathBinder.bindClasspath(ImmutableList.of()),
- ProcessorInfo.create(
- ImmutableList.of(new HelloWorldProcessor()),
- /* loader= */ getClass().getClassLoader(),
- /* options= */ ImmutableMap.of(),
- SourceVersion.latestSupported()),
- TURBINE_BOOTCLASSPATH,
- /* moduleVersion=*/ Optional.empty())
- .units();
- fail(Joiner.on('\n').join(source));
- } catch (TurbineError e) {
- assertThat(e).hasMessageThat().isEqualTo(lines(expected));
- }
+ TurbineError e =
+ assertThrows(
+ Joiner.on('\n').join(source),
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ ImmutableList.of(parseLines(source)),
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ ProcessorInfo.create(
+ ImmutableList.of(new HelloWorldProcessor()),
+ /* loader= */ getClass().getClassLoader(),
+ /* options= */ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.empty())
+ .units());
+ assertThat(e).hasMessageThat().isEqualTo(lines(expected));
}
private static CompUnit parseLines(String... lines) {
diff --git a/javatests/com/google/turbine/binder/BinderTest.java b/javatests/com/google/turbine/binder/BinderTest.java
index e238ee0..40387ac 100644
--- a/javatests/com/google/turbine/binder/BinderTest.java
+++ b/javatests/com/google/turbine/binder/BinderTest.java
@@ -16,16 +16,15 @@
package com.google.turbine.binder;
-import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH;
-import static org.junit.Assert.fail;
+import static java.util.Objects.requireNonNull;
+import static org.junit.Assert.assertThrows;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.turbine.binder.bound.SourceTypeBoundClass;
-import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.diag.TurbineError;
import com.google.turbine.lower.IntegrationTestSupport;
@@ -84,17 +83,16 @@ public class BinderTest {
new ClassSymbol("a/A$Inner2"),
new ClassSymbol("b/B"));
- SourceTypeBoundClass a = bound.get(new ClassSymbol("a/A"));
+ SourceTypeBoundClass a = getBoundClass(bound, "a/A");
assertThat(a.superclass()).isEqualTo(new ClassSymbol("java/lang/Object"));
assertThat(a.interfaces()).isEmpty();
- assertThat(bound.get(new ClassSymbol("a/A$Inner1")).superclass())
- .isEqualTo(new ClassSymbol("b/B"));
+ assertThat(getBoundClass(bound, "a/A$Inner1").superclass()).isEqualTo(new ClassSymbol("b/B"));
- assertThat(bound.get(new ClassSymbol("a/A$Inner2")).superclass())
+ assertThat(getBoundClass(bound, "a/A$Inner2").superclass())
.isEqualTo(new ClassSymbol("a/A$Inner1"));
- SourceTypeBoundClass b = bound.get(new ClassSymbol("b/B"));
+ SourceTypeBoundClass b = getBoundClass(bound, "b/B");
assertThat(b.superclass()).isEqualTo(new ClassSymbol("a/A"));
}
@@ -129,12 +127,12 @@ public class BinderTest {
new ClassSymbol("b/B"),
new ClassSymbol("b/B$BInner"));
- assertThat(bound.get(new ClassSymbol("b/B")).interfaces())
+ assertThat(getBoundClass(bound, "b/B").interfaces())
.containsExactly(new ClassSymbol("com/i/I"));
- assertThat(bound.get(new ClassSymbol("b/B$BInner")).superclass())
+ assertThat(getBoundClass(bound, "b/B$BInner").superclass())
.isEqualTo(new ClassSymbol("com/i/I$IInner"));
- assertThat(bound.get(new ClassSymbol("b/B$BInner")).interfaces()).isEmpty();
+ assertThat(getBoundClass(bound, "b/B$BInner").interfaces()).isEmpty();
}
@Test
@@ -161,7 +159,7 @@ public class BinderTest {
/* moduleVersion=*/ Optional.empty())
.units();
- assertThat(bound.get(new ClassSymbol("other/Foo")).superclass())
+ assertThat(getBoundClass(bound, "other/Foo").superclass())
.isEqualTo(new ClassSymbol("com/test/Test$Inner"));
}
@@ -182,16 +180,16 @@ public class BinderTest {
" class Inner {}",
"}"));
- try {
- Binder.bind(
- units,
- ClassPathBinder.bindClasspath(ImmutableList.of()),
- TURBINE_BOOTCLASSPATH,
- /* moduleVersion=*/ Optional.empty());
- fail();
- } catch (TurbineError e) {
- assertThat(e).hasMessageThat().contains("cycle in class hierarchy: a.A -> b.B -> a.A");
- }
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.empty()));
+ assertThat(e).hasMessageThat().contains("cycle in class hierarchy: a.A -> b.B -> a.A");
}
@Test
@@ -211,7 +209,7 @@ public class BinderTest {
/* moduleVersion=*/ Optional.empty())
.units();
- SourceTypeBoundClass a = bound.get(new ClassSymbol("com/test/Annotation"));
+ SourceTypeBoundClass a = getBoundClass(bound, "com/test/Annotation");
assertThat(a.access())
.isEqualTo(
TurbineFlag.ACC_PUBLIC
@@ -240,7 +238,7 @@ public class BinderTest {
/* moduleVersion=*/ Optional.empty())
.units();
- SourceTypeBoundClass a = bound.get(new ClassSymbol("a/A"));
+ SourceTypeBoundClass a = getBoundClass(bound, "a/A");
assertThat(a.interfaces()).containsExactly(new ClassSymbol("java/util/Map$Entry"));
}
@@ -259,7 +257,7 @@ public class BinderTest {
try (OutputStream os = Files.newOutputStream(libJar);
JarOutputStream jos = new JarOutputStream(os)) {
jos.putNextEntry(new JarEntry("B.class"));
- jos.write(lib.get("B"));
+ jos.write(requireNonNull(lib.get("B")));
}
ImmutableList<Tree.CompUnit> units =
@@ -280,39 +278,17 @@ public class BinderTest {
/* moduleVersion=*/ Optional.empty())
.units();
- SourceTypeBoundClass a = bound.get(new ClassSymbol("C$A"));
+ SourceTypeBoundClass a = getBoundClass(bound, "C$A");
assertThat(a.annotationMetadata().target()).containsExactly(TurbineElementType.TYPE_USE);
}
- // Test that we don't crash on invalid constant field initializers.
- // (Error reporting is deferred to javac.)
- @Test
- public void invalidConst() throws Exception {
- ImmutableList<Tree.CompUnit> units =
- ImmutableList.of(
- parseLines(
- "package a;", //
- "public class A {",
- " public static final boolean b = true == 42;",
- "}"));
-
- ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound =
- Binder.bind(
- units,
- ClassPathBinder.bindClasspath(ImmutableList.of()),
- TURBINE_BOOTCLASSPATH,
- /* moduleVersion=*/ Optional.empty())
- .units();
-
- assertThat(bound.keySet()).containsExactly(new ClassSymbol("a/A"));
-
- SourceTypeBoundClass a = bound.get(new ClassSymbol("a/A"));
- FieldInfo f = getOnlyElement(a.fields());
- assertThat(f.name()).isEqualTo("b");
- assertThat(f.value()).isNull();
- }
-
private Tree.CompUnit parseLines(String... lines) {
return Parser.parse(Joiner.on('\n').join(lines));
}
+
+ private static SourceTypeBoundClass getBoundClass(
+ Map<ClassSymbol, SourceTypeBoundClass> bound, String name) {
+ // requireNonNull is safe as long as we call this method with classes that exist in our sources.
+ return requireNonNull(bound.get(new ClassSymbol(name)));
+ }
}
diff --git a/javatests/com/google/turbine/binder/ClassPathBinderTest.java b/javatests/com/google/turbine/binder/ClassPathBinderTest.java
index c11d814..6c6bc3e 100644
--- a/javatests/com/google/turbine/binder/ClassPathBinderTest.java
+++ b/javatests/com/google/turbine/binder/ClassPathBinderTest.java
@@ -16,17 +16,22 @@
package com.google.turbine.binder;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Iterables.getLast;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.MoreCollectors.onlyElement;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH;
+import static com.google.turbine.testing.TestResources.getResourceBytes;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.fail;
+import static java.util.Locale.ENGLISH;
+import static java.util.Objects.requireNonNull;
+import static org.junit.Assert.assertThrows;
import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
-import com.google.common.io.ByteStreams;
import com.google.common.io.MoreFiles;
import com.google.turbine.binder.bound.EnumConstantValue;
import com.google.turbine.binder.bound.TypeBoundClass;
@@ -34,6 +39,7 @@ import com.google.turbine.binder.bytecode.BytecodeBoundClass;
import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.lookup.LookupKey;
import com.google.turbine.binder.lookup.LookupResult;
+import com.google.turbine.binder.lookup.PackageScope;
import com.google.turbine.binder.lookup.Scope;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.FieldSymbol;
@@ -42,40 +48,100 @@ import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.tree.Tree.Ident;
import com.google.turbine.type.AnnoInfo;
import com.google.turbine.type.Type.ClassTy;
-import java.io.IOError;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
-@RunWith(JUnit4.class)
+@RunWith(Parameterized.class)
public class ClassPathBinderTest {
+ @Parameterized.Parameters
+ public static ImmutableCollection<Object[]> parameters() {
+ Object[] testCases = {
+ TURBINE_BOOTCLASSPATH,
+ FileManagerClassBinder.adapt(
+ ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, ENGLISH, UTF_8),
+ StandardLocation.PLATFORM_CLASS_PATH),
+ };
+ return Arrays.stream(testCases).map(x -> new Object[] {x}).collect(toImmutableList());
+ }
+
+ private final ClassPath classPath;
+
+ public ClassPathBinderTest(ClassPath classPath) {
+ this.classPath = classPath;
+ }
+
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+ private static Ident ident(String string) {
+ return new Ident(/* position= */ -1, string);
+ }
+
@Test
- public void classPathLookup() throws IOException {
+ public void classPathLookup() {
- Scope javaLang = TURBINE_BOOTCLASSPATH.index().lookupPackage(ImmutableList.of("java", "lang"));
+ Scope javaLang = classPath.index().lookupPackage(ImmutableList.of("java", "lang"));
- LookupResult result = javaLang.lookup(new LookupKey(ImmutableList.of(new Ident(-1, "String"))));
+ final String string = "String";
+ LookupResult result = javaLang.lookup(new LookupKey(ImmutableList.of(ident(string))));
assertThat(result.remaining()).isEmpty();
assertThat(result.sym()).isEqualTo(new ClassSymbol("java/lang/String"));
- result = javaLang.lookup(new LookupKey(ImmutableList.of(new Ident(-1, "Object"))));
+ result = javaLang.lookup(new LookupKey(ImmutableList.of(ident("Object"))));
assertThat(result.remaining()).isEmpty();
assertThat(result.sym()).isEqualTo(new ClassSymbol("java/lang/Object"));
}
@Test
- public void classPathClasses() throws IOException {
- Env<ClassSymbol, BytecodeBoundClass> env = TURBINE_BOOTCLASSPATH.env();
+ public void packageScope() {
+
+ PackageScope result = classPath.index().lookupPackage(ImmutableList.of("java", "nosuch"));
+ assertThat(result).isNull();
+
+ result = classPath.index().lookupPackage(ImmutableList.of("java", "lang"));
+ assertThat(result.classes()).contains(new ClassSymbol("java/lang/String"));
+
+ assertThat(result.lookup(new LookupKey(ImmutableList.of(ident("NoSuch"))))).isNull();
+ }
+
+ @Test
+ public void scope() {
+ Scope scope = classPath.index().scope();
+ LookupResult result;
+
+ result =
+ scope.lookup(
+ new LookupKey(
+ ImmutableList.of(ident("java"), ident("util"), ident("Map"), ident("Entry"))));
+ assertThat(result.sym()).isEqualTo(new ClassSymbol("java/util/Map"));
+ assertThat(result.remaining().stream().map(Ident::value)).containsExactly("Entry");
+
+ result =
+ scope.lookup(new LookupKey(ImmutableList.of(ident("java"), ident("util"), ident("Map"))));
+ assertThat(result.sym()).isEqualTo(new ClassSymbol("java/util/Map"));
+ assertThat(result.remaining()).isEmpty();
+
+ result =
+ scope.lookup(
+ new LookupKey(ImmutableList.of(ident("java"), ident("util"), ident("NoSuch"))));
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void classPathClasses() {
+ Env<ClassSymbol, BytecodeBoundClass> env = classPath.env();
TypeBoundClass c = env.get(new ClassSymbol("java/util/Map$Entry"));
assertThat(c.owner()).isEqualTo(new ClassSymbol("java/util/Map"));
@@ -96,7 +162,7 @@ public class ClassPathBinderTest {
@Test
public void interfaces() {
- Env<ClassSymbol, BytecodeBoundClass> env = TURBINE_BOOTCLASSPATH.env();
+ Env<ClassSymbol, BytecodeBoundClass> env = classPath.env();
TypeBoundClass c = env.get(new ClassSymbol("java/lang/annotation/Retention"));
assertThat(c.interfaceTypes()).hasSize(1);
@@ -114,7 +180,7 @@ public class ClassPathBinderTest {
@Test
public void annotations() {
- Env<ClassSymbol, BytecodeBoundClass> env = TURBINE_BOOTCLASSPATH.env();
+ Env<ClassSymbol, BytecodeBoundClass> env = classPath.env();
TypeBoundClass c = env.get(new ClassSymbol("java/lang/annotation/Retention"));
AnnoInfo anno =
@@ -122,34 +188,25 @@ public class ClassPathBinderTest {
.filter(a -> a.sym().equals(new ClassSymbol("java/lang/annotation/Retention")))
.collect(onlyElement());
assertThat(anno.values().keySet()).containsExactly("value");
- assertThat(((EnumConstantValue) anno.values().get("value")).sym())
+ // requireNonNull is safe because we checked that the keySet contains `"value"`.
+ assertThat(((EnumConstantValue) requireNonNull(anno.values().get("value"))).sym())
.isEqualTo(
new FieldSymbol(new ClassSymbol("java/lang/annotation/RetentionPolicy"), "RUNTIME"));
}
@Test
public void byteCodeBoundClassName() {
+ Env<ClassSymbol, BytecodeBoundClass> env = classPath.env();
BytecodeBoundClass c =
new BytecodeBoundClass(
new ClassSymbol("java/util/List"),
- () -> {
- try {
- return ByteStreams.toByteArray(
- getClass().getClassLoader().getResourceAsStream("java/util/ArrayList.class"));
- } catch (IOException e) {
- throw new IOError(e);
- }
- },
- null,
+ () -> getResourceBytes(getClass(), "/java/util/ArrayList.class"),
+ env,
null);
- try {
- c.owner();
- fail();
- } catch (VerifyException e) {
- assertThat(e)
- .hasMessageThat()
- .contains("expected class data for java/util/List, saw java/util/ArrayList instead");
- }
+ VerifyException e = assertThrows(VerifyException.class, () -> c.owner());
+ assertThat(e)
+ .hasMessageThat()
+ .contains("expected class data for java/util/List, saw java/util/ArrayList instead");
}
@Test
@@ -157,12 +214,9 @@ public class ClassPathBinderTest {
Path lib = temporaryFolder.newFile("NOT_A_JAR").toPath();
MoreFiles.asCharSink(lib, UTF_8).write("hello");
- try {
- ClassPathBinder.bindClasspath(ImmutableList.of(lib));
- fail();
- } catch (IOException e) {
- assertThat(e).hasMessageThat().contains("NOT_A_JAR");
- }
+ IOException e =
+ assertThrows(IOException.class, () -> ClassPathBinder.bindClasspath(ImmutableList.of(lib)));
+ assertThat(e).hasMessageThat().contains("NOT_A_JAR");
}
@Test
@@ -178,4 +232,21 @@ public class ClassPathBinderTest {
assertThat(new String(classPath.resource("foo/bar/hello.txt").get(), UTF_8)).isEqualTo("hello");
assertThat(classPath.resource("foo/bar/Baz.class")).isNull();
}
+
+ @Test
+ public void resourcesFileManager() throws Exception {
+ Path path = temporaryFolder.newFile("tmp.jar").toPath();
+ try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(path))) {
+ jos.putNextEntry(new JarEntry("foo/bar/hello.txt"));
+ jos.write("hello".getBytes(UTF_8));
+ jos.putNextEntry(new JarEntry("foo/bar/Baz.class"));
+ jos.write("goodbye".getBytes(UTF_8));
+ }
+ StandardJavaFileManager fileManager =
+ ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, ENGLISH, UTF_8);
+ fileManager.setLocation(StandardLocation.CLASS_PATH, ImmutableList.of(path.toFile()));
+ ClassPath classPath = FileManagerClassBinder.adapt(fileManager, StandardLocation.CLASS_PATH);
+ assertThat(new String(classPath.resource("foo/bar/hello.txt").get(), UTF_8)).isEqualTo("hello");
+ assertThat(classPath.resource("foo/bar/NoSuch.class")).isNull();
+ }
}
diff --git a/javatests/com/google/turbine/binder/CtSymClassBinderTest.java b/javatests/com/google/turbine/binder/CtSymClassBinderTest.java
new file mode 100644
index 0000000..d3a2c0e
--- /dev/null
+++ b/javatests/com/google/turbine/binder/CtSymClassBinderTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2020 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.google.turbine.binder;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class CtSymClassBinderTest {
+ @Test
+ public void formatReleaseVersion() {
+ ImmutableList.of(5, 6, 7, 8, 9)
+ .forEach(
+ x -> assertThat(CtSymClassBinder.formatReleaseVersion(x)).isEqualTo(String.valueOf(x)));
+ ImmutableMap.of(
+ 10, "A",
+ 11, "B",
+ 12, "C",
+ 35, "Z")
+ .forEach((k, v) -> assertThat(CtSymClassBinder.formatReleaseVersion(k)).isEqualTo(v));
+ ImmutableList.of(4, 36)
+ .forEach(
+ x ->
+ assertThrows(
+ Integer.toString(x),
+ IllegalArgumentException.class,
+ () -> CtSymClassBinder.formatReleaseVersion(x)));
+ }
+}
diff --git a/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java b/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java
index 3e841a5..65d973d 100644
--- a/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java
+++ b/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java
@@ -17,6 +17,7 @@
package com.google.turbine.binder.bytecode;
import static com.google.common.collect.Iterables.getLast;
+import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.MoreCollectors.onlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH;
@@ -31,6 +32,7 @@ import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo;
import com.google.turbine.binder.env.CompoundEnv;
import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.model.TurbineFlag;
import com.google.turbine.type.Type;
import com.google.turbine.type.Type.ClassTy;
import java.io.IOException;
@@ -40,6 +42,7 @@ import java.io.UncheckedIOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.jspecify.nullness.Nullable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -81,9 +84,11 @@ public class BytecodeBoundClassTest {
.isEqualTo(new ClassSymbol("java/lang/String"));
}
+ @SuppressWarnings({"deprecation", "TypeNameShadowing", "InlineMeSuggester"})
static class HasMethod {
@Deprecated
- <X, Y extends X, Z extends Throwable> X foo(@Deprecated X bar, Y baz) throws IOException, Z {
+ <X, Y extends X, Z extends Throwable> @Nullable X foo(@Deprecated X bar, Y baz)
+ throws IOException, Z {
return null;
}
@@ -175,6 +180,19 @@ public class BytecodeBoundClassTest {
assertThat(getBytecodeBoundClass(C.class, B.class, A.class).methods()).hasSize(1);
}
+ interface D {
+ default void f() {}
+ }
+
+ @Test
+ public void defaultMethods() {
+ assertThat(
+ (getOnlyElement(getBytecodeBoundClass(D.class).methods()).access()
+ & TurbineFlag.ACC_DEFAULT)
+ == TurbineFlag.ACC_DEFAULT)
+ .isTrue();
+ }
+
private static byte[] toByteArrayOrDie(InputStream is) {
try {
return ByteStreams.toByteArray(is);
@@ -201,7 +219,7 @@ public class BytecodeBoundClassTest {
.append(
new Env<ClassSymbol, BytecodeBoundClass>() {
@Override
- public BytecodeBoundClass get(ClassSymbol sym) {
+ public @Nullable BytecodeBoundClass get(ClassSymbol sym) {
return map.get(sym);
}
});
diff --git a/javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java b/javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java
index 022e47c..861bfef 100644
--- a/javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java
+++ b/javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java
@@ -18,7 +18,7 @@ package com.google.turbine.binder.lookup;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.turbine.binder.sym.ClassSymbol;
@@ -105,15 +105,8 @@ public class TopLevelIndexTest {
@Test
public void emptyLookup() {
- LookupKey key = lookupKey(ImmutableList.of("java", "util", "List"));
- key = key.rest();
- key = key.rest();
- try {
- key.rest();
- fail("expected exception");
- } catch (NoSuchElementException e) {
- // expected
- }
+ LookupKey key = lookupKey(ImmutableList.of("java", "util", "List")).rest().rest();
+ assertThrows(NoSuchElementException.class, () -> key.rest());
}
private LookupKey lookupKey(ImmutableList<String> names) {
diff --git a/javatests/com/google/turbine/bytecode/ClassReaderTest.java b/javatests/com/google/turbine/bytecode/ClassReaderTest.java
index fb64541..ad5b90d 100644
--- a/javatests/com/google/turbine/bytecode/ClassReaderTest.java
+++ b/javatests/com/google/turbine/bytecode/ClassReaderTest.java
@@ -18,6 +18,7 @@ package com.google.turbine.bytecode;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
@@ -34,8 +35,11 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ByteVector;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Handle;
import org.objectweb.asm.ModuleVisitor;
import org.objectweb.asm.Opcodes;
@@ -136,7 +140,7 @@ public class ClassReaderTest {
assertThat(annotation.typeName()).isEqualTo("Ljava/lang/annotation/Retention;");
assertThat(annotation.elementValuePairs()).hasSize(1);
assertThat(annotation.elementValuePairs()).containsKey("value");
- ElementValue value = annotation.elementValuePairs().get("value");
+ ElementValue value = requireNonNull(annotation.elementValuePairs().get("value"));
assertThat(value.kind()).isEqualTo(ElementValue.Kind.ENUM);
ElementValue.EnumConstValue enumValue = (ElementValue.EnumConstValue) value;
assertThat(enumValue.typeName()).isEqualTo("Ljava/lang/annotation/RetentionPolicy;");
@@ -233,6 +237,18 @@ public class ClassReaderTest {
}
@Test
+ public void condy() {
+ ClassWriter cw = new ClassWriter(0);
+ cw.visit(52, Opcodes.ACC_SUPER, "Test", null, "java/lang/Object", null);
+ cw.newConstantDynamic(
+ "f", "Ljava/lang/String;", new Handle(Opcodes.H_INVOKESTATIC, "A", "f", "()V", false));
+ byte[] bytes = cw.toByteArray();
+
+ ClassFile cf = ClassReader.read(null, bytes);
+ assertThat(cf.name()).isEqualTo("Test");
+ }
+
+ @Test
public void v53() {
ClassWriter cw = new ClassWriter(0);
cw.visitAnnotation("Ljava/lang/Deprecated;", true);
@@ -335,4 +351,28 @@ public class ClassReaderTest {
assertThat(p2.descriptor()).isEqualTo("p2");
assertThat(p2.implDescriptors()).containsExactly("p2i1", "p2i2", "p2i3");
}
+
+ @Test
+ public void transitiveJar() {
+ ClassWriter cw = new ClassWriter(0);
+ cw.visit(
+ 52,
+ Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER,
+ "Hello",
+ null,
+ "java/lang/Object",
+ null);
+ cw.visitAttribute(
+ new Attribute("TurbineTransitiveJar") {
+ @Override
+ protected ByteVector write(
+ ClassWriter classWriter, byte[] code, int codeLength, int maxStack, int maxLocals) {
+ ByteVector result = new ByteVector();
+ result.putShort(classWriter.newUTF8("path/to/transitive.jar"));
+ return result;
+ }
+ });
+ ClassFile cf = ClassReader.read(null, cw.toByteArray());
+ assertThat(cf.transitiveJar()).isEqualTo("path/to/transitive.jar");
+ }
}
diff --git a/javatests/com/google/turbine/bytecode/ClassWriterTest.java b/javatests/com/google/turbine/bytecode/ClassWriterTest.java
index 71cf356..a6f9234 100644
--- a/javatests/com/google/turbine/bytecode/ClassWriterTest.java
+++ b/javatests/com/google/turbine/bytecode/ClassWriterTest.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.google.common.jimfs.Configuration;
@@ -46,6 +47,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.objectweb.asm.ModuleVisitor;
import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.RecordComponentVisitor;
@RunWith(JUnit4.class)
public class ClassWriterTest {
@@ -90,7 +92,8 @@ public class ClassWriterTest {
byte[] original = Files.readAllBytes(out.resolve("test/Test.class"));
byte[] actual = ClassWriter.writeClass(ClassReader.read(null, original));
- assertThat(AsmUtils.textify(original)).isEqualTo(AsmUtils.textify(actual));
+ assertThat(AsmUtils.textify(original, /* skipDebug= */ true))
+ .isEqualTo(AsmUtils.textify(actual, /* skipDebug= */ true));
}
// Test that >Short.MAX_VALUE constants round-trip through the constant pool.
@@ -145,10 +148,114 @@ public class ClassWriterTest {
byte[] inputBytes = cw.toByteArray();
byte[] outputBytes = ClassWriter.writeClass(ClassReader.read("module-info", inputBytes));
- assertThat(AsmUtils.textify(inputBytes)).isEqualTo(AsmUtils.textify(outputBytes));
+ assertThat(AsmUtils.textify(inputBytes, /* skipDebug= */ true))
+ .isEqualTo(AsmUtils.textify(outputBytes, /* skipDebug= */ true));
// test a round trip
outputBytes = ClassWriter.writeClass(ClassReader.read("module-info", outputBytes));
- assertThat(AsmUtils.textify(inputBytes)).isEqualTo(AsmUtils.textify(outputBytes));
+ assertThat(AsmUtils.textify(inputBytes, /* skipDebug= */ true))
+ .isEqualTo(AsmUtils.textify(outputBytes, /* skipDebug= */ true));
+ }
+
+ @Test
+ public void record() {
+
+ org.objectweb.asm.ClassWriter cw = new org.objectweb.asm.ClassWriter(0);
+
+ cw.visit(
+ Opcodes.V16,
+ Opcodes.ACC_FINAL | Opcodes.ACC_SUPER | Opcodes.ACC_RECORD,
+ "R",
+ /* signature= */ null,
+ "java/lang/Record",
+ /* interfaces= */ null);
+
+ RecordComponentVisitor rv =
+ cw.visitRecordComponent("x", "Ljava/util/List;", "Ljava/util/List<Ljava/lang/Integer;>;");
+ rv.visitAnnotation("LA;", true);
+ rv.visitTypeAnnotation(318767104, null, "LA;", true);
+ cw.visitRecordComponent("y", "I", null);
+
+ byte[] expectedBytes = cw.toByteArray();
+
+ ClassFile classFile =
+ new ClassFile(
+ /* access= */ Opcodes.ACC_FINAL | Opcodes.ACC_SUPER | Opcodes.ACC_RECORD,
+ /* majorVersion= */ 60,
+ /* name= */ "R",
+ /* signature= */ null,
+ /* superClass= */ "java/lang/Record",
+ /* interfaces= */ ImmutableList.of(),
+ /* permits= */ ImmutableList.of(),
+ /* methods= */ ImmutableList.of(),
+ /* fields= */ ImmutableList.of(),
+ /* annotations= */ ImmutableList.of(),
+ /* innerClasses= */ ImmutableList.of(),
+ /* typeAnnotations= */ ImmutableList.of(),
+ /* module= */ null,
+ /* nestHost= */ null,
+ /* nestMembers= */ ImmutableList.of(),
+ /* record= */ new ClassFile.RecordInfo(
+ ImmutableList.of(
+ new ClassFile.RecordInfo.RecordComponentInfo(
+ "x",
+ "Ljava/util/List;",
+ "Ljava/util/List<Ljava/lang/Integer;>;",
+ ImmutableList.of(
+ new ClassFile.AnnotationInfo("LA;", true, ImmutableMap.of())),
+ ImmutableList.of(
+ new ClassFile.TypeAnnotationInfo(
+ ClassFile.TypeAnnotationInfo.TargetType.FIELD,
+ ClassFile.TypeAnnotationInfo.EMPTY_TARGET,
+ ClassFile.TypeAnnotationInfo.TypePath.root(),
+ new ClassFile.AnnotationInfo("LA;", true, ImmutableMap.of())))),
+ new ClassFile.RecordInfo.RecordComponentInfo(
+ "y", "I", null, ImmutableList.of(), ImmutableList.of()))),
+ /* transitiveJar= */ null);
+
+ byte[] actualBytes = ClassWriter.writeClass(classFile);
+
+ assertThat(AsmUtils.textify(actualBytes, /* skipDebug= */ true))
+ .isEqualTo(AsmUtils.textify(expectedBytes, /* skipDebug= */ true));
+ }
+
+ @Test
+ public void nestHost() {
+
+ org.objectweb.asm.ClassWriter cw = new org.objectweb.asm.ClassWriter(0);
+
+ cw.visit(Opcodes.V16, Opcodes.ACC_SUPER, "N", null, null, null);
+
+ cw.visitNestHost("H");
+ cw.visitNestMember("A");
+ cw.visitNestMember("B");
+ cw.visitNestMember("C");
+
+ byte[] expectedBytes = cw.toByteArray();
+
+ ClassFile classFile =
+ new ClassFile(
+ /* access= */ Opcodes.ACC_SUPER,
+ /* majorVersion= */ 60,
+ /* name= */ "N",
+ /* signature= */ null,
+ /* superClass= */ null,
+ /* interfaces= */ ImmutableList.of(),
+ /* permits= */ ImmutableList.of(),
+ /* methods= */ ImmutableList.of(),
+ /* fields= */ ImmutableList.of(),
+ /* annotations= */ ImmutableList.of(),
+ /* innerClasses= */ ImmutableList.of(),
+ /* typeAnnotations= */ ImmutableList.of(),
+ /* module= */ null,
+ /* nestHost= */ "H",
+ /* nestMembers= */ ImmutableList.of("A", "B", "C"),
+ /* record= */ null,
+ /* transitiveJar= */ null);
+
+ byte[] actualBytes = ClassWriter.writeClass(classFile);
+
+ assertThat(AsmUtils.textify(actualBytes, /* skipDebug= */ true))
+ .isEqualTo(AsmUtils.textify(expectedBytes, /* skipDebug= */ true));
}
}
diff --git a/javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java b/javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java
index f3ab8e7..8602fe5 100644
--- a/javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java
+++ b/javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java
@@ -17,6 +17,7 @@
package com.google.turbine.bytecode.sig;
import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.io.MoreFiles.getFileExtension;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Splitter;
@@ -70,7 +71,7 @@ public class SigIntegrationTest {
Stream<Path> stream = Files.walk(jarfs.getPath("/"))) {
stream
.filter(Files::isRegularFile)
- .filter(p -> p.getFileName().toString().endsWith(".class"))
+ .filter(p -> getFileExtension(p).equals("class"))
.forEachOrdered(consumer);
}
}
@@ -80,7 +81,7 @@ public class SigIntegrationTest {
Map<String, ?> env = new HashMap<>();
try (FileSystem fileSystem = FileSystems.newFileSystem(URI.create("jrt:/"), env);
Stream<Path> stream = Files.walk(fileSystem.getPath("/modules"))) {
- stream.filter(p -> p.getFileName().toString().endsWith(".class")).forEachOrdered(consumer);
+ stream.filter(p -> getFileExtension(p).equals("class")).forEachOrdered(consumer);
}
}
}
@@ -93,7 +94,7 @@ public class SigIntegrationTest {
try {
new ClassReader(Files.newInputStream(path))
.accept(
- new ClassVisitor(Opcodes.ASM7) {
+ new ClassVisitor(Opcodes.ASM9) {
@Override
public void visit(
int version,
diff --git a/javatests/com/google/turbine/deps/AbstractTransitiveTest.java b/javatests/com/google/turbine/deps/AbstractTransitiveTest.java
deleted file mode 100644
index c5b68ff..0000000
--- a/javatests/com/google/turbine/deps/AbstractTransitiveTest.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 com.google.turbine.deps;
-
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.collect.Iterables.getOnlyElement;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.io.ByteStreams;
-import com.google.turbine.bytecode.ClassFile;
-import com.google.turbine.bytecode.ClassFile.InnerClass;
-import com.google.turbine.bytecode.ClassReader;
-import com.google.turbine.main.Main;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Opcodes;
-
-public abstract class AbstractTransitiveTest {
-
- protected abstract Path runTurbine(ImmutableList<Path> sources, ImmutableList<Path> classpath)
- throws IOException;
-
- @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
-
- class SourceBuilder {
- private final Path lib;
- private final ImmutableList.Builder<Path> sources = ImmutableList.builder();
-
- SourceBuilder() throws IOException {
- lib = temporaryFolder.newFolder().toPath();
- }
-
- SourceBuilder addSourceLines(String name, String... lines) throws IOException {
- Path path = lib.resolve(name);
- Files.createDirectories(path.getParent());
- Files.write(path, Arrays.asList(lines), UTF_8);
- sources.add(path);
- return this;
- }
-
- ImmutableList<Path> build() {
- return sources.build();
- }
- }
-
- private Map<String, byte[]> readJar(Path libb) throws IOException {
- Map<String, byte[]> jarEntries = new LinkedHashMap<>();
- try (JarFile jf = new JarFile(libb.toFile())) {
- Enumeration<JarEntry> entries = jf.entries();
- while (entries.hasMoreElements()) {
- JarEntry je = entries.nextElement();
- jarEntries.put(je.getName(), ByteStreams.toByteArray(jf.getInputStream(je)));
- }
- }
- return jarEntries;
- }
-
- @Test
- public void transitive() throws Exception {
- Path liba =
- runTurbine(
- new SourceBuilder()
- .addSourceLines(
- "a/A.java",
- "package a;",
- "import java.util.Map;",
- "public class A {",
- " public @interface Anno {",
- " int x() default 42;",
- " }",
- " public static class Inner {}",
- " public static final int CONST = 42;",
- " public int mutable = 42;",
- " public Map.Entry<String, String> f(Map<String, String> m) {",
- " return m.entrySet().iterator().next();",
- " }",
- "}")
- .build(),
- ImmutableList.of());
-
- Path libb =
- runTurbine(
- new SourceBuilder()
- .addSourceLines("b/B.java", "package b;", "public class B extends a.A {}")
- .build(),
- ImmutableList.of(liba));
-
- // libb repackages A, and any member types
- assertThat(readJar(libb).keySet())
- .containsExactly(
- "b/B.class",
- "META-INF/TRANSITIVE/a/A.class",
- "META-INF/TRANSITIVE/a/A$Anno.class",
- "META-INF/TRANSITIVE/a/A$Inner.class");
-
- ClassFile a = ClassReader.read(null, readJar(libb).get("META-INF/TRANSITIVE/a/A.class"));
- // methods and non-constant fields are removed
- assertThat(getOnlyElement(a.fields()).name()).isEqualTo("CONST");
- assertThat(a.methods()).isEmpty();
- assertThat(Iterables.transform(a.innerClasses(), InnerClass::innerClass))
- .containsExactly("a/A$Anno", "a/A$Inner");
-
- // annotation interface methods are preserved
- assertThat(
- ClassReader.read(null, readJar(libb).get("META-INF/TRANSITIVE/a/A$Anno.class"))
- .methods())
- .hasSize(1);
-
- // A class that references members of the transitive supertype A by simple name
- // compiles cleanly against the repackaged version of A.
- // Explicitly use turbine; javac-turbine doesn't support direct-classpath compilations.
-
- Path libc = temporaryFolder.newFolder().toPath().resolve("out.jar");
- ImmutableList<String> sources =
- new SourceBuilder()
- .addSourceLines(
- "c/C.java",
- "package c;",
- "public class C extends b.B {",
- " @Anno(x = 2) static final Inner i; // a.A$Inner ",
- " static final int X = CONST; // a.A#CONST",
- "}")
- .build()
- .stream()
- .map(Path::toString)
- .collect(toImmutableList());
- Main.compile(
- optionsWithBootclasspath()
- .setSources(sources)
- .setClassPath(
- ImmutableList.of(libb).stream().map(Path::toString).collect(toImmutableList()))
- .setOutput(libc.toString())
- .build());
-
- assertThat(readJar(libc).keySet())
- .containsExactly(
- "c/C.class",
- "META-INF/TRANSITIVE/b/B.class",
- "META-INF/TRANSITIVE/a/A.class",
- "META-INF/TRANSITIVE/a/A$Anno.class",
- "META-INF/TRANSITIVE/a/A$Inner.class");
- }
-
- @Test
- public void anonymous() throws Exception {
- Path liba = temporaryFolder.newFolder().toPath().resolve("out.jar");
- try (OutputStream os = Files.newOutputStream(liba);
- JarOutputStream jos = new JarOutputStream(os)) {
- {
- jos.putNextEntry(new JarEntry("a/A.class"));
- ClassWriter cw = new ClassWriter(0);
- cw.visit(52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A", null, "java/lang/Object", null);
- cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
- cw.visitInnerClass("a/A$1", "a/A", null, Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC);
- cw.visitInnerClass("a/A$I", "a/A", "I", Opcodes.ACC_STATIC);
- jos.write(cw.toByteArray());
- }
- {
- jos.putNextEntry(new JarEntry("a/A$1.class"));
- ClassWriter cw = new ClassWriter(0);
- cw.visit(
- 52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A$1", null, "java/lang/Object", null);
- cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
- cw.visitInnerClass("a/A$1", "a/A", "I", Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC);
- jos.write(cw.toByteArray());
- }
- {
- jos.putNextEntry(new JarEntry("a/A$I.class"));
- ClassWriter cw = new ClassWriter(0);
- cw.visit(
- 52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A$I", null, "java/lang/Object", null);
- cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
- cw.visitInnerClass("a/A$I", "a/A", "I", Opcodes.ACC_STATIC);
- jos.write(cw.toByteArray());
- }
- }
- Path libb =
- runTurbine(
- new SourceBuilder()
- .addSourceLines(
- "b/B.java", //
- "package b;",
- "public class B extends a.A {}")
- .build(),
- ImmutableList.of(liba));
-
- // libb repackages A and any named member types
- assertThat(readJar(libb).keySet())
- .containsExactly(
- "b/B.class", "META-INF/TRANSITIVE/a/A.class", "META-INF/TRANSITIVE/a/A$I.class");
- }
-
- @Test
- public void childClass() throws Exception {
- Path liba =
- runTurbine(
- new SourceBuilder()
- .addSourceLines(
- "a/S.java", //
- "package a;",
- "public class S {}")
- .addSourceLines(
- "a/A.java", //
- "package a;",
- "public class A {",
- " public class I extends S {}",
- "}")
- .build(),
- ImmutableList.of());
-
- Path libb =
- runTurbine(
- new SourceBuilder()
- .addSourceLines(
- "b/B.java", //
- "package b;",
- "public class B extends a.A {",
- " class I extends a.A.I {",
- " }",
- "}")
- .build(),
- ImmutableList.of(liba));
-
- assertThat(readJar(libb).keySet())
- .containsExactly(
- "b/B.class",
- "b/B$I.class",
- "META-INF/TRANSITIVE/a/A.class",
- "META-INF/TRANSITIVE/a/A$I.class",
- "META-INF/TRANSITIVE/a/S.class");
- }
-}
diff --git a/javatests/com/google/turbine/deps/DependenciesTest.java b/javatests/com/google/turbine/deps/DependenciesTest.java
index bc663cd..ba905db 100644
--- a/javatests/com/google/turbine/deps/DependenciesTest.java
+++ b/javatests/com/google/turbine/deps/DependenciesTest.java
@@ -29,6 +29,7 @@ import com.google.turbine.diag.SourceFile;
import com.google.turbine.lower.IntegrationTestSupport;
import com.google.turbine.lower.Lower;
import com.google.turbine.lower.Lower.Lowered;
+import com.google.turbine.options.LanguageVersion;
import com.google.turbine.parse.Parser;
import com.google.turbine.proto.DepsProto;
import com.google.turbine.testing.TestClassPaths;
@@ -106,7 +107,12 @@ public class DependenciesTest {
TestClassPaths.TURBINE_BOOTCLASSPATH,
/* moduleVersion=*/ Optional.empty());
- Lowered lowered = Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv());
+ Lowered lowered =
+ Lower.lowerAll(
+ LanguageVersion.createDefault(),
+ bound.units(),
+ bound.modules(),
+ bound.classPathEnv());
return Dependencies.collectDeps(
Optional.of("//test"), TestClassPaths.TURBINE_BOOTCLASSPATH, bound, lowered);
diff --git a/javatests/com/google/turbine/deps/TransitiveTest.java b/javatests/com/google/turbine/deps/TransitiveTest.java
index 2c9f807..f08e899 100644
--- a/javatests/com/google/turbine/deps/TransitiveTest.java
+++ b/javatests/com/google/turbine/deps/TransitiveTest.java
@@ -17,20 +17,281 @@
package com.google.turbine.deps;
import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.io.ByteStreams;
+import com.google.protobuf.ExtensionRegistry;
+import com.google.turbine.bytecode.ClassFile;
+import com.google.turbine.bytecode.ClassFile.InnerClass;
+import com.google.turbine.bytecode.ClassReader;
import com.google.turbine.main.Main;
+import com.google.turbine.proto.DepsProto;
+import com.google.turbine.proto.DepsProto.Dependency.Kind;
+import java.io.BufferedInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
@RunWith(JUnit4.class)
-public class TransitiveTest extends AbstractTransitiveTest {
+public class TransitiveTest {
- @Override
- protected Path runTurbine(ImmutableList<Path> sources, ImmutableList<Path> classpath)
+ @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ class SourceBuilder {
+ private final Path lib;
+ private final ImmutableList.Builder<Path> sources = ImmutableList.builder();
+
+ SourceBuilder() throws IOException {
+ lib = temporaryFolder.newFolder().toPath();
+ }
+
+ SourceBuilder addSourceLines(String name, String... lines) throws IOException {
+ Path path = lib.resolve(name);
+ Files.createDirectories(path.getParent());
+ Files.write(path, Arrays.asList(lines), UTF_8);
+ sources.add(path);
+ return this;
+ }
+
+ ImmutableList<Path> build() {
+ return sources.build();
+ }
+ }
+
+ private static Map<String, byte[]> readJar(Path libb) throws IOException {
+ Map<String, byte[]> jarEntries = new LinkedHashMap<>();
+ try (JarFile jf = new JarFile(libb.toFile())) {
+ Enumeration<JarEntry> entries = jf.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry je = entries.nextElement();
+ jarEntries.put(je.getName(), ByteStreams.toByteArray(jf.getInputStream(je)));
+ }
+ }
+ return jarEntries;
+ }
+
+ @Test
+ public void transitive() throws Exception {
+ Path liba =
+ runTurbine(
+ new SourceBuilder()
+ .addSourceLines(
+ "a/A.java",
+ "package a;",
+ "import java.util.Map;",
+ "public class A {",
+ " public @interface Anno {",
+ " int x() default 42;",
+ " }",
+ " public static class Inner {}",
+ " public static final int CONST = 42;",
+ " public int mutable = 42;",
+ " public Map.Entry<String, String> f(Map<String, String> m) {",
+ " return m.entrySet().iterator().next();",
+ " }",
+ "}")
+ .build(),
+ ImmutableList.of());
+
+ Path libb =
+ runTurbine(
+ new SourceBuilder()
+ .addSourceLines("b/B.java", "package b;", "public class B extends a.A {}")
+ .build(),
+ ImmutableList.of(liba));
+
+ // libb repackages A, and any member types
+ assertThat(readJar(libb).keySet())
+ .containsExactly(
+ "b/B.class",
+ "META-INF/TRANSITIVE/a/A.class",
+ "META-INF/TRANSITIVE/a/A$Anno.class",
+ "META-INF/TRANSITIVE/a/A$Inner.class");
+
+ ClassFile a = ClassReader.read(null, readJar(libb).get("META-INF/TRANSITIVE/a/A.class"));
+ // methods and non-constant fields are removed
+ assertThat(getOnlyElement(a.fields()).name()).isEqualTo("CONST");
+ assertThat(a.methods()).isEmpty();
+ assertThat(Iterables.transform(a.innerClasses(), InnerClass::innerClass))
+ .containsExactly("a/A$Anno", "a/A$Inner");
+
+ // annotation interface methods are preserved
+ assertThat(
+ ClassReader.read(null, readJar(libb).get("META-INF/TRANSITIVE/a/A$Anno.class"))
+ .methods())
+ .hasSize(1);
+
+ // When a.A is repackaged as a transitive class in libb, its 'transitive jar' attribute
+ // should record the path to the original liba jar.
+ assertThat(a.transitiveJar()).isEqualTo(liba.toString());
+ // The transitive jar attribute is only set for transitive classes, not e.g. b.B in libb:
+ ClassFile b = ClassReader.read(null, readJar(libb).get("b/B.class"));
+ assertThat(b.transitiveJar()).isNull();
+
+ // A class that references members of the transitive supertype A by simple name
+ // compiles cleanly against the repackaged version of A.
+ // Explicitly use turbine; javac-turbine doesn't support direct-classpath compilations.
+
+ Path libc = temporaryFolder.newFolder().toPath().resolve("out.jar");
+ Path libcDeps = temporaryFolder.newFolder().toPath().resolve("out.jdeps");
+ ImmutableList<String> sources =
+ new SourceBuilder()
+ .addSourceLines(
+ "c/C.java",
+ "package c;",
+ "public class C extends b.B {",
+ " @Anno(x = 2) static final Inner i; // a.A$Inner ",
+ " static final int X = CONST; // a.A#CONST",
+ "}")
+ .build()
+ .stream()
+ .map(Path::toString)
+ .collect(toImmutableList());
+ Main.compile(
+ optionsWithBootclasspath()
+ .setSources(sources)
+ .setClassPath(
+ ImmutableList.of(libb).stream().map(Path::toString).collect(toImmutableList()))
+ .setOutput(libc.toString())
+ .setOutputDeps(libcDeps.toString())
+ .build());
+
+ assertThat(readJar(libc).keySet())
+ .containsExactly(
+ "c/C.class",
+ "META-INF/TRANSITIVE/b/B.class",
+ "META-INF/TRANSITIVE/a/A.class",
+ "META-INF/TRANSITIVE/a/A$Anno.class",
+ "META-INF/TRANSITIVE/a/A$Inner.class");
+
+ // liba is recorded as an explicit dep, even thought it's only present as a transitive class
+ // repackaged in lib
+ assertThat(readDeps(libcDeps))
+ .containsExactly(liba.toString(), Kind.EXPLICIT, libb.toString(), Kind.EXPLICIT);
+ }
+
+ private static ImmutableMap<String, Kind> readDeps(Path libcDeps) throws IOException {
+ DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder();
+ try (InputStream is = new BufferedInputStream(Files.newInputStream(libcDeps))) {
+ deps.mergeFrom(is, ExtensionRegistry.getEmptyRegistry());
+ }
+ return deps.getDependencyList().stream()
+ .collect(toImmutableMap(d -> d.getPath(), d -> d.getKind()));
+ }
+
+ @Test
+ public void anonymous() throws Exception {
+ Path liba = temporaryFolder.newFolder().toPath().resolve("out.jar");
+ try (OutputStream os = Files.newOutputStream(liba);
+ JarOutputStream jos = new JarOutputStream(os)) {
+ {
+ jos.putNextEntry(new JarEntry("a/A.class"));
+ ClassWriter cw = new ClassWriter(0);
+ cw.visit(52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A", null, "java/lang/Object", null);
+ cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
+ cw.visitInnerClass("a/A$1", "a/A", null, Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC);
+ cw.visitInnerClass("a/A$I", "a/A", "I", Opcodes.ACC_STATIC);
+ jos.write(cw.toByteArray());
+ }
+ {
+ jos.putNextEntry(new JarEntry("a/A$1.class"));
+ ClassWriter cw = new ClassWriter(0);
+ cw.visit(
+ 52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A$1", null, "java/lang/Object", null);
+ cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
+ cw.visitInnerClass("a/A$1", "a/A", "I", Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC);
+ jos.write(cw.toByteArray());
+ }
+ {
+ jos.putNextEntry(new JarEntry("a/A$I.class"));
+ ClassWriter cw = new ClassWriter(0);
+ cw.visit(
+ 52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A$I", null, "java/lang/Object", null);
+ cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
+ cw.visitInnerClass("a/A$I", "a/A", "I", Opcodes.ACC_STATIC);
+ jos.write(cw.toByteArray());
+ }
+ }
+ Path libb =
+ runTurbine(
+ new SourceBuilder()
+ .addSourceLines(
+ "b/B.java", //
+ "package b;",
+ "public class B extends a.A {}")
+ .build(),
+ ImmutableList.of(liba));
+
+ // libb repackages A and any named member types
+ assertThat(readJar(libb).keySet())
+ .containsExactly(
+ "b/B.class", "META-INF/TRANSITIVE/a/A.class", "META-INF/TRANSITIVE/a/A$I.class");
+ }
+
+ @Test
+ public void childClass() throws Exception {
+ Path liba =
+ runTurbine(
+ new SourceBuilder()
+ .addSourceLines(
+ "a/S.java", //
+ "package a;",
+ "public class S {}")
+ .addSourceLines(
+ "a/A.java", //
+ "package a;",
+ "public class A {",
+ " public class I extends S {}",
+ "}")
+ .build(),
+ ImmutableList.of());
+
+ Path libb =
+ runTurbine(
+ new SourceBuilder()
+ .addSourceLines(
+ "b/B.java", //
+ "package b;",
+ "public class B extends a.A {",
+ " class I extends a.A.I {",
+ " }",
+ "}")
+ .build(),
+ ImmutableList.of(liba));
+
+ assertThat(readJar(libb).keySet())
+ .containsExactly(
+ "b/B.class",
+ "b/B$I.class",
+ "META-INF/TRANSITIVE/a/A.class",
+ "META-INF/TRANSITIVE/a/A$I.class",
+ "META-INF/TRANSITIVE/a/S.class");
+ }
+
+ private Path runTurbine(ImmutableList<Path> sources, ImmutableList<Path> classpath)
throws IOException {
Path out = temporaryFolder.newFolder().toPath().resolve("out.jar");
Main.compile(
diff --git a/javatests/com/google/turbine/lower/IntegrationTestSupport.java b/javatests/com/google/turbine/lower/IntegrationTestSupport.java
index a03473d..f20962b 100644
--- a/javatests/com/google/turbine/lower/IntegrationTestSupport.java
+++ b/javatests/com/google/turbine/lower/IntegrationTestSupport.java
@@ -17,11 +17,14 @@
package com.google.turbine.lower;
import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.io.MoreFiles.getFileExtension;
import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
import static org.junit.Assert.fail;
import com.google.common.base.Joiner;
@@ -35,6 +38,7 @@ import com.google.turbine.binder.Binder.BindingResult;
import com.google.turbine.binder.ClassPath;
import com.google.turbine.binder.ClassPathBinder;
import com.google.turbine.diag.SourceFile;
+import com.google.turbine.options.LanguageVersion;
import com.google.turbine.parse.Parser;
import com.google.turbine.testing.AsmUtils;
import com.google.turbine.tree.Tree.CompUnit;
@@ -79,10 +83,11 @@ import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InnerClassNode;
import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.RecordComponentNode;
import org.objectweb.asm.tree.TypeAnnotationNode;
/** Support for bytecode diffing-integration tests. */
-public class IntegrationTestSupport {
+public final class IntegrationTestSupport {
/**
* Normalizes order of members, attributes, and constant pool entries, to allow diffing bytecode.
@@ -240,12 +245,22 @@ public class IntegrationTestSupport {
for (FieldNode f : n.fields) {
sortAnnotations(f.visibleAnnotations);
sortAnnotations(f.invisibleAnnotations);
-
- sortAnnotations(f.visibleAnnotations);
- sortAnnotations(f.invisibleAnnotations);
sortTypeAnnotations(f.visibleTypeAnnotations);
sortTypeAnnotations(f.invisibleTypeAnnotations);
}
+
+ if (n.recordComponents != null) {
+ for (RecordComponentNode r : n.recordComponents) {
+ sortAnnotations(r.visibleAnnotations);
+ sortAnnotations(r.invisibleAnnotations);
+ sortTypeAnnotations(r.visibleTypeAnnotations);
+ sortTypeAnnotations(r.invisibleTypeAnnotations);
+ }
+ }
+
+ if (n.nestMembers != null) {
+ Collections.sort(n.nestMembers);
+ }
}
private static void sortParameterAnnotations(List<AnnotationNode>[] parameters) {
@@ -321,6 +336,26 @@ public class IntegrationTestSupport {
addTypesInTypeAnnotations(types, f.visibleTypeAnnotations);
addTypesInTypeAnnotations(types, f.invisibleTypeAnnotations);
}
+ if (n.recordComponents != null) {
+ for (RecordComponentNode r : n.recordComponents) {
+ collectTypesFromSignature(types, r.descriptor);
+ collectTypesFromSignature(types, r.signature);
+
+ addTypesInAnnotations(types, r.visibleAnnotations);
+ addTypesInAnnotations(types, r.invisibleAnnotations);
+ addTypesInTypeAnnotations(types, r.visibleTypeAnnotations);
+ addTypesInTypeAnnotations(types, r.invisibleTypeAnnotations);
+ }
+ }
+
+ if (n.nestMembers != null) {
+ for (String member : n.nestMembers) {
+ InnerClassNode i = infos.get(member);
+ if (i.outerName != null) {
+ types.add(member);
+ }
+ }
+ }
List<InnerClassNode> used = new ArrayList<>();
for (InnerClassNode i : n.innerClasses) {
@@ -334,6 +369,11 @@ public class IntegrationTestSupport {
}
addInnerChain(infos, used, n.name);
n.innerClasses = used;
+
+ if (n.nestMembers != null) {
+ Set<String> members = used.stream().map(i -> i.name).collect(toSet());
+ n.nestMembers = n.nestMembers.stream().filter(members::contains).collect(toList());
+ }
}
private static void addTypesFromParameterAnnotations(
@@ -410,20 +450,21 @@ public class IntegrationTestSupport {
final Set<String> classes1 = classes;
new SignatureReader(signature)
.accept(
- new SignatureVisitor(Opcodes.ASM7) {
+ new SignatureVisitor(Opcodes.ASM9) {
private final Set<String> classes = classes1;
// class signatures may contain type arguments that contain class signatures
Deque<List<String>> pieces = new ArrayDeque<>();
@Override
public void visitInnerClassType(String name) {
- pieces.peek().add(name);
+ pieces.element().add(name);
}
@Override
public void visitClassType(String name) {
- pieces.push(new ArrayList<>());
- pieces.peek().add(name);
+ List<String> classType = new ArrayList<>();
+ classType.add(name);
+ pieces.push(classType);
}
@Override
@@ -434,20 +475,32 @@ public class IntegrationTestSupport {
});
}
- static Map<String, byte[]> runTurbine(Map<String, String> input, ImmutableList<Path> classpath)
+ public static Map<String, byte[]> runTurbine(
+ Map<String, String> input, ImmutableList<Path> classpath) throws IOException {
+ return runTurbine(input, classpath, ImmutableList.of());
+ }
+
+ public static Map<String, byte[]> runTurbine(
+ Map<String, String> input, ImmutableList<Path> classpath, ImmutableList<String> javacopts)
throws IOException {
return runTurbine(
- input, classpath, TURBINE_BOOTCLASSPATH, /* moduleVersion= */ Optional.empty());
+ input, classpath, TURBINE_BOOTCLASSPATH, /* moduleVersion= */ Optional.empty(), javacopts);
}
static Map<String, byte[]> runTurbine(
Map<String, String> input,
ImmutableList<Path> classpath,
ClassPath bootClassPath,
- Optional<String> moduleVersion)
+ Optional<String> moduleVersion,
+ ImmutableList<String> javacopts)
throws IOException {
BindingResult bound = turbineAnalysis(input, classpath, bootClassPath, moduleVersion);
- return Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()).bytes();
+ return Lower.lowerAll(
+ LanguageVersion.fromJavacopts(javacopts),
+ bound.units(),
+ bound.modules(),
+ bound.classPathEnv())
+ .bytes();
}
public static BindingResult turbineAnalysis(
@@ -510,7 +563,7 @@ public class IntegrationTestSupport {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs)
throws IOException {
- if (path.getFileName().toString().endsWith(".class")) {
+ if (getFileExtension(path).equals("class")) {
classes.add(path);
}
return FileVisitResult.CONTINUE;
@@ -551,7 +604,9 @@ public class IntegrationTestSupport {
fileManager.setLocationFromPaths(StandardLocation.CLASS_OUTPUT, ImmutableList.of(out));
fileManager.setLocationFromPaths(StandardLocation.CLASS_PATH, classpath);
fileManager.setLocationFromPaths(StandardLocation.locationFor("MODULE_PATH"), classpath);
- if (inputs.stream().filter(i -> i.getFileName().toString().equals("module-info.java")).count()
+ if (inputs.stream()
+ .filter(i -> requireNonNull(i.getFileName()).toString().equals("module-info.java"))
+ .count()
> 1) {
// multi-module mode
fileManager.setLocationFromPaths(
@@ -578,7 +633,7 @@ public class IntegrationTestSupport {
na = na.substring(1);
}
sb.append(String.format("=== %s ===\n", na));
- sb.append(AsmUtils.textify(compiled.get(key)));
+ sb.append(AsmUtils.textify(compiled.get(key), /* skipDebug= */ true));
}
return sb.toString();
}
@@ -634,4 +689,10 @@ public class IntegrationTestSupport {
return new TestInput(sources, classes);
}
}
+
+ public static int getMajor() {
+ return Runtime.version().feature();
+ }
+
+ private IntegrationTestSupport() {}
}
diff --git a/javatests/com/google/turbine/lower/LongStringIntegrationTest.java b/javatests/com/google/turbine/lower/LongStringIntegrationTest.java
new file mode 100644
index 0000000..a462b69
--- /dev/null
+++ b/javatests/com/google/turbine/lower/LongStringIntegrationTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.google.turbine.lower;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(JUnit4.class)
+public class LongStringIntegrationTest {
+
+ @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void test() throws Exception {
+ Map<String, byte[]> output =
+ runTurbineWithStack(
+ /* stackSize= */ 1,
+ /* input= */ ImmutableMap.of("Test.java", source()),
+ /* classpath= */ ImmutableList.of());
+
+ assertThat(output.keySet()).containsExactly("Test");
+ String result = fieldValue(output.get("Test"));
+ assertThat(result).startsWith("...");
+ assertThat(result).hasLength(10000);
+ }
+
+ private static Map<String, byte[]> runTurbineWithStack(
+ int stackSize, ImmutableMap<String, String> input, ImmutableList<Path> classpath)
+ throws InterruptedException {
+ Map<String, byte[]> output = new HashMap<>();
+ Thread t =
+ new Thread(
+ /* group= */ null,
+ () -> {
+ try {
+ output.putAll(IntegrationTestSupport.runTurbine(input, classpath));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ },
+ /* name= */ "turbine",
+ stackSize);
+ t.run();
+ t.join();
+ return output;
+ }
+
+ /** Extract the string value of a constant field from the class file. */
+ private static String fieldValue(byte[] classFile) {
+ String[] result = {null};
+ new ClassReader(classFile)
+ .accept(
+ new ClassVisitor(Opcodes.ASM9) {
+ @Override
+ public FieldVisitor visitField(
+ int access, String name, String desc, String signature, Object value) {
+ result[0] = (String) value;
+ return null;
+ }
+ },
+ 0);
+ return result[0];
+ }
+
+ /** Create a source file with a long concatenated string literal: {@code "" + "." + "." + ...}. */
+ private static String source() {
+ StringBuilder input = new StringBuilder();
+ input.append("class Test { public static final String C = \"\"");
+ for (int i = 0; i < 10000; i++) {
+ input.append("+ \".\"\n");
+ }
+ input.append("; }");
+ return input.toString();
+ }
+}
diff --git a/javatests/com/google/turbine/lower/LowerIntegrationTest.java b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
index 85c3450..97170ca 100644
--- a/javatests/com/google/turbine/lower/LowerIntegrationTest.java
+++ b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
@@ -17,12 +17,13 @@
package com.google.turbine.lower;
import static com.google.common.truth.Truth.assertThat;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static com.google.turbine.testing.TestResources.getResource;
import static java.util.stream.Collectors.toList;
+import static org.junit.Assume.assumeTrue;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
-import com.google.common.io.ByteStreams;
import java.io.IOError;
import java.io.IOException;
import java.nio.file.Files;
@@ -42,29 +43,112 @@ import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class LowerIntegrationTest {
+ private static final ImmutableMap<String, Integer> SOURCE_VERSION =
+ ImmutableMap.of("record.test", 16, "record2.test", 16, "sealed.test", 17);
+
@Parameters(name = "{index}: {0}")
public static Iterable<Object[]> parameters() {
String[] testCases = {
+ // keep-sorted start
+ "B33513475.test",
+ "B33513475b.test",
+ "B33513475c.test",
+ "B70953542.test",
+ "B8056066.test",
+ "B8056066b.test",
+ "B8075274.test",
+ "B8148131.test",
"abstractenum.test",
"access1.test",
+ "ambiguous_identifier.test",
+ "anno_const_coerce.test",
+ "anno_const_scope.test",
+ "anno_nested.test",
+ "anno_repeated.test",
+ "anno_self_const.test",
+ "anno_void.test",
+ "annoconstvis.test",
+ "annotation_bool_default.test",
+ "annotation_class_default.test",
+ "annotation_clinit.test",
+ "annotation_declaration.test",
+ "annotation_enum_default.test",
+ "annotation_scope.test",
+ "annotations_default.test",
+ "annouse.test",
+ "annouse10.test",
+ "annouse11.test",
+ "annouse12.test",
+ "annouse13.test",
+ "annouse14.test",
+ "annouse15.test",
+ "annouse16.test",
+ "annouse17.test",
+ "annouse2.test",
+ "annouse3.test",
+ "annouse4.test",
+ "annouse5.test",
+ "annouse6.test",
+ "annouse7.test",
+ "annouse8.test",
+ "annouse9.test",
+ "annovis.test",
"anonymous.test",
+ "array_class_literal.test",
+ "ascii_sub.test",
"asset.test",
- "outerparam.test",
"basic_field.test",
"basic_nested.test",
"bcp.test",
+ "bmethod.test",
+ "bounds.test",
+ "boxed_const.test",
"builder.test",
"byte.test",
"byte2.test",
+ "bytecode_boolean_const.test",
+ "bytenoncanon.test",
+ "c_array.test",
+ "canon.test",
+ "canon_class_header.test",
+ "canon_recursive.test",
+ "cast_tail.test",
"circ_cvar.test",
"clash.test",
+ "complex_param_anno.test",
+ "concat.test",
+ "const.test",
+ "const_all.test",
+ "const_arith.test",
+ "const_boxed.test",
+ "const_byte.test",
+ "const_char.test",
+ "const_conditional.test",
+ "const_conv.test",
+ "const_field.test",
+ "const_hiding.test",
+ "const_moreexpr.test",
+ "const_multi.test",
+ "const_nonfinal.test",
+ "const_octal_underscore.test",
+ "const_operation_order.test",
+ "const_types.test",
+ "const_underscore.test",
+ "constlevel.test",
+ "constpack.test",
+ "ctor_anno.test",
"ctorvis.test",
"cvar_qualified.test",
"cycle.test",
"default_fbound.test",
"default_rawfbound.test",
"default_simple.test",
+ "deficient_types_classfile.test",
+ "dollar.test",
+ "empty_package_info.test",
"enum1.test",
+ "enum_abstract.test",
+ "enum_final.test",
"enumctor.test",
"enumctor2.test",
"enumimpl.test",
@@ -78,6 +162,7 @@ public class LowerIntegrationTest {
"enumint_objectmethod2.test",
"enumint_objectmethod_raw.test",
"enuminthacks.test",
+ "enummemberanno.test",
"enumstat.test",
"erasurebound.test",
"existingctor.test",
@@ -87,230 +172,160 @@ public class LowerIntegrationTest {
"extendsandimplements.test",
"extrainnerclass.test",
"fbound.test",
+ "field_anno.test",
"firstcomparator.test",
+ "float_exponent.test",
"fuse.test",
"genericarrayfield.test",
"genericexn.test",
"genericexn2.test",
+ "genericnoncanon.test",
+ "genericnoncanon1.test",
+ "genericnoncanon10.test",
+ "genericnoncanon2.test",
+ "genericnoncanon3.test",
+ "genericnoncanon4.test",
+ "genericnoncanon5.test",
+ "genericnoncanon6.test",
+ "genericnoncanon8.test",
+ "genericnoncanon9.test",
+ "genericnoncanon_byte.test",
+ "genericnoncanon_method3.test",
"genericret.test",
+ "hex_int.test",
"hierarchy.test",
"ibound.test",
"icu.test",
"icu2.test",
+ "import_wild_order.test",
+ "importconst.test",
"importinner.test",
+ "inner_static.test",
+ "innerannodecl.test",
+ "innerclassanno.test",
"innerctor.test",
"innerenum.test",
"innerint.test",
"innerstaticgeneric.test",
+ "interface_field.test",
+ "interface_member_public.test",
+ "interface_method.test",
"interfacemem.test",
"interfaces.test",
+ // TODO(cushon): crashes ASM, see:
+ // https://gitlab.ow2.org/asm/asm/issues/317776
+ // "canon_array.test",
+ "java_lang_object.test",
+ "javadoc_deprecated.test",
"lexical.test",
"lexical2.test",
"lexical4.test",
"list.test",
+ "local.test",
+ "long_expression.test",
"loopthroughb.test",
"mapentry.test",
+ "marker.test",
"member.test",
+ "member_import_clash.test",
+ // TODO(cushon): support for source level 9 in integration tests
+ // "B74332665.test",
+ "memberimport.test",
"mods.test",
"morefields.test",
"moremethods.test",
"multifield.test",
"nested.test",
"nested2.test",
+ "nested_member_import.test",
+ "nested_member_import_noncanon.test",
+ "non_const.test",
+ "noncanon.test",
+ "noncanon_static_wild.test",
+ "nonconst_unary_expression.test",
"one.test",
"outer.test",
+ "outerparam.test",
+ "package_info.test",
+ "packagedecl.test",
"packageprivateprotectedinner.test",
"param_bound.test",
+ "prim_class.test",
+ "private_member.test",
"privateinner.test",
"proto.test",
"proto2.test",
"qual.test",
"raw.test",
"raw2.test",
+ "raw_canon.test",
+ "rawcanon.test",
"rawfbound.test",
+ "receiver_param.test",
+ "record.test",
+ "record2.test",
"rek.test",
"samepkg.test",
+ "sealed.test",
"self.test",
"semi.test",
+ // https://bugs.openjdk.java.net/browse/JDK-8054064 ?
+ "shadow_inherited.test",
"simple.test",
"simplemethod.test",
+ "source_anno_retention.test",
+ "source_bootclasspath_order.test",
+ "static_final_boxed.test",
+ "static_member_type_import.test",
+ "static_member_type_import_recursive.test",
+ "static_type_import.test",
+ "strictfp.test",
"string.test",
+ "string_const.test",
"superabstract.test",
"supplierfunction.test",
"tbound.test",
+ "tyanno_inner.test",
+ "tyanno_varargs.test",
"typaram.test",
+ "typaram_lookup.test",
+ "typaram_lookup_enclosing.test",
+ "type_anno_ambiguous.test",
+ "type_anno_ambiguous_param.test",
+ "type_anno_ambiguous_qualified.test",
+ "type_anno_array_bound.test",
+ "type_anno_array_dims.test",
+ "type_anno_c_array.test",
+ "type_anno_cstyle_array_dims.test",
+ "type_anno_hello.test",
+ "type_anno_order.test",
+ "type_anno_parameter_index.test",
+ "type_anno_qual.test",
+ "type_anno_raw.test",
+ "type_anno_receiver.test",
+ "type_anno_retention.test",
+ "type_anno_return.test",
+ "tyvar_bound.test",
"tyvarfield.test",
+ "unary.test",
+ "underscore_literal.test",
+ "unicode.test",
+ "unicode_pkg.test",
"useextend.test",
"vanillaexception.test",
"varargs.test",
- "wild.test",
- "bytenoncanon.test",
- "canon.test",
- "genericnoncanon.test",
- "genericnoncanon1.test",
- "genericnoncanon10.test",
- "genericnoncanon2.test",
- "genericnoncanon3.test",
- "genericnoncanon4.test",
- "genericnoncanon5.test",
- "genericnoncanon6.test",
- "genericnoncanon8.test",
- "genericnoncanon9.test",
- "genericnoncanon_byte.test",
- "genericnoncanon_method3.test",
- "noncanon.test",
- "rawcanon.test",
- "wildboundcanon.test",
- "wildcanon.test",
- "annoconstvis.test",
- "const_byte.test",
- "const_char.test",
- "const_field.test",
- "const_types.test",
- "const_underscore.test",
- "constlevel.test",
- "constpack.test",
- "importconst.test",
- "const.test",
- "const_all.test",
- "const_arith.test",
- "const_conditional.test",
- "const_moreexpr.test",
- "const_multi.test",
- "field_anno.test",
- "annotation_bool_default.test",
- "annotation_class_default.test",
- "annotation_declaration.test",
- "annotation_enum_default.test",
- "annotations_default.test",
- "annouse.test",
- "annouse10.test",
- "annouse11.test",
- "annouse12.test",
- "annouse13.test",
- "annouse14.test",
- "annouse15.test",
- "annouse16.test",
- "annouse17.test",
- "annouse2.test",
- "annouse3.test",
- "annouse4.test",
- "annouse5.test",
- "annouse6.test",
- "annouse7.test",
- "annouse8.test",
- "annouse9.test",
- "annovis.test",
- "complex_param_anno.test",
- "enummemberanno.test",
- "innerannodecl.test",
- "source_anno_retention.test",
- "anno_nested.test",
- "nested_member_import.test",
- "nested_member_import_noncanon.test",
- "unary.test",
- "hex_int.test",
- "const_conv.test",
- "bmethod.test",
- "prim_class.test",
- "wild2.test",
- "wild3.test",
- "const_hiding.test",
- "interface_field.test",
- "concat.test",
- "static_type_import.test",
- "non_const.test",
- "bounds.test",
- "cast_tail.test",
- "marker.test",
- "interface_method.test",
- "raw_canon.test",
- "float_exponent.test",
- "boxed_const.test",
- "package_info.test",
- "import_wild_order.test",
- "canon_recursive.test",
- // TODO(cushon): crashes ASM, see:
- // https://gitlab.ow2.org/asm/asm/issues/317776
- // "canon_array.test",
- "java_lang_object.test",
+ "visible_nested.test",
"visible_package.test",
+ "visible_package_private_toplevel.test",
"visible_private.test",
- "visible_same_package.test",
- "private_member.test",
- "visible_nested.test",
"visible_qualified.test",
- "ascii_sub.test",
- "bytecode_boolean_const.test",
- "tyvar_bound.test",
- "type_anno_hello.test",
- "type_anno_array_dims.test",
- "nonconst_unary_expression.test",
- "type_anno_ambiguous.test",
- "type_anno_ambiguous_param.test",
- "unicode.test",
- "annotation_scope.test",
- "visible_package_private_toplevel.test",
- "receiver_param.test",
- "static_member_type_import.test",
- "type_anno_qual.test",
- "array_class_literal.test",
- "underscore_literal.test",
- "c_array.test",
- "type_anno_retention.test",
- "member_import_clash.test",
- "anno_repeated.test",
- "long_expression.test",
- "const_nonfinal.test",
- "enum_abstract.test",
- "deficient_types_classfile.test",
- "ctor_anno.test",
- "anno_const_coerce.test",
- "const_octal_underscore.test",
- "const_boxed.test",
- "interface_member_public.test",
- "javadoc_deprecated.test",
- "strictfp.test",
- "type_anno_raw.test",
- "inner_static.test",
- "innerclassanno.test",
- "type_anno_parameter_index.test",
- "anno_const_scope.test",
- "type_anno_ambiguous_qualified.test",
- "type_anno_array_bound.test",
- "type_anno_return.test",
- "type_anno_order.test",
- "canon_class_header.test",
- "type_anno_receiver.test",
- "enum_final.test",
- "dollar.test",
- "typaram_lookup.test",
- "typaram_lookup_enclosing.test",
- "B33513475.test",
- "B33513475b.test",
- "B33513475c.test",
- "noncanon_static_wild.test",
- "B8075274.test",
- "B8148131.test",
- "B8056066.test",
- "B8056066b.test",
- "source_bootclasspath_order.test",
- "anno_self_const.test",
- "type_anno_cstyle_array_dims.test",
- "packagedecl.test",
- "static_member_type_import_recursive.test",
- "B70953542.test",
- // TODO(cushon): support for source level 9 in integration tests
- // "B74332665.test",
- "memberimport.test",
- "type_anno_c_array.test",
- // https://bugs.openjdk.java.net/browse/JDK-8054064 ?
- "shadow_inherited.test",
- "static_final_boxed.test",
- "anno_void.test",
- "tyanno_varargs.test",
- "tyanno_inner.test",
- "local.test",
+ "visible_same_package.test",
+ "wild.test",
+ "wild2.test",
+ "wild3.test",
+ "wildboundcanon.test",
+ "wildcanon.test",
+ // keep-sorted end
};
List<Object[]> tests =
ImmutableList.copyOf(testCases).stream().map(x -> new Object[] {x}).collect(toList());
@@ -344,10 +359,7 @@ public class LowerIntegrationTest {
public void test() throws Exception {
IntegrationTestSupport.TestInput input =
- IntegrationTestSupport.TestInput.parse(
- new String(
- ByteStreams.toByteArray(getClass().getResourceAsStream("testdata/" + test)),
- UTF_8));
+ IntegrationTestSupport.TestInput.parse(getResource(getClass(), "testdata/" + test));
ImmutableList<Path> classpathJar = ImmutableList.of();
if (!input.classes.isEmpty()) {
@@ -363,9 +375,16 @@ public class LowerIntegrationTest {
classpathJar = ImmutableList.of(lib);
}
- Map<String, byte[]> expected = IntegrationTestSupport.runJavac(input.sources, classpathJar);
+ int version = SOURCE_VERSION.getOrDefault(test, 8);
+ assumeTrue(version <= Runtime.version().feature());
+ ImmutableList<String> javacopts =
+ ImmutableList.of("-source", String.valueOf(version), "-target", String.valueOf(version));
+
+ Map<String, byte[]> expected =
+ IntegrationTestSupport.runJavac(input.sources, classpathJar, javacopts);
- Map<String, byte[]> actual = IntegrationTestSupport.runTurbine(input.sources, classpathJar);
+ Map<String, byte[]> actual =
+ IntegrationTestSupport.runTurbine(input.sources, classpathJar, javacopts);
assertThat(IntegrationTestSupport.dump(IntegrationTestSupport.sortMembers(actual)))
.isEqualTo(IntegrationTestSupport.dump(IntegrationTestSupport.canonicalize(expected)));
diff --git a/javatests/com/google/turbine/lower/LowerTest.java b/javatests/com/google/turbine/lower/LowerTest.java
index 8151e81..6d3a6df 100644
--- a/javatests/com/google/turbine/lower/LowerTest.java
+++ b/javatests/com/google/turbine/lower/LowerTest.java
@@ -18,13 +18,13 @@ package com.google.turbine.lower;
import static com.google.common.truth.Truth.assertThat;
import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.fail;
+import static com.google.turbine.testing.TestResources.getResource;
+import static java.util.Objects.requireNonNull;
+import static org.junit.Assert.assertThrows;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import com.google.common.io.ByteStreams;
import com.google.turbine.binder.Binder;
import com.google.turbine.binder.Binder.BindingResult;
import com.google.turbine.binder.ClassPathBinder;
@@ -41,6 +41,7 @@ import com.google.turbine.diag.TurbineError;
import com.google.turbine.model.TurbineConstantTypeKind;
import com.google.turbine.model.TurbineFlag;
import com.google.turbine.model.TurbineTyKind;
+import com.google.turbine.options.LanguageVersion;
import com.google.turbine.parse.Parser;
import com.google.turbine.testing.AsmUtils;
import com.google.turbine.type.Type;
@@ -184,9 +185,11 @@ public class LowerTest {
SourceTypeBoundClass c =
new SourceTypeBoundClass(
interfaceTypes,
+ ImmutableList.of(),
xtnds,
tps,
access,
+ ImmutableList.of(),
methods,
fields,
owner,
@@ -204,11 +207,13 @@ public class LowerTest {
SourceTypeBoundClass i =
new SourceTypeBoundClass(
ImmutableList.of(),
+ ImmutableList.of(),
Type.ClassTy.OBJECT,
ImmutableMap.of(),
TurbineFlag.ACC_STATIC | TurbineFlag.ACC_PROTECTED,
ImmutableList.of(),
ImmutableList.of(),
+ ImmutableList.of(),
new ClassSymbol("test/Test"),
TurbineTyKind.CLASS,
ImmutableMap.of("Inner", new ClassSymbol("test/Test$Inner")),
@@ -227,24 +232,17 @@ public class LowerTest {
Map<String, byte[]> bytes =
Lower.lowerAll(
+ LanguageVersion.createDefault(),
ImmutableMap.of(
new ClassSymbol("test/Test"), c, new ClassSymbol("test/Test$Inner"), i),
ImmutableList.of(),
TURBINE_BOOTCLASSPATH.env())
.bytes();
- assertThat(AsmUtils.textify(bytes.get("test/Test")))
- .isEqualTo(
- new String(
- ByteStreams.toByteArray(
- LowerTest.class.getResourceAsStream("testdata/golden/outer.txt")),
- UTF_8));
- assertThat(AsmUtils.textify(bytes.get("test/Test$Inner")))
- .isEqualTo(
- new String(
- ByteStreams.toByteArray(
- LowerTest.class.getResourceAsStream("testdata/golden/inner.txt")),
- UTF_8));
+ assertThat(AsmUtils.textify(bytes.get("test/Test"), /* skipDebug= */ false))
+ .isEqualTo(getResource(LowerTest.class, "testdata/golden/outer.txt"));
+ assertThat(AsmUtils.textify(bytes.get("test/Test$Inner"), /* skipDebug= */ false))
+ .isEqualTo(getResource(LowerTest.class, "testdata/golden/inner.txt"));
}
@Test
@@ -264,11 +262,16 @@ public class LowerTest {
TURBINE_BOOTCLASSPATH,
/* moduleVersion=*/ Optional.empty());
Map<String, byte[]> lowered =
- Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()).bytes();
+ Lower.lowerAll(
+ LanguageVersion.createDefault(),
+ bound.units(),
+ bound.modules(),
+ bound.classPathEnv())
+ .bytes();
List<String> attributes = new ArrayList<>();
new ClassReader(lowered.get("Test$Inner$InnerMost"))
.accept(
- new ClassVisitor(Opcodes.ASM7) {
+ new ClassVisitor(Opcodes.ASM9) {
@Override
public void visitInnerClass(
String name, String outerName, String innerName, int access) {
@@ -285,10 +288,7 @@ public class LowerTest {
public void wildArrayElement() throws Exception {
IntegrationTestSupport.TestInput input =
IntegrationTestSupport.TestInput.parse(
- new String(
- ByteStreams.toByteArray(
- getClass().getResourceAsStream("testdata/canon_array.test")),
- UTF_8));
+ getResource(getClass(), "testdata/canon_array.test"));
Map<String, byte[]> actual =
IntegrationTestSupport.runTurbine(input.sources, ImmutableList.of());
@@ -342,15 +342,20 @@ public class LowerTest {
TURBINE_BOOTCLASSPATH,
/* moduleVersion=*/ Optional.empty());
Map<String, byte[]> lowered =
- Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()).bytes();
+ Lower.lowerAll(
+ LanguageVersion.createDefault(),
+ bound.units(),
+ bound.modules(),
+ bound.classPathEnv())
+ .bytes();
TypePath[] path = new TypePath[1];
new ClassReader(lowered.get("Test"))
.accept(
- new ClassVisitor(Opcodes.ASM7) {
+ new ClassVisitor(Opcodes.ASM9) {
@Override
public FieldVisitor visitField(
int access, String name, String desc, String signature, Object value) {
- return new FieldVisitor(Opcodes.ASM7) {
+ return new FieldVisitor(Opcodes.ASM9) {
@Override
public AnnotationVisitor visitTypeAnnotation(
int typeRef, TypePath typePath, String desc, boolean visible) {
@@ -397,7 +402,7 @@ public class LowerTest {
Map<String, Object> values = new LinkedHashMap<>();
new ClassReader(actual.get("Test"))
.accept(
- new ClassVisitor(Opcodes.ASM7) {
+ new ClassVisitor(Opcodes.ASM9) {
@Override
public FieldVisitor visitField(
int access, String name, String desc, String signature, Object value) {
@@ -420,11 +425,16 @@ public class LowerTest {
TURBINE_BOOTCLASSPATH,
/* moduleVersion=*/ Optional.empty());
Map<String, byte[]> lowered =
- Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()).bytes();
+ Lower.lowerAll(
+ LanguageVersion.createDefault(),
+ bound.units(),
+ bound.modules(),
+ bound.classPathEnv())
+ .bytes();
int[] acc = {0};
new ClassReader(lowered.get("Test"))
.accept(
- new ClassVisitor(Opcodes.ASM7) {
+ new ClassVisitor(Opcodes.ASM9) {
@Override
public void visit(
int version,
@@ -488,7 +498,7 @@ public class LowerTest {
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
sources.forEach(
(k, v) -> builder.put(k, v.replaceAll("import static b\\.B\\.nosuch\\..*;", "")));
- noImports = builder.build();
+ noImports = builder.buildOrThrow();
}
Map<String, byte[]> expected = IntegrationTestSupport.runJavac(noImports, ImmutableList.of());
@@ -522,16 +532,11 @@ public class LowerTest {
Path libJar = temporaryFolder.newFile("lib.jar").toPath();
try (OutputStream os = Files.newOutputStream(libJar);
JarOutputStream jos = new JarOutputStream(os)) {
- jos.putNextEntry(new JarEntry("A$M.class"));
- jos.write(lib.get("A$M"));
- jos.putNextEntry(new JarEntry("A$M$I.class"));
- jos.write(lib.get("A$M$I"));
- jos.putNextEntry(new JarEntry("B.class"));
- jos.write(lib.get("B"));
- jos.putNextEntry(new JarEntry("B$BM.class"));
- jos.write(lib.get("B$BM"));
- jos.putNextEntry(new JarEntry("B$BM$BI.class"));
- jos.write(lib.get("B$BM$BI"));
+ write(jos, lib, "A$M");
+ write(jos, lib, "A$M$I");
+ write(jos, lib, "B");
+ write(jos, lib, "B$BM");
+ write(jos, lib, "B$BM$BI");
}
ImmutableMap<String, String> sources =
@@ -544,14 +549,13 @@ public class LowerTest {
"}"))
.build();
- try {
- IntegrationTestSupport.runTurbine(sources, ImmutableList.of(libJar));
- fail();
- } catch (TurbineError error) {
- assertThat(error)
- .hasMessageThat()
- .contains("Test.java: error: could not locate class file for A");
- }
+ TurbineError error =
+ assertThrows(
+ TurbineError.class,
+ () -> IntegrationTestSupport.runTurbine(sources, ImmutableList.of(libJar)));
+ assertThat(error)
+ .hasMessageThat()
+ .contains("Test.java: error: could not locate class file for A");
}
@Test
@@ -579,16 +583,11 @@ public class LowerTest {
Path libJar = temporaryFolder.newFile("lib.jar").toPath();
try (OutputStream os = Files.newOutputStream(libJar);
JarOutputStream jos = new JarOutputStream(os)) {
- jos.putNextEntry(new JarEntry("A$M.class"));
- jos.write(lib.get("A$M"));
- jos.putNextEntry(new JarEntry("A$M$I.class"));
- jos.write(lib.get("A$M$I"));
- jos.putNextEntry(new JarEntry("B.class"));
- jos.write(lib.get("B"));
- jos.putNextEntry(new JarEntry("B$BM.class"));
- jos.write(lib.get("B$BM"));
- jos.putNextEntry(new JarEntry("B$BM$BI.class"));
- jos.write(lib.get("B$BM$BI"));
+ write(jos, lib, "A$M");
+ write(jos, lib, "A$M$I");
+ write(jos, lib, "B");
+ write(jos, lib, "B$BM");
+ write(jos, lib, "B$BM$BI");
}
ImmutableMap<String, String> sources =
@@ -603,18 +602,15 @@ public class LowerTest {
"}"))
.build();
- try {
- IntegrationTestSupport.runTurbine(sources, ImmutableList.of(libJar));
- fail();
- } catch (TurbineError error) {
- assertThat(error)
- .hasMessageThat()
- .contains(
- lines(
- "Test.java:3: error: could not locate class file for A",
- " I i;",
- " ^"));
- }
+ TurbineError error =
+ assertThrows(
+ TurbineError.class,
+ () -> IntegrationTestSupport.runTurbine(sources, ImmutableList.of(libJar)));
+ assertThat(error)
+ .hasMessageThat()
+ .contains(
+ lines(
+ "Test.java:3: error: could not locate class file for A", " I i;", " ^"));
}
// If an element incorrectly has multiple visibility modifiers, pick one, and rely on javac to
@@ -629,7 +625,7 @@ public class LowerTest {
int[] testAccess = {0};
new ClassReader(lowered.get("Test"))
.accept(
- new ClassVisitor(Opcodes.ASM7) {
+ new ClassVisitor(Opcodes.ASM9) {
@Override
public void visit(
int version,
@@ -646,7 +642,46 @@ public class LowerTest {
assertThat((testAccess[0] & TurbineFlag.ACC_PROTECTED)).isNotEqualTo(TurbineFlag.ACC_PROTECTED);
}
+ @Test
+ public void minClassVersion() throws Exception {
+ BindingResult bound =
+ Binder.bind(
+ ImmutableList.of(Parser.parse("class Test {}")),
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.empty());
+ Map<String, byte[]> lowered =
+ Lower.lowerAll(
+ LanguageVersion.fromJavacopts(ImmutableList.of("-source", "7", "-target", "7")),
+ bound.units(),
+ bound.modules(),
+ bound.classPathEnv())
+ .bytes();
+ int[] major = {0};
+ new ClassReader(lowered.get("Test"))
+ .accept(
+ new ClassVisitor(Opcodes.ASM9) {
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ major[0] = version;
+ }
+ },
+ 0);
+ assertThat(major[0]).isEqualTo(Opcodes.V1_8);
+ }
+
static String lines(String... lines) {
return Joiner.on(System.lineSeparator()).join(lines);
}
+
+ static void write(JarOutputStream jos, Map<String, byte[]> lib, String name) throws IOException {
+ jos.putNextEntry(new JarEntry(name + ".class"));
+ jos.write(requireNonNull(lib.get(name)));
+ }
}
diff --git a/javatests/com/google/turbine/lower/MissingJavaBaseModule.java b/javatests/com/google/turbine/lower/MissingJavaBaseModule.java
new file mode 100644
index 0000000..230b18f
--- /dev/null
+++ b/javatests/com/google/turbine/lower/MissingJavaBaseModule.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.google.turbine.lower;
+
+import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_VERSION;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.turbine.binder.ClassPath;
+import com.google.turbine.binder.CtSymClassBinder;
+import com.google.turbine.binder.JimageClassBinder;
+import com.google.turbine.binder.bound.ModuleInfo;
+import com.google.turbine.binder.bytecode.BytecodeBoundClass;
+import com.google.turbine.binder.env.Env;
+import com.google.turbine.binder.lookup.TopLevelIndex;
+import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.binder.sym.ModuleSymbol;
+import java.util.Map;
+import java.util.Optional;
+import org.jspecify.nullness.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MissingJavaBaseModule {
+
+ @Test
+ public void test() throws Exception {
+
+ Map<String, String> sources = ImmutableMap.of("module-info.java", "module foo {}");
+
+ Map<String, byte[]> expected =
+ IntegrationTestSupport.runJavac(
+ sources, ImmutableList.of(), ImmutableList.of("--release", "9", "--module-version=42"));
+
+ ClassPath base =
+ Double.parseDouble(JAVA_CLASS_VERSION.value()) < 54
+ ? JimageClassBinder.bindDefault()
+ : CtSymClassBinder.bind(9);
+ ClassPath bootclasspath =
+ new ClassPath() {
+ @Override
+ public Env<ClassSymbol, BytecodeBoundClass> env() {
+ return base.env();
+ }
+
+ @Override
+ public Env<ModuleSymbol, ModuleInfo> moduleEnv() {
+ return new Env<ModuleSymbol, ModuleInfo>() {
+ @Override
+ public @Nullable ModuleInfo get(ModuleSymbol sym) {
+ if (sym.name().equals("java.base")) {
+ return null;
+ }
+ return base.moduleEnv().get(sym);
+ }
+ };
+ }
+
+ @Override
+ public TopLevelIndex index() {
+ return base.index();
+ }
+
+ @Override
+ public @Nullable Supplier<byte[]> resource(String path) {
+ return base.resource(path);
+ }
+ };
+ Map<String, byte[]> actual =
+ IntegrationTestSupport.runTurbine(
+ sources,
+ ImmutableList.of(),
+ bootclasspath,
+ Optional.of("42"),
+ /* javacopts= */ ImmutableList.of());
+
+ assertEquals(dump(expected), dump(actual));
+ }
+
+ private String dump(Map<String, byte[]> map) throws Exception {
+ return IntegrationTestSupport.dump(
+ map.entrySet().stream()
+ .filter(e -> e.getKey().endsWith("module-info"))
+ .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)));
+ }
+}
diff --git a/javatests/com/google/turbine/lower/ModuleIntegrationTest.java b/javatests/com/google/turbine/lower/ModuleIntegrationTest.java
index 03c6fb7..0157fea 100644
--- a/javatests/com/google/turbine/lower/ModuleIntegrationTest.java
+++ b/javatests/com/google/turbine/lower/ModuleIntegrationTest.java
@@ -18,12 +18,11 @@ package com.google.turbine.lower;
import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_VERSION;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static com.google.turbine.testing.TestResources.getResource;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertEquals;
import com.google.common.collect.ImmutableList;
-import com.google.common.io.ByteStreams;
import com.google.turbine.binder.CtSymClassBinder;
import com.google.turbine.binder.JimageClassBinder;
import java.nio.file.Files;
@@ -68,10 +67,7 @@ public class ModuleIntegrationTest {
}
IntegrationTestSupport.TestInput input =
- IntegrationTestSupport.TestInput.parse(
- new String(
- ByteStreams.toByteArray(getClass().getResourceAsStream("moduletestdata/" + test)),
- UTF_8));
+ IntegrationTestSupport.TestInput.parse(getResource(getClass(), "moduletestdata/" + test));
ImmutableList<Path> classpathJar = ImmutableList.of();
if (!input.classes.isEmpty()) {
@@ -100,8 +96,9 @@ public class ModuleIntegrationTest {
classpathJar,
Double.parseDouble(JAVA_CLASS_VERSION.value()) < 54
? JimageClassBinder.bindDefault()
- : CtSymClassBinder.bind("9"),
- Optional.of("42"));
+ : CtSymClassBinder.bind(9),
+ Optional.of("42"),
+ /* javacopts= */ ImmutableList.of());
assertEquals(dump(expected), dump(actual));
}
diff --git a/javatests/com/google/turbine/lower/testdata/ambiguous_identifier.test b/javatests/com/google/turbine/lower/testdata/ambiguous_identifier.test
new file mode 100644
index 0000000..d7bbc54
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/ambiguous_identifier.test
@@ -0,0 +1,14 @@
+=== Test.java ===
+class Test {
+ static final int non = 42;
+ static final int sealed = 1;
+ // here 'non-sealed' is a binary expression subtracting two identifiers,
+ // not a contextual hyphenated keyword
+ static final int x = non-sealed;
+}
+
+// handle backtracking when we see 'non', but it isn't part of a contextualy
+// hyphenated keyword 'non-sealed'
+class non {
+ non self;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/annotation_clinit.test b/javatests/com/google/turbine/lower/testdata/annotation_clinit.test
new file mode 100644
index 0000000..7419ed6
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/annotation_clinit.test
@@ -0,0 +1,18 @@
+%%% pkg/Anno.java %%%
+package pkg;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Anno {
+ String CONSTANT = Anno.class.toString();
+
+ String value() default "";
+}
+
+=== pkg/T.java ===
+package pkg;
+
+@Anno
+class T {}
diff --git a/javatests/com/google/turbine/lower/testdata/array_class_literal.test b/javatests/com/google/turbine/lower/testdata/array_class_literal.test
index 9033b04..4287cdf 100644
--- a/javatests/com/google/turbine/lower/testdata/array_class_literal.test
+++ b/javatests/com/google/turbine/lower/testdata/array_class_literal.test
@@ -1,4 +1,6 @@
=== Test.java ===
+import java.util.Map;
+
@interface Anno {
Class<?> value() default Object.class;
}
@@ -8,4 +10,5 @@ class Test {
@Anno(byte[][].class) int b;
@Anno(int[][].class) int c;
@Anno(Object[].class) int d;
+ @Anno(Map.Entry[].class) int e;
}
diff --git a/javatests/com/google/turbine/lower/testdata/const_operation_order.test b/javatests/com/google/turbine/lower/testdata/const_operation_order.test
new file mode 100644
index 0000000..a088823
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/const_operation_order.test
@@ -0,0 +1,13 @@
+=== test/A.java ===
+package test;
+
+public class A {
+ public static final double D1 = 1.0 / (2 / 3);
+ public static final double D2 = 1.0 / 2 / 3;
+ public static final double M1 = 1.0 + (2 + 3);
+ public static final double M2 = 1.0 + 2 + 3;
+ public static final double A1 = 1.0 + (2 + 3);
+ public static final double A2 = 1.0 + 2 + 3;
+ public static final double S1 = 1.0 - (2 - 3);
+ public static final double S2 = 1.0 - 2 - 3;
+}
diff --git a/javatests/com/google/turbine/lower/testdata/empty_package_info.test b/javatests/com/google/turbine/lower/testdata/empty_package_info.test
new file mode 100644
index 0000000..be28f14
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/empty_package_info.test
@@ -0,0 +1,3 @@
+=== package-info.java ===
+
+package test;
diff --git a/javatests/com/google/turbine/lower/testdata/record.test b/javatests/com/google/turbine/lower/testdata/record.test
new file mode 100644
index 0000000..7d92c2b
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/record.test
@@ -0,0 +1,46 @@
+=== Records.java ===
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.util.List;
+
+class Records {
+ record R1() {}
+
+ private record R2() {}
+
+ @Deprecated
+ private record R3() {}
+
+ record R4<T>() {}
+
+ record R5<T>(int x) {}
+
+ record R6<T>(@Deprecated int x) {}
+
+ record R7<T>(@Deprecated int x, int... y) {}
+
+ record R8<T>() implements Comparable<R8<T>> {
+ @Override
+ public int compareTo(R8<T> other) {
+ return 0;
+ }
+ }
+
+ record R9(int x) {
+ R9(int x) {
+ this.x = x;
+ }
+ }
+
+ @Target(ElementType.TYPE_USE)
+ @interface A {}
+
+ @Target(ElementType.RECORD_COMPONENT)
+ @interface B {}
+
+ @Target({ElementType.TYPE_USE, ElementType.RECORD_COMPONENT})
+ @interface C {}
+
+ record R10<T>(@A List<@A T> x, @B int y, @C int z) {
+ }
+}
diff --git a/javatests/com/google/turbine/lower/testdata/record2.test b/javatests/com/google/turbine/lower/testdata/record2.test
new file mode 100644
index 0000000..af4093e
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/record2.test
@@ -0,0 +1,27 @@
+=== Records.java ===
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.util.Objects;
+
+class Records {
+ public record R1(String one) {
+ public R1 {
+ Objects.requireNonNull(one);
+ }
+ }
+
+ public record R2(String one) {
+ @Deprecated
+ public R2 {
+ Objects.requireNonNull(one);
+ }
+ }
+
+ public record R3<T>(T x) {
+ @Deprecated
+ public R3 {
+ Objects.requireNonNull(x);
+ }
+ }
+}
diff --git a/javatests/com/google/turbine/lower/testdata/sealed.test b/javatests/com/google/turbine/lower/testdata/sealed.test
new file mode 100644
index 0000000..0bac7b1
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/sealed.test
@@ -0,0 +1,11 @@
+=== Sealed.java ===
+
+sealed class Sealed permits Sealed.Foo, Sealed.Bar {
+ static final class Foo extends Sealed {}
+ static final class Bar extends Sealed {}
+}
+
+sealed interface ISealed permits ISealed.Foo, ISealed.Bar {
+ static final class Foo implements ISealed {}
+ static non-sealed class Bar implements ISealed {}
+}
diff --git a/javatests/com/google/turbine/lower/testdata/string_const.test b/javatests/com/google/turbine/lower/testdata/string_const.test
new file mode 100644
index 0000000..cd39c37
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/string_const.test
@@ -0,0 +1,7 @@
+=== T.java ===
+
+class T {
+ public static final String A = "" + 42;
+ public static final boolean B = "1" == "2";
+ public static final boolean C = "1" != "2";
+}
diff --git a/javatests/com/google/turbine/lower/testdata/unicode_pkg.test b/javatests/com/google/turbine/lower/testdata/unicode_pkg.test
new file mode 100644
index 0000000..85d38d9
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/unicode_pkg.test
@@ -0,0 +1,4 @@
+=== Test.java ===
+package pkg𐀀.test;
+
+class Test {}
diff --git a/javatests/com/google/turbine/main/MainTest.java b/javatests/com/google/turbine/main/MainTest.java
index 5d47632..3504891 100644
--- a/javatests/com/google/turbine/main/MainTest.java
+++ b/javatests/com/google/turbine/main/MainTest.java
@@ -23,7 +23,8 @@ import static com.google.common.truth.Truth8.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.fail;
+import static java.util.Objects.requireNonNull;
+import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -31,6 +32,7 @@ import com.google.common.io.ByteStreams;
import com.google.common.io.MoreFiles;
import com.google.protobuf.ExtensionRegistry;
import com.google.turbine.diag.TurbineError;
+import com.google.turbine.options.LanguageVersion;
import com.google.turbine.options.TurbineOptions;
import com.google.turbine.proto.ManifestProto;
import java.io.BufferedInputStream;
@@ -40,8 +42,11 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.io.Writer;
+import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Enumeration;
@@ -88,16 +93,16 @@ public class MainTest {
}
Path output = temporaryFolder.newFile("output.jar").toPath();
- try {
- Main.compile(
- optionsWithBootclasspath()
- .setSourceJars(ImmutableList.of(sourcesa.toString(), sourcesb.toString()))
- .setOutput(output.toString())
- .build());
- fail();
- } catch (TurbineError e) {
- assertThat(e).hasMessageThat().contains("error: duplicate declaration of Test");
- }
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Main.compile(
+ optionsWithBootclasspath()
+ .setSourceJars(ImmutableList.of(sourcesa.toString(), sourcesb.toString()))
+ .setOutput(output.toString())
+ .build()));
+ assertThat(e).hasMessageThat().contains("error: duplicate declaration of Test");
}
@Test
@@ -171,7 +176,7 @@ public class MainTest {
Main.compile(
TurbineOptions.builder()
- .setRelease("9")
+ .setLanguageVersion(LanguageVersion.fromJavacopts(ImmutableList.of("--release", "9")))
.setSources(ImmutableList.of(src.toString()))
.setSourceJars(ImmutableList.of(srcjar.toString()))
.setOutput(output.toString())
@@ -204,8 +209,8 @@ public class MainTest {
assertThat(entries.map(JarEntry::getName))
.containsAtLeast("META-INF/", "META-INF/MANIFEST.MF");
}
- Manifest manifest = jarFile.getManifest();
- Attributes attributes = manifest.getMainAttributes();
+ Manifest manifest = requireNonNull(jarFile.getManifest());
+ Attributes attributes = requireNonNull(manifest.getMainAttributes());
ImmutableMap<String, ?> entries =
attributes.entrySet().stream()
.collect(toImmutableMap(e -> e.getKey().toString(), Map.Entry::getValue));
@@ -215,12 +220,15 @@ public class MainTest {
"Manifest-Version", "1.0",
"Target-Label", "//foo:foo",
"Injecting-Rule-Kind", "foo_library");
- assertThat(jarFile.getEntry(JarFile.MANIFEST_NAME).getLastModifiedTime().toInstant())
+ assertThat(
+ requireNonNull(jarFile.getEntry(JarFile.MANIFEST_NAME))
+ .getLastModifiedTime()
+ .toInstant())
.isEqualTo(
LocalDateTime.of(2010, 1, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant());
}
try (JarFile jarFile = new JarFile(gensrcOutput.toFile())) {
- Manifest manifest = jarFile.getManifest();
+ Manifest manifest = requireNonNull(jarFile.getManifest());
Attributes attributes = manifest.getMainAttributes();
ImmutableMap<String, ?> entries =
attributes.entrySet().stream()
@@ -257,16 +265,16 @@ public class MainTest {
Path output = temporaryFolder.newFile("output.jar").toPath();
- try {
- Main.compile(
- TurbineOptions.builder()
- .setSources(ImmutableList.of(src.toString()))
- .setOutput(output.toString())
- .build());
- fail();
- } catch (IllegalArgumentException expected) {
- assertThat(expected).hasMessageThat().contains("java.lang");
- }
+ IllegalArgumentException expected =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ Main.compile(
+ TurbineOptions.builder()
+ .setSources(ImmutableList.of(src.toString()))
+ .setOutput(output.toString())
+ .build()));
+ assertThat(expected).hasMessageThat().contains("java.lang");
}
@Test
@@ -274,14 +282,17 @@ public class MainTest {
Path src = temporaryFolder.newFile("Test.java").toPath();
MoreFiles.asCharSink(src, UTF_8).write("public class Test {}");
- try {
- Main.compile(optionsWithBootclasspath().setSources(ImmutableList.of(src.toString())).build());
- fail();
- } catch (UsageException expected) {
- assertThat(expected)
- .hasMessageThat()
- .contains("at least one of --output, --gensrc_output, or --resource_output is required");
- }
+ UsageException expected =
+ assertThrows(
+ UsageException.class,
+ () ->
+ Main.compile(
+ optionsWithBootclasspath()
+ .setSources(ImmutableList.of(src.toString()))
+ .build()));
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("at least one of --output, --gensrc_output, or --resource_output is required");
}
@Test
@@ -471,4 +482,60 @@ public class MainTest {
assertThat(entries.map(JarEntry::getName)).containsExactly("g/Gen.class");
}
}
+
+ @Test
+ public void testGensrcDirectoryOutput() throws IOException {
+ Path src = temporaryFolder.newFile("Foo.java").toPath();
+ MoreFiles.asCharSink(src, UTF_8).write("package f; @Deprecated class Foo {}");
+
+ Path output = temporaryFolder.newFile("output.jar").toPath();
+ Path gensrc = temporaryFolder.newFolder("gensrcOutput").toPath();
+
+ Main.compile(
+ optionsWithBootclasspath()
+ .setSources(ImmutableList.of(src.toString()))
+ .setTargetLabel("//foo:foo")
+ .setInjectingRuleKind("foo_library")
+ .setOutput(output.toString())
+ .setGensrcOutput(gensrc.toString())
+ .setProcessors(ImmutableList.of(SourceGeneratingProcessor.class.getName()))
+ .build());
+
+ assertThat(listDirectoryContents(gensrc)).containsExactly(gensrc.resolve("g/Gen.java"));
+ }
+
+ @Test
+ public void testResourceDirectoryOutput() throws IOException {
+ Path src = temporaryFolder.newFile("Foo.java").toPath();
+ MoreFiles.asCharSink(src, UTF_8).write("package f; @Deprecated class Foo {}");
+
+ Path output = temporaryFolder.newFile("output.jar").toPath();
+ Path resources = temporaryFolder.newFolder("resources").toPath();
+
+ Main.compile(
+ optionsWithBootclasspath()
+ .setSources(ImmutableList.of(src.toString()))
+ .setTargetLabel("//foo:foo")
+ .setInjectingRuleKind("foo_library")
+ .setOutput(output.toString())
+ .setResourceOutput(resources.toString())
+ .setProcessors(ImmutableList.of(ClassGeneratingProcessor.class.getName()))
+ .build());
+
+ assertThat(listDirectoryContents(resources)).containsExactly(resources.resolve("g/Gen.class"));
+ }
+
+ private static ImmutableList<Path> listDirectoryContents(Path output) throws IOException {
+ ImmutableList.Builder<Path> paths = ImmutableList.builder();
+ Files.walkFileTree(
+ output,
+ new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
+ paths.add(path);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ return paths.build();
+ }
}
diff --git a/javatests/com/google/turbine/main/ReducedClasspathTest.java b/javatests/com/google/turbine/main/ReducedClasspathTest.java
index d74c640..2810481 100644
--- a/javatests/com/google/turbine/main/ReducedClasspathTest.java
+++ b/javatests/com/google/turbine/main/ReducedClasspathTest.java
@@ -20,7 +20,8 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.fail;
+import static java.util.Objects.requireNonNull;
+import static org.junit.Assert.assertThrows;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
@@ -106,7 +107,7 @@ public class ReducedClasspathTest {
try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) {
for (String className : classNames) {
jos.putNextEntry(new JarEntry(className + ".class"));
- jos.write(compiled.get(className));
+ jos.write(requireNonNull(compiled.get(className), className));
}
}
return lib;
@@ -231,19 +232,19 @@ public class ReducedClasspathTest {
Path output = temporaryFolder.newFile("output.jar").toPath();
- try {
- Main.compile(
- optionsWithBootclasspath()
- .setOutput(output.toString())
- .setSources(ImmutableList.of(src.toString()))
- .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED)
- .setClassPath(ImmutableList.of(libc.toString()))
- .setDepsArtifacts(ImmutableList.of(libcJdeps.toString()))
- .build());
- fail();
- } catch (TurbineError e) {
- assertThat(e).hasMessageThat().contains("could not resolve I");
- }
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Main.compile(
+ optionsWithBootclasspath()
+ .setOutput(output.toString())
+ .setSources(ImmutableList.of(src.toString()))
+ .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED)
+ .setClassPath(ImmutableList.of(libc.toString()))
+ .setDepsArtifacts(ImmutableList.of(libcJdeps.toString()))
+ .build()));
+ assertThat(e).hasMessageThat().contains("could not resolve I");
}
static String lines(String... lines) {
diff --git a/javatests/com/google/turbine/options/LanguageVersionTest.java b/javatests/com/google/turbine/options/LanguageVersionTest.java
new file mode 100644
index 0000000..601652c
--- /dev/null
+++ b/javatests/com/google/turbine/options/LanguageVersionTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2020 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.google.turbine.options;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import javax.lang.model.SourceVersion;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class LanguageVersionTest {
+ @Test
+ public void parseSourceVersion() {
+ assertThat(LanguageVersion.fromJavacopts(ImmutableList.of()).sourceVersion())
+ .isEqualTo(SourceVersion.RELEASE_8);
+ assertThat(
+ LanguageVersion.fromJavacopts(ImmutableList.of("-source", "8", "-target", "11"))
+ .sourceVersion())
+ .isEqualTo(SourceVersion.RELEASE_8);
+ assertThat(
+ LanguageVersion.fromJavacopts(ImmutableList.of("-source", "8", "-source", "7"))
+ .sourceVersion())
+ .isEqualTo(SourceVersion.RELEASE_7);
+ }
+
+ @Test
+ public void withPrefix() {
+ assertThat(LanguageVersion.fromJavacopts(ImmutableList.of("-source", "1.7")).sourceVersion())
+ .isEqualTo(SourceVersion.RELEASE_7);
+ assertThat(LanguageVersion.fromJavacopts(ImmutableList.of("-source", "1.8")).sourceVersion())
+ .isEqualTo(SourceVersion.RELEASE_8);
+ }
+
+ @Test
+ public void invalidPrefix() {
+ assertThat(LanguageVersion.fromJavacopts(ImmutableList.of("-source", "1.10")).source())
+ .isEqualTo(10);
+ IllegalArgumentException expected =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LanguageVersion.fromJavacopts(ImmutableList.of("-source", "1.11")));
+ assertThat(expected).hasMessageThat().contains("invalid -source version: 1.11");
+ }
+
+ @Test
+ public void latestSupported() {
+ String latest = SourceVersion.latestSupported().toString();
+ assertThat(latest).startsWith("RELEASE_");
+ latest = latest.substring("RELEASE_".length());
+ assertThat(LanguageVersion.fromJavacopts(ImmutableList.of("-source", latest)).sourceVersion())
+ .isEqualTo(SourceVersion.latestSupported());
+ }
+
+ @Test
+ public void missingArgument() {
+ for (String flag :
+ ImmutableList.of("-source", "--source", "-target", "--target", "--release")) {
+ IllegalArgumentException expected =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LanguageVersion.fromJavacopts(ImmutableList.of(flag)));
+ assertThat(expected).hasMessageThat().contains(flag + " requires an argument");
+ }
+ }
+
+ @Test
+ public void invalidSourceVersion() {
+ IllegalArgumentException expected =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LanguageVersion.fromJavacopts(ImmutableList.of("-source", "NOSUCH")));
+ assertThat(expected).hasMessageThat().contains("invalid -source version: NOSUCH");
+ }
+
+ @Test
+ public void invalidRelease() {
+ IllegalArgumentException expected =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> LanguageVersion.fromJavacopts(ImmutableList.of("--release", "NOSUCH")));
+ assertThat(expected).hasMessageThat().contains("invalid --release version: NOSUCH");
+ }
+
+ @Test
+ public void parseRelease() {
+ assertThat(LanguageVersion.fromJavacopts(ImmutableList.of("--release", "16")).release())
+ .hasValue(16);
+ assertThat(
+ LanguageVersion.fromJavacopts(ImmutableList.of("-source", "8", "-target", "8"))
+ .release())
+ .isEmpty();
+ }
+
+ @Test
+ public void parseTarget() {
+ assertThat(
+ LanguageVersion.fromJavacopts(
+ ImmutableList.of("--release", "12", "-source", "8", "-target", "11"))
+ .target())
+ .isEqualTo(11);
+ assertThat(
+ LanguageVersion.fromJavacopts(
+ ImmutableList.of("-source", "8", "-target", "11", "--release", "12"))
+ .target())
+ .isEqualTo(12);
+ }
+
+ @Test
+ public void releaseUnderride() {
+ assertThat(
+ LanguageVersion.fromJavacopts(ImmutableList.of("--release", "12", "-source", "8"))
+ .release())
+ .isEmpty();
+ assertThat(
+ LanguageVersion.fromJavacopts(ImmutableList.of("--release", "12", "-target", "8"))
+ .release())
+ .isEmpty();
+ }
+
+ @Test
+ public void unsupportedSourceVersion() {
+ LanguageVersion languageVersion =
+ LanguageVersion.fromJavacopts(ImmutableList.of("-source", "9999"));
+ IllegalArgumentException expected =
+ assertThrows(IllegalArgumentException.class, languageVersion::sourceVersion);
+ assertThat(expected).hasMessageThat().contains("invalid -source version:");
+ }
+}
diff --git a/javatests/com/google/turbine/options/TurbineOptionsTest.java b/javatests/com/google/turbine/options/TurbineOptionsTest.java
index d4b468b..95eea59 100644
--- a/javatests/com/google/turbine/options/TurbineOptionsTest.java
+++ b/javatests/com/google/turbine/options/TurbineOptionsTest.java
@@ -18,6 +18,7 @@ package com.google.turbine.options;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList;
@@ -283,45 +284,23 @@ public class TurbineOptionsTest {
@Test
public void unknownOption() throws Exception {
- try {
- TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList("--nosuch")));
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessageThat().contains("unknown option");
- }
+ IllegalArgumentException e =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList("--nosuch"))));
+ assertThat(e).hasMessageThat().contains("unknown option");
}
@Test
public void unterminatedJavacopts() throws Exception {
- try {
- TurbineOptionsParser.parse(
- Iterables.concat(BASE_ARGS, Arrays.asList("--javacopts", "--release", "8")));
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessageThat().contains("javacopts should be terminated by `--`");
- }
- }
-
- @Test
- public void releaseJavacopts() throws Exception {
- TurbineOptions options =
- TurbineOptionsParser.parse(
- Iterables.concat(
- BASE_ARGS,
- Arrays.asList(
- "--release",
- "9",
- "--javacopts",
- "--release",
- "8",
- "--release",
- "7",
- "--release",
- "--")));
- assertThat(options.release()).hasValue("7");
- assertThat(options.javacOpts())
- .containsExactly("--release", "8", "--release", "7", "--release")
- .inOrder();
+ IllegalArgumentException e =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ TurbineOptionsParser.parse(
+ Iterables.concat(BASE_ARGS, Arrays.asList("--javacopts", "--release", "8"))));
+ assertThat(e).hasMessageThat().contains("javacopts should be terminated by `--`");
}
@Test
@@ -348,11 +327,9 @@ public class TurbineOptionsTest {
@Test
public void invalidUnescape() throws Exception {
String[] lines = {"--sources", "'Foo$Bar.java"};
- try {
- TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines)));
- fail();
- } catch (IllegalArgumentException expected) {
- }
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines))));
}
@Test
@@ -373,4 +350,37 @@ public class TurbineOptionsTest {
assertThat(options.reducedClasspathMode()).isEqualTo(mode);
}
}
+
+ @Test
+ public void javaBuilderCompatibility() throws Exception {
+ TurbineOptions options =
+ TurbineOptionsParser.parse(
+ Iterables.concat(
+ BASE_ARGS,
+ ImmutableList.of(
+ "--output_deps_proto",
+ "output_deps.proto",
+ "--generated_sources_output",
+ "generated_sources.jar",
+ "--experimental_fix_deps_tool",
+ "ignored",
+ "--strict_java_deps",
+ "ignored",
+ "--native_header_output",
+ "ignored",
+ "--compress_jar")));
+ assertThat(options.outputDeps()).hasValue("output_deps.proto");
+ assertThat(options.gensrcOutput()).hasValue("generated_sources.jar");
+ }
+
+ @Test
+ public void requiredValue() throws Exception {
+ IllegalArgumentException e =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ TurbineOptionsParser.parse(
+ Iterables.concat(BASE_ARGS, ImmutableList.of("--output", "--system"))));
+ assertThat(e).hasMessageThat().contains("missing required argument for: --output");
+ }
}
diff --git a/javatests/com/google/turbine/parse/ExpressionParserTest.java b/javatests/com/google/turbine/parse/ExpressionParserTest.java
index 9fa96e2..7b5889b 100644
--- a/javatests/com/google/turbine/parse/ExpressionParserTest.java
+++ b/javatests/com/google/turbine/parse/ExpressionParserTest.java
@@ -39,7 +39,7 @@ public class ExpressionParserTest {
"14 + 42", "(14 + 42)",
},
{
- "14 + 42 + 123", "((14 + 42) + 123)",
+ "14 + 42 + 123", "(14 + 42 + 123)",
},
{
"14 / 42 + 123", "((14 / 42) + 123)",
@@ -49,7 +49,7 @@ public class ExpressionParserTest {
},
{
"1 + 2 / 3 + 4 / 5 + 6 / 7 / 8 + 9 + 10",
- "(((((1 + (2 / 3)) + (4 / 5)) + ((6 / 7) / 8)) + 9) + 10)",
+ "(1 + (2 / 3) + (4 / 5) + (6 / 7 / 8) + 9 + 10)",
},
{
"1 >> 2 || 3 ^ 4 << 3", "((1 >> 2) || (3 ^ (4 << 3)))",
@@ -64,7 +64,7 @@ public class ExpressionParserTest {
"((Object) 1 + 2)", "((Object) 1 + 2)",
},
{
- "(1) + 1 + 2", "((1 + 1) + 2)",
+ "(1) + 1 + 2", "(1 + 1 + 2)",
},
{
"((1 + 2) / (1 + 2))", "((1 + 2) / (1 + 2))",
@@ -82,7 +82,7 @@ public class ExpressionParserTest {
"(int) +2", "(int) +2",
},
{
- "(1 + 2) +2", "((1 + 2) + 2)",
+ "(1 + 2) + 2", "((1 + 2) + 2)",
},
{
"((1 + (2 / 3)) + (4 / 5))", "((1 + (2 / 3)) + (4 / 5))",
@@ -121,7 +121,7 @@ public class ExpressionParserTest {
"x.y = z", null,
},
{
- "0b100L + 0100L + 0x100L", "((4L + 64L) + 256L)",
+ "0b100L + 0100L + 0x100L", "(4L + 64L + 256L)",
},
{
"1+-2", "(1 + -2)",
@@ -132,6 +132,9 @@ public class ExpressionParserTest {
{
"A ? B : C ? D : E;", "(A ? B : (C ? D : E))",
},
+ {
+ "Foo.class", "Foo.class",
+ },
});
}
@@ -146,7 +149,8 @@ public class ExpressionParserTest {
@Test
public void test() {
StreamLexer lexer = new StreamLexer(new UnicodeEscapePreprocessor(new SourceFile(null, input)));
- Tree.Expression expression = new ConstExpressionParser(lexer, lexer.next()).expression();
+ Tree.Expression expression =
+ new ConstExpressionParser(lexer, lexer.next(), lexer.position()).expression();
if (expected == null) {
assertThat(expression).isNull();
} else {
diff --git a/javatests/com/google/turbine/parse/JavacLexer.java b/javatests/com/google/turbine/parse/JavacLexer.java
index d8939f1..6e1a984 100644
--- a/javatests/com/google/turbine/parse/JavacLexer.java
+++ b/javatests/com/google/turbine/parse/JavacLexer.java
@@ -27,7 +27,7 @@ import java.util.ArrayList;
import java.util.List;
/** A javac-based reference lexer. */
-public class JavacLexer {
+public final class JavacLexer {
static List<String> javacLex(final String input) {
Context context = new Context();
@@ -283,4 +283,6 @@ public class JavacLexer {
}
return token.kind.toString();
}
+
+ private JavacLexer() {}
}
diff --git a/javatests/com/google/turbine/parse/LexerTest.java b/javatests/com/google/turbine/parse/LexerTest.java
index 8530d52..c3d7804 100644
--- a/javatests/com/google/turbine/parse/LexerTest.java
+++ b/javatests/com/google/turbine/parse/LexerTest.java
@@ -328,6 +328,11 @@ public class LexerTest {
lexerComparisonTest("foo /*/*/ bar");
}
+ @Test
+ public void unicode() {
+ lexerComparisonTest("import pkg\uD800\uDC00.test;");
+ }
+
private void lexerComparisonTest(String s) {
assertThat(lex(s)).containsExactlyElementsIn(JavacLexer.javacLex(s));
}
diff --git a/javatests/com/google/turbine/parse/ParseErrorTest.java b/javatests/com/google/turbine/parse/ParseErrorTest.java
index 6a9ad11..2c48b81 100644
--- a/javatests/com/google/turbine/parse/ParseErrorTest.java
+++ b/javatests/com/google/turbine/parse/ParseErrorTest.java
@@ -17,7 +17,7 @@
package com.google.turbine.parse;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
import com.google.common.base.Joiner;
import com.google.turbine.diag.SourceFile;
@@ -35,13 +35,9 @@ public class ParseErrorTest {
StreamLexer lexer =
new StreamLexer(
new UnicodeEscapePreprocessor(new SourceFile("<>", String.valueOf("2147483648"))));
- ConstExpressionParser parser = new ConstExpressionParser(lexer, lexer.next());
- try {
- parser.expression();
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e).hasMessageThat().contains("invalid literal");
- }
+ ConstExpressionParser parser = new ConstExpressionParser(lexer, lexer.next(), lexer.position());
+ TurbineError e = assertThrows(TurbineError.class, () -> parser.expression());
+ assertThat(e).hasMessageThat().contains("invalid literal");
}
@Test
@@ -49,234 +45,266 @@ public class ParseErrorTest {
StreamLexer lexer =
new StreamLexer(
new UnicodeEscapePreprocessor(new SourceFile("<>", String.valueOf("0x100000000"))));
- ConstExpressionParser parser = new ConstExpressionParser(lexer, lexer.next());
- try {
- parser.expression();
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e).hasMessageThat().contains("invalid literal");
- }
+ ConstExpressionParser parser = new ConstExpressionParser(lexer, lexer.next(), lexer.position());
+ TurbineError e = assertThrows(TurbineError.class, () -> parser.expression());
+ assertThat(e).hasMessageThat().contains("invalid literal");
}
@Test
public void unexpectedTopLevel() {
String input = "public static void main(String[] args) {}";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:1: error: unexpected token: void",
- "public static void main(String[] args) {}",
- " ^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unexpected token: void",
+ "public static void main(String[] args) {}",
+ " ^"));
}
@Test
public void unexpectedIdentifier() {
String input = "public clas Test {}";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:1: error: unexpected identifier 'clas'", //
- "public clas Test {}",
- " ^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unexpected identifier 'clas'", //
+ "public clas Test {}",
+ " ^"));
}
@Test
public void missingTrailingCloseBrace() {
String input = "public class Test {\n\n";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:2: error: unexpected end of input", //
- "",
- "^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:2: error: unexpected end of input", //
+ "",
+ "^"));
}
@Test
public void annotationArgument() {
String input = "@A(x = System.err.println()) class Test {}\n";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:1: error: invalid annotation argument", //
- "@A(x = System.err.println()) class Test {}",
- " ^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: invalid annotation argument", //
+ "@A(x = System.err.println()) class Test {}",
+ " ^"));
}
@Test
public void dropParens() {
String input = "enum E { ONE(";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:1: error: unexpected end of input", //
- "enum E { ONE(",
- " ^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unexpected end of input", //
+ "enum E { ONE(",
+ " ^"));
}
@Test
public void dropBlocks() {
String input = "class T { Object f = new Object() {";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:1: error: unexpected end of input", //
- "class T { Object f = new Object() {",
- " ^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unexpected end of input", //
+ "class T { Object f = new Object() {",
+ " ^"));
}
@Test
public void unterminatedString() {
String input = "class T { String s = \"hello\nworld\"; }";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:1: error: unterminated string literal", //
- "class T { String s = \"hello",
- " ^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unterminated string literal", //
+ "class T { String s = \"hello",
+ " ^"));
}
@Test
public void emptyChar() {
String input = "class T { char c = ''; }";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:1: error: empty char literal", //
- "class T { char c = ''; }",
- " ^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: empty char literal", //
+ "class T { char c = ''; }",
+ " ^"));
}
@Test
public void unterminatedChar() {
String input = "class T { char c = '; }";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:1: error: unterminated char literal", //
- "class T { char c = '; }",
- " ^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unterminated char literal", //
+ "class T { char c = '; }",
+ " ^"));
}
@Test
public void unterminatedExpr() {
String input = "class T { String s = hello + world }";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:1: error: unterminated expression, expected ';' not found", //
- "class T { String s = hello + world }",
- " ^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unterminated expression, expected ';' not found", //
+ "class T { String s = hello + world }",
+ " ^"));
}
@Test
public void abruptMultivariableDeclaration() {
String input = "class T { int x,; }";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:1: error: expected token <identifier>", //
- "class T { int x,; }",
- " ^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: expected token <identifier>", //
+ "class T { int x,; }",
+ " ^"));
}
@Test
public void invalidAnnotation() {
String input = "@Foo(x = @E [] x) class T {}";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:1: error: invalid annotation argument", //
- "@Foo(x = @E [] x) class T {}",
- " ^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: invalid annotation argument", //
+ "@Foo(x = @E [] x) class T {}",
+ " ^"));
}
@Test
public void unclosedComment() {
String input = "/** *\u001a/ class Test {}";
- try {
- Parser.parse(input);
- fail("expected parsing to fail");
- } catch (TurbineError e) {
- assertThat(e)
- .hasMessageThat()
- .isEqualTo(
- lines(
- "<>:1: error: unclosed comment", //
- "/** *\u001a/ class Test {}",
- "^"));
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unclosed comment", //
+ "/** *\u001a/ class Test {}",
+ "^"));
+ }
+
+ @Test
+ public void unclosedGenerics() {
+ String input = "enum\te{l;p u@.<@";
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unexpected end of input", //
+ "enum\te{l;p u@.<@",
+ " ^"));
+ }
+
+ @Test
+ public void arrayDot() {
+ String input = "enum\te{p;ullt[].<~>>>L\0";
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unexpected token: <", //
+ "enum\te{p;ullt[].<~>>>L\0",
+ " ^"));
+ }
+
+ @Test
+ public void implementsBeforeExtends() {
+ String input = "class T implements A extends B {}";
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: 'extends' must come before 'implements'",
+ "class T implements A extends B {}",
+ " ^"));
+ }
+
+ @Test
+ public void unpairedSurrogate() {
+ String input = "import pkg\uD800.PackageTest;";
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unpaired surrogate 0xd800",
+ "import pkg\uD800.PackageTest;",
+ " ^"));
+ }
+
+ @Test
+ public void abruptSurrogate() {
+ String input = "import pkg\uD800";
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines("<>:1: error: unpaired surrogate 0xd800", "import pkg\uD800", " ^"));
+ }
+
+ @Test
+ public void unexpectedSurrogate() {
+ String input = "..\uD800\uDC00";
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: unexpected input: U+10000", //
+ "..\uD800\uDC00",
+ " ^"));
+ }
+
+ @Test
+ public void notCast() {
+ String input = "@j(@truetugt^(oflur)!%t";
+ TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input));
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo(
+ lines(
+ "<>:1: error: could not evaluate constant expression",
+ "@j(@truetugt^(oflur)!%t",
+ " ^"));
}
private static String lines(String... lines) {
diff --git a/javatests/com/google/turbine/parse/ParserIntegrationTest.java b/javatests/com/google/turbine/parse/ParserIntegrationTest.java
index 2503553..c758a74 100644
--- a/javatests/com/google/turbine/parse/ParserIntegrationTest.java
+++ b/javatests/com/google/turbine/parse/ParserIntegrationTest.java
@@ -42,6 +42,7 @@ public class ParserIntegrationTest {
public static Iterable<Object[]> parameters() {
String[] tests = {
"anno1.input",
+ "anno2.input",
"annodecl1.input",
"annodecl2.input",
"annodecl3.input",
@@ -75,6 +76,8 @@ public class ParserIntegrationTest {
"weirdstring.input",
"type_annotations.input",
"module-info.input",
+ "record.input",
+ "sealed.input",
};
return Iterables.transform(
Arrays.asList(tests),
diff --git a/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java b/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java
index e3f7b63..b3e09b8 100644
--- a/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java
+++ b/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java
@@ -18,7 +18,7 @@ package com.google.turbine.parse;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
import com.google.turbine.diag.SourceFile;
import com.google.turbine.diag.TurbineError;
@@ -57,19 +57,11 @@ public class UnicodeEscapePreprocessorTest {
@Test
public void abruptEnd() {
- try {
- readAll("\\u00");
- fail();
- } catch (TurbineError e) {
- assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.UNEXPECTED_EOF);
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> readAll("\\u00"));
+ assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.UNEXPECTED_EOF);
- try {
- readAll("\\u");
- fail();
- } catch (TurbineError e) {
- assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.UNEXPECTED_EOF);
- }
+ e = assertThrows(TurbineError.class, () -> readAll("\\u"));
+ assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.UNEXPECTED_EOF);
}
@Test
@@ -79,19 +71,16 @@ public class UnicodeEscapePreprocessorTest {
@Test
public void invalidEscape() {
- try {
- readAll("\\uUUUU");
- fail();
- } catch (TurbineError e) {
- assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.INVALID_UNICODE);
- }
+ TurbineError e = assertThrows(TurbineError.class, () -> readAll("\\uUUUU"));
+ assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.INVALID_UNICODE);
}
private List<Character> readAll(String input) {
UnicodeEscapePreprocessor reader = new UnicodeEscapePreprocessor(new SourceFile(null, input));
List<Character> result = new ArrayList<>();
- for (char ch = reader.next(); ch != UnicodeEscapePreprocessor.ASCII_SUB; ch = reader.next()) {
- result.add(ch);
+ for (int ch = reader.next(); ch != UnicodeEscapePreprocessor.ASCII_SUB; ch = reader.next()) {
+ assertThat(Character.isBmpCodePoint(ch)).isTrue();
+ result.add((char) ch);
}
return result;
}
diff --git a/javatests/com/google/turbine/parse/testdata/anno2.input b/javatests/com/google/turbine/parse/testdata/anno2.input
new file mode 100644
index 0000000..60b1901
--- /dev/null
+++ b/javatests/com/google/turbine/parse/testdata/anno2.input
@@ -0,0 +1,3 @@
+@Foo(bar = FooBar.Bar[].class)
+class Test {
+} \ No newline at end of file
diff --git a/javatests/com/google/turbine/parse/testdata/record.input b/javatests/com/google/turbine/parse/testdata/record.input
new file mode 100644
index 0000000..575d741
--- /dev/null
+++ b/javatests/com/google/turbine/parse/testdata/record.input
@@ -0,0 +1,53 @@
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.util.List;
+
+private record R() {
+}
+
+class Records {
+ record R1() {
+ }
+
+ private record R2() {
+ }
+
+ @Deprecated
+ private record R3() {
+ }
+
+ record R4<T>() {
+ }
+
+ record R5<T>(int x) {
+ }
+
+ record R6<T>(@Deprecated int x) {
+ }
+
+ record R7<T>(@Deprecated int x, int[] y) {
+ }
+
+ record R8<T>() implements Comparable<R8<T>> {
+ @Override
+ public int compareTo(R8<T> other) {}
+ }
+
+ record R9(int x) {
+ }
+
+ @Target(ElementType.TYPE_USE)
+ @interface A {
+ }
+
+ @Target(ElementType.RECORD_COMPONENT)
+ @interface B {
+ }
+
+ @Target({ElementType.TYPE_USE, ElementType.RECORD_COMPONENT})
+ @interface C {
+ }
+
+ record R10<T>(@A List<@A T> x, @B int y, @C int z) {
+ }
+}
diff --git a/javatests/com/google/turbine/parse/testdata/sealed.input b/javatests/com/google/turbine/parse/testdata/sealed.input
new file mode 100644
index 0000000..5a277b8
--- /dev/null
+++ b/javatests/com/google/turbine/parse/testdata/sealed.input
@@ -0,0 +1,15 @@
+sealed class Sealed permits Sealed.Foo, Sealed.Bar {
+ static final class Foo extends Sealed {
+ }
+
+ static final class Bar extends Sealed {
+ }
+}
+
+sealed interface ISealed permits ISealed.Foo, ISealed.Bar {
+ static final class Foo implements ISealed {
+ }
+
+ static non-sealed class Bar implements ISealed {
+ }
+}
diff --git a/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java b/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java
index e6a59bf..7d8d479 100644
--- a/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java
+++ b/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java
@@ -21,6 +21,7 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import com.google.common.base.Joiner;
@@ -231,6 +232,14 @@ class AbstractTurbineTypesTest {
"Float",
"Double",
},
+ // type annotations
+ {
+ "@A List<@B Integer>",
+ "@A List",
+ "@A int @B []",
+ "@A List<@A int @B []>",
+ "Map.@A Entry<@B Integer, @C Number>",
+ },
};
List<String> files = new ArrayList<>();
AtomicInteger idx = new AtomicInteger();
@@ -242,6 +251,7 @@ class AbstractTurbineTypesTest {
"package p;",
"import java.util.*;",
"import java.io.*;",
+ "import java.lang.annotation.*;",
String.format("abstract class Test%s {", idx.getAndIncrement()),
Streams.mapWithIndex(
Arrays.stream(group), (x, i) -> String.format(" %s f%d;\n", x, i))
@@ -250,6 +260,9 @@ class AbstractTurbineTypesTest {
" abstract <V extends List<V>> V g();",
" abstract <W extends ArrayList> W h();",
" abstract <X extends Serializable> X i();",
+ " @Target(ElementType.TYPE_USE) @interface A {}",
+ " @Target(ElementType.TYPE_USE) @interface B {}",
+ " @Target(ElementType.TYPE_USE) @interface C {}",
"}");
String content = sb.toString();
files.add(content);
@@ -332,6 +345,21 @@ class AbstractTurbineTypesTest {
" void h(T t) {}",
"}"));
+ // type variable bounds
+ files.add(
+ Joiner.on('\n')
+ .join(
+ "import java.util.List;",
+ "class N<X, T extends X> {",
+ " void h(T t) {}",
+ "}",
+ "class O<X extends Enum<X>, T extends X> {",
+ " void h(T t) {}",
+ "}",
+ "class P<X extends List<?>, T extends X> {",
+ " void h(T t) {}",
+ "}"));
+
Context context = new Context();
JavaFileManager fileManager = new JavacFileManager(context, true, UTF_8);
idx.set(0);
@@ -397,8 +425,11 @@ class AbstractTurbineTypesTest {
ListMultimap<String, TypeMirror> turbineInputs =
MultimapBuilder.linkedHashKeys().arrayListValues().build();
- turbineElements
- .get(name)
+ /*
+ * requireNonNull is safe because `name` is from `javacElements`, which we checked has the
+ * same keys as `turbineElements`.
+ */
+ requireNonNull(turbineElements.get(name))
.getEnclosedElements()
.forEach(e -> getTypes(turbineTypes, e, turbineInputs));
diff --git a/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java
index ed5af6a..fee2c75 100644
--- a/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java
+++ b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java
@@ -19,9 +19,14 @@ package com.google.turbine.processing;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.MoreCollectors.onlyElement;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
-import static org.junit.Assert.fail;
+import static javax.lang.model.util.ElementFilter.methodsIn;
+import static javax.lang.model.util.ElementFilter.typesIn;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
@@ -33,6 +38,7 @@ import com.google.turbine.binder.ClassPathBinder;
import com.google.turbine.binder.Processing;
import com.google.turbine.binder.Processing.ProcessorInfo;
import com.google.turbine.diag.SourceFile;
+import com.google.turbine.diag.TurbineDiagnostic;
import com.google.turbine.diag.TurbineError;
import com.google.turbine.lower.IntegrationTestSupport;
import com.google.turbine.parse.Parser;
@@ -45,10 +51,15 @@ import java.io.Writer;
import java.util.Optional;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
+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;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
@@ -73,39 +84,33 @@ public class ProcessingIntegrationTest {
}
}
- private static final IntegrationTestSupport.TestInput SOURCES =
- IntegrationTestSupport.TestInput.parse(
- Joiner.on('\n')
- .join(
- "=== Test.java ===", //
- "@Deprecated",
- "class Test extends NoSuch {",
- "}"));
-
@Test
public void crash() throws IOException {
ImmutableList<Tree.CompUnit> units =
- SOURCES.sources.entrySet().stream()
- .map(e -> new SourceFile(e.getKey(), e.getValue()))
- .map(Parser::parse)
- .collect(toImmutableList());
- try {
- Binder.bind(
- units,
- ClassPathBinder.bindClasspath(ImmutableList.of()),
- Processing.ProcessorInfo.create(
- ImmutableList.of(new CrashingProcessor()),
- getClass().getClassLoader(),
- ImmutableMap.of(),
- SourceVersion.latestSupported()),
- TestClassPaths.TURBINE_BOOTCLASSPATH,
- Optional.empty());
- fail();
- } catch (TurbineError e) {
- assertThat(e.diagnostics()).hasSize(2);
- assertThat(e.diagnostics().get(0).message()).contains("could not resolve NoSuch");
- assertThat(e.diagnostics().get(1).message()).contains("crash!");
- }
+ parseUnit(
+ "=== Test.java ===", //
+ "@Deprecated",
+ "class Test extends NoSuch {",
+ "}");
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ Processing.ProcessorInfo.create(
+ ImmutableList.of(new CrashingProcessor()),
+ getClass().getClassLoader(),
+ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty()));
+ ImmutableList<String> messages =
+ e.diagnostics().stream().map(TurbineDiagnostic::message).collect(toImmutableList());
+ assertThat(messages).hasSize(2);
+ assertThat(messages.get(0)).contains("could not resolve NoSuch");
+ assertThat(messages.get(1)).contains("crash!");
}
@SupportedAnnotationTypes("*")
@@ -142,38 +147,30 @@ public class ProcessingIntegrationTest {
@Test
public void warnings() throws IOException {
ImmutableList<Tree.CompUnit> units =
- IntegrationTestSupport.TestInput.parse(
- Joiner.on('\n')
- .join(
- "=== Test.java ===", //
- "@Deprecated",
- "class Test {",
- "}"))
- .sources
- .entrySet()
- .stream()
- .map(e -> new SourceFile(e.getKey(), e.getValue()))
- .map(Parser::parse)
- .collect(toImmutableList());
- try {
- Binder.bind(
- units,
- ClassPathBinder.bindClasspath(ImmutableList.of()),
- Processing.ProcessorInfo.create(
- ImmutableList.of(new WarningProcessor()),
- getClass().getClassLoader(),
- ImmutableMap.of(),
- SourceVersion.latestSupported()),
- TestClassPaths.TURBINE_BOOTCLASSPATH,
- Optional.empty());
- fail();
- } catch (TurbineError e) {
- ImmutableList<String> diags =
- e.diagnostics().stream().map(d -> d.message()).collect(toImmutableList());
- assertThat(diags).hasSize(2);
- assertThat(diags.get(0)).contains("proc warning");
- assertThat(diags.get(1)).contains("proc error");
- }
+ parseUnit(
+ "=== Test.java ===", //
+ "@Deprecated",
+ "class Test {",
+ "}");
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ Processing.ProcessorInfo.create(
+ ImmutableList.of(new WarningProcessor()),
+ getClass().getClassLoader(),
+ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty()));
+ ImmutableList<String> diags =
+ e.diagnostics().stream().map(d -> d.message()).collect(toImmutableList());
+ assertThat(diags).hasSize(2);
+ assertThat(diags.get(0)).contains("proc warning");
+ assertThat(diags.get(1)).contains("proc error");
}
@SupportedAnnotationTypes("*")
@@ -219,19 +216,11 @@ public class ProcessingIntegrationTest {
@Test
public void resources() throws IOException {
ImmutableList<Tree.CompUnit> units =
- IntegrationTestSupport.TestInput.parse(
- Joiner.on('\n')
- .join(
- "=== Test.java ===", //
- "@Deprecated",
- "class Test {",
- "}"))
- .sources
- .entrySet()
- .stream()
- .map(e -> new SourceFile(e.getKey(), e.getValue()))
- .map(Parser::parse)
- .collect(toImmutableList());
+ parseUnit(
+ "=== Test.java ===", //
+ "@Deprecated",
+ "class Test {",
+ "}");
BindingResult bound =
Binder.bind(
units,
@@ -247,34 +236,27 @@ public class ProcessingIntegrationTest {
assertThat(bound.generatedSources().keySet()).containsExactly("Gen.java", "source.txt");
assertThat(bound.generatedClasses().keySet()).containsExactly("class.txt");
- assertThat(bound.generatedSources().get("source.txt").source())
+ // The requireNonNull calls are safe because of the keySet checks above.
+ assertThat(requireNonNull(bound.generatedSources().get("source.txt")).source())
.isEqualTo("hello source output");
- assertThat(new String(bound.generatedClasses().get("class.txt"), UTF_8))
+ assertThat(new String(requireNonNull(bound.generatedClasses().get("class.txt")), UTF_8))
.isEqualTo("hello class output");
}
@Test
public void getAllAnnotations() throws IOException {
ImmutableList<Tree.CompUnit> units =
- IntegrationTestSupport.TestInput.parse(
- Joiner.on('\n')
- .join(
- "=== A.java ===", //
- "import java.lang.annotation.Inherited;",
- "@Inherited",
- "@interface A {}",
- "=== B.java ===", //
- "@interface B {}",
- "=== One.java ===", //
- "@A @B class One {}",
- "=== Two.java ===", //
- "class Two extends One {}"))
- .sources
- .entrySet()
- .stream()
- .map(e -> new SourceFile(e.getKey(), e.getValue()))
- .map(Parser::parse)
- .collect(toImmutableList());
+ parseUnit(
+ "=== A.java ===", //
+ "import java.lang.annotation.Inherited;",
+ "@Inherited",
+ "@interface A {}",
+ "=== B.java ===", //
+ "@interface B {}",
+ "=== One.java ===", //
+ "@A @B class One {}",
+ "=== Two.java ===", //
+ "class Two extends One {}");
BindingResult bound =
Binder.bind(
units,
@@ -343,4 +325,483 @@ public class ProcessingIntegrationTest {
.collect(joining(", ")));
}
}
+
+ private static void logError(
+ ProcessingEnvironment processingEnv,
+ RoundEnvironment roundEnv,
+ Class<?> processorClass,
+ int round) {
+ processingEnv
+ .getMessager()
+ .printMessage(
+ Diagnostic.Kind.ERROR,
+ String.format(
+ "%d: %s {errorRaised=%s, processingOver=%s}",
+ round,
+ processorClass.getSimpleName(),
+ roundEnv.errorRaised(),
+ roundEnv.processingOver()));
+ }
+
+ @SupportedAnnotationTypes("*")
+ public static class ErrorProcessor extends AbstractProcessor {
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ int round = 0;
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ int round = ++this.round;
+ logError(processingEnv, roundEnv, getClass(), round);
+ String name = "Gen" + round;
+ try (Writer writer = processingEnv.getFiler().createSourceFile(name).openWriter()) {
+ writer.write(String.format("class %s {}", name));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ return false;
+ }
+ }
+
+ @SupportedAnnotationTypes("*")
+ public static class FinalRoundErrorProcessor extends AbstractProcessor {
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ int round = 0;
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ int round = ++this.round;
+ if (roundEnv.processingOver()) {
+ logError(processingEnv, roundEnv, getClass(), round);
+ }
+ return false;
+ }
+ }
+
+ @Test
+ public void errorsAndFinalRound() throws IOException {
+ ImmutableList<Tree.CompUnit> units =
+ parseUnit(
+ "=== Test.java ===", //
+ "@Deprecated",
+ "class Test {",
+ "}");
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ Processing.ProcessorInfo.create(
+ ImmutableList.of(new ErrorProcessor(), new FinalRoundErrorProcessor()),
+ getClass().getClassLoader(),
+ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty()));
+ ImmutableList<String> diags =
+ e.diagnostics().stream().map(d -> d.message()).collect(toImmutableList());
+ assertThat(diags)
+ .containsExactly(
+ "1: ErrorProcessor {errorRaised=false, processingOver=false}",
+ "2: ErrorProcessor {errorRaised=true, processingOver=true}",
+ "2: FinalRoundErrorProcessor {errorRaised=true, processingOver=true}")
+ .inOrder();
+ }
+
+ @SupportedAnnotationTypes("*")
+ public static class SuperTypeProcessor extends AbstractProcessor {
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ TypeElement typeElement = processingEnv.getElementUtils().getTypeElement("T");
+ processingEnv
+ .getMessager()
+ .printMessage(
+ Diagnostic.Kind.ERROR,
+ typeElement.getSuperclass()
+ + " "
+ + processingEnv.getTypeUtils().directSupertypes(typeElement.asType()));
+ return false;
+ }
+ }
+
+ @Test
+ public void superType() throws IOException {
+ ImmutableList<Tree.CompUnit> units =
+ parseUnit(
+ "=== T.java ===", //
+ "@Deprecated",
+ "class T extends S {",
+ "}");
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ Processing.ProcessorInfo.create(
+ ImmutableList.of(new SuperTypeProcessor()),
+ getClass().getClassLoader(),
+ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty()));
+ ImmutableList<String> diags =
+ e.diagnostics().stream().map(d -> d.message()).collect(toImmutableList());
+ assertThat(diags).containsExactly("could not resolve S", "S [S]").inOrder();
+ }
+
+ @SupportedAnnotationTypes("*")
+ public static class GenerateAnnotationProcessor extends AbstractProcessor {
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ private boolean first = true;
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (first) {
+ try {
+ JavaFileObject file = processingEnv.getFiler().createSourceFile("A");
+ try (Writer writer = file.openWriter()) {
+ writer.write("@interface A {}");
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ first = false;
+ }
+ return false;
+ }
+ }
+
+ @Test
+ public void generatedAnnotationDefinition() throws IOException {
+ ImmutableList<Tree.CompUnit> units =
+ parseUnit(
+ "=== T.java ===", //
+ "@interface B {",
+ " A value() default @A;",
+ "}",
+ "@B(value = @A)",
+ "class T {",
+ "}");
+ BindingResult bound =
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ ProcessorInfo.create(
+ ImmutableList.of(new GenerateAnnotationProcessor()),
+ getClass().getClassLoader(),
+ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty());
+ assertThat(bound.generatedSources()).containsKey("A.java");
+ }
+
+ @SupportedAnnotationTypes("*")
+ public static class GenerateQualifiedProcessor extends AbstractProcessor {
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ String superType =
+ processingEnv.getElementUtils().getTypeElement("T").getSuperclass().toString();
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, superType);
+ return false;
+ }
+ }
+
+ @Test
+ public void qualifiedErrorType() throws IOException {
+ ImmutableList<Tree.CompUnit> units =
+ parseUnit(
+ "=== T.java ===", //
+ "class T extends G.I {",
+ "}");
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ ProcessorInfo.create(
+ ImmutableList.of(new GenerateQualifiedProcessor()),
+ getClass().getClassLoader(),
+ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty()));
+ assertThat(
+ e.diagnostics().stream()
+ .filter(d -> d.severity().equals(Diagnostic.Kind.NOTE))
+ .map(d -> d.message()))
+ .containsExactly("G.I");
+ }
+
+ @SupportedAnnotationTypes("*")
+ public static class ElementValueInspector extends AbstractProcessor {
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ TypeElement element = processingEnv.getElementUtils().getTypeElement("T");
+ for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
+ processingEnv
+ .getMessager()
+ .printMessage(
+ Diagnostic.Kind.NOTE,
+ String.format("@Deprecated(%s)", annotationMirror.getElementValues()),
+ element,
+ annotationMirror);
+ }
+ return false;
+ }
+ }
+
+ @Test
+ public void badElementValue() throws IOException {
+ ImmutableList<Tree.CompUnit> units =
+ parseUnit(
+ "=== T.java ===", //
+ "@Deprecated(noSuch = 42) class T {}");
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ ProcessorInfo.create(
+ ImmutableList.of(new ElementValueInspector()),
+ getClass().getClassLoader(),
+ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty()));
+ assertThat(
+ e.diagnostics().stream()
+ .filter(d -> d.severity().equals(Diagnostic.Kind.ERROR))
+ .map(d -> d.message()))
+ .containsExactly("could not resolve element noSuch() in java.lang.Deprecated");
+ assertThat(
+ e.diagnostics().stream()
+ .filter(d -> d.severity().equals(Diagnostic.Kind.NOTE))
+ .map(d -> d.message()))
+ .containsExactly("@Deprecated({})");
+ }
+
+ @SupportedAnnotationTypes("*")
+ public static class RecordProcessor extends AbstractProcessor {
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ for (Element e : roundEnv.getRootElements()) {
+ processingEnv
+ .getMessager()
+ .printMessage(
+ Diagnostic.Kind.ERROR,
+ e.getKind() + " " + e + " " + ((TypeElement) e).getSuperclass());
+ for (Element m : e.getEnclosedElements()) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, m.getKind() + " " + m);
+ }
+ }
+ return false;
+ }
+ }
+
+ @Test
+ public void recordProcessing() throws IOException {
+ assumeTrue(Runtime.version().feature() >= 15);
+ ImmutableList<Tree.CompUnit> units =
+ parseUnit(
+ "=== R.java ===", //
+ "record R<T>(@Deprecated T x, int... y) {}");
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ ProcessorInfo.create(
+ ImmutableList.of(new RecordProcessor()),
+ getClass().getClassLoader(),
+ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty()));
+ assertThat(
+ e.diagnostics().stream()
+ .filter(d -> d.severity().equals(Diagnostic.Kind.ERROR))
+ .map(d -> d.message()))
+ .containsExactly(
+ "RECORD R java.lang.Record",
+ "RECORD_COMPONENT x",
+ "RECORD_COMPONENT y",
+ "CONSTRUCTOR R(T,int[])",
+ "METHOD toString()",
+ "METHOD hashCode()",
+ "METHOD equals(java.lang.Object)",
+ "METHOD x()",
+ "METHOD y()");
+ }
+
+ @Test
+ public void missingElementValue() {
+ ImmutableList<Tree.CompUnit> units =
+ parseUnit(
+ "=== T.java ===", //
+ "import java.lang.annotation.Retention;",
+ "@Retention() @interface T {}");
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ ProcessorInfo.create(
+ // missing annotation arguments are not a recoverable error, annotation
+ // processing shouldn't happen
+ ImmutableList.of(new CrashingProcessor()),
+ getClass().getClassLoader(),
+ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty()));
+ assertThat(e.diagnostics().stream().map(d -> d.message()))
+ .containsExactly("missing required annotation argument: value");
+ }
+
+ private static ImmutableList<Tree.CompUnit> parseUnit(String... lines) {
+ return IntegrationTestSupport.TestInput.parse(Joiner.on('\n').join(lines))
+ .sources
+ .entrySet()
+ .stream()
+ .map(e -> new SourceFile(e.getKey(), e.getValue()))
+ .map(Parser::parse)
+ .collect(toImmutableList());
+ }
+
+ @SupportedAnnotationTypes("*")
+ public static class AllMethodsProcessor extends AbstractProcessor {
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+
+ ImmutableList<ExecutableElement> methods =
+ typesIn(roundEnv.getRootElements()).stream()
+ .flatMap(t -> methodsIn(t.getEnclosedElements()).stream())
+ .collect(toImmutableList());
+ for (ExecutableElement a : methods) {
+ for (ExecutableElement b : methods) {
+ if (a.equals(b)) {
+ continue;
+ }
+ ExecutableType ta = (ExecutableType) a.asType();
+ ExecutableType tb = (ExecutableType) b.asType();
+ boolean r = processingEnv.getTypeUtils().isSubsignature(ta, tb);
+ processingEnv
+ .getMessager()
+ .printMessage(
+ Diagnostic.Kind.ERROR,
+ String.format(
+ "%s#%s%s <: %s#%s%s ? %s",
+ a.getEnclosingElement(),
+ a.getSimpleName(),
+ ta,
+ b.getEnclosingElement(),
+ b.getSimpleName(),
+ tb,
+ r));
+ }
+ }
+ return false;
+ }
+ }
+
+ @Test
+ public void bound() {
+ ImmutableList<Tree.CompUnit> units =
+ parseUnit(
+ "=== A.java ===", //
+ "import java.util.List;",
+ "class A<T> {",
+ " <U extends T> U f(List<U> list) {",
+ " return list.get(0);",
+ " }",
+ "}",
+ "class B extends A<String> {",
+ " @Override",
+ " <U extends String> U f(List<U> list) {",
+ " return super.f(list);",
+ " }",
+ "}",
+ "class C extends A<Object> {",
+ " @Override",
+ " <U> U f(List<U> list) {",
+ " return super.f(list);",
+ " }",
+ "}");
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ ProcessorInfo.create(
+ ImmutableList.of(new AllMethodsProcessor()),
+ getClass().getClassLoader(),
+ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty()));
+ assertThat(e.diagnostics().stream().map(d -> d.message()))
+ .containsExactly(
+ "A#f<U>(java.util.List<U>)U <: B#f<U>(java.util.List<U>)U ? false",
+ "A#f<U>(java.util.List<U>)U <: C#f<U>(java.util.List<U>)U ? false",
+ "B#f<U>(java.util.List<U>)U <: A#f<U>(java.util.List<U>)U ? false",
+ "B#f<U>(java.util.List<U>)U <: C#f<U>(java.util.List<U>)U ? false",
+ "C#f<U>(java.util.List<U>)U <: A#f<U>(java.util.List<U>)U ? false",
+ "C#f<U>(java.util.List<U>)U <: B#f<U>(java.util.List<U>)U ? false");
+ }
}
diff --git a/javatests/com/google/turbine/processing/TurbineAnnotationMirrorTest.java b/javatests/com/google/turbine/processing/TurbineAnnotationMirrorTest.java
index a049860..b8daced 100644
--- a/javatests/com/google/turbine/processing/TurbineAnnotationMirrorTest.java
+++ b/javatests/com/google/turbine/processing/TurbineAnnotationMirrorTest.java
@@ -20,6 +20,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.truth.Truth.assertThat;
+import com.google.auto.common.AnnotationValues;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -134,17 +135,17 @@ public class TurbineAnnotationMirrorTest {
@Override
public Object visitType(TypeMirror t, Void unused) {
- return value.toString();
+ return AnnotationValues.toString(value);
}
@Override
public Object visitEnumConstant(VariableElement c, Void unused) {
- return value.toString();
+ return AnnotationValues.toString(value);
}
@Override
public Object visitAnnotation(AnnotationMirror a, Void unused) {
- return value.toString();
+ return AnnotationValues.toString(value);
}
@Override
diff --git a/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java b/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java
index d339700..a8c00aa 100644
--- a/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java
+++ b/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java
@@ -18,11 +18,11 @@ package com.google.turbine.processing;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
+import static com.google.turbine.testing.TestResources.getResourceBytes;
+import static org.junit.Assert.assertThrows;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
-import com.google.common.io.ByteStreams;
import com.google.common.primitives.Ints;
import com.google.common.testing.EqualsTester;
import com.google.turbine.binder.Binder;
@@ -39,7 +39,6 @@ import com.google.turbine.processing.TurbineElement.TurbineTypeElement;
import com.google.turbine.testing.TestClassPaths;
import com.google.turbine.tree.Tree.CompUnit;
import java.io.IOException;
-import java.io.InputStream;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
@@ -161,17 +160,13 @@ public class TurbineAnnotationProxyTest {
assertThat(a.b().value()).isEqualTo(-1);
assertThat(a.e()).isEqualTo(ElementType.PACKAGE);
- try {
- a.c();
- fail();
- } catch (MirroredTypeException e) {
+ {
+ MirroredTypeException e = assertThrows(MirroredTypeException.class, () -> a.c());
assertThat(e.getTypeMirror().getKind()).isEqualTo(TypeKind.DECLARED);
assertThat(getQualifiedName(e.getTypeMirror())).contains("java.lang.String");
}
- try {
- a.cx();
- fail();
- } catch (MirroredTypesException e) {
+ {
+ MirroredTypesException e = assertThrows(MirroredTypesException.class, () -> a.cx());
assertThat(
e.getTypeMirrors().stream().map(m -> getQualifiedName(m)).collect(toImmutableList()))
.containsExactly("java.lang.Integer", "java.lang.Long");
@@ -208,9 +203,7 @@ public class TurbineAnnotationProxyTest {
private static void addClass(JarOutputStream jos, Class<?> clazz) throws IOException {
String entryPath = clazz.getName().replace('.', '/') + ".class";
jos.putNextEntry(new JarEntry(entryPath));
- try (InputStream is = clazz.getClassLoader().getResourceAsStream(entryPath)) {
- ByteStreams.copy(is, jos);
- }
+ jos.write(getResourceBytes(clazz, "/" + entryPath));
}
private static String getQualifiedName(TypeMirror typeMirror) {
diff --git a/javatests/com/google/turbine/processing/TurbineElementsHidesTest.java b/javatests/com/google/turbine/processing/TurbineElementsHidesTest.java
new file mode 100644
index 0000000..55e9039
--- /dev/null
+++ b/javatests/com/google/turbine/processing/TurbineElementsHidesTest.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2019 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.google.turbine.processing;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.Arrays.stream;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ObjectArrays;
+import com.google.common.truth.Expect;
+import com.google.turbine.binder.Binder;
+import com.google.turbine.binder.ClassPathBinder;
+import com.google.turbine.binder.bound.TypeBoundClass;
+import com.google.turbine.binder.env.CompoundEnv;
+import com.google.turbine.binder.env.Env;
+import com.google.turbine.binder.env.SimpleEnv;
+import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.diag.SourceFile;
+import com.google.turbine.lower.IntegrationTestSupport;
+import com.google.turbine.lower.IntegrationTestSupport.TestInput;
+import com.google.turbine.parse.Parser;
+import com.google.turbine.processing.TurbineElement.TurbineTypeElement;
+import com.google.turbine.testing.TestClassPaths;
+import com.google.turbine.tree.Tree.CompUnit;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TaskEvent;
+import com.sun.source.util.TaskListener;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementScanner8;
+import javax.lang.model.util.Elements;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaFileObject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TurbineElementsHidesTest {
+
+ @Rule public final Expect expect = Expect.create();
+
+ @Parameters
+ public static Iterable<TestInput[]> parameters() {
+ // An array of test inputs. Each element is an array of lines of sources to compile.
+ String[][] inputs = {
+ {
+ "=== A.java ===", //
+ "abstract class A {",
+ " int f;",
+ " static int f() { return 1; }",
+ " static int f(int x) { return 1; }",
+ "}",
+ "=== B.java ===",
+ "abstract class B extends A {",
+ " int f;",
+ " int g;",
+ " static int f() { return 1; }",
+ " static int f(int x) { return 1; }",
+ " static int g() { return 1; }",
+ " static int g(int x) { return 1; }",
+ "}",
+ "=== C.java ===",
+ "abstract class C extends B {",
+ " int f;",
+ " int g;",
+ " int h;",
+ " static int f() { return 1; }",
+ " static int g() { return 1; }",
+ " static int h() { return 1; }",
+ " static int f(int x) { return 1; }",
+ " static int g(int x) { return 1; }",
+ " static int h(int x) { return 1; }",
+ "}",
+ },
+ {
+ "=== A.java ===",
+ "class A {",
+ " class I {",
+ " }",
+ "}",
+ "=== B.java ===",
+ "class B extends A {",
+ " class I extends A.I {",
+ " }",
+ "}",
+ "=== C.java ===",
+ "class C extends B {",
+ " class I extends B.I {",
+ " }",
+ "}",
+ },
+ {
+ "=== A.java ===",
+ "class A {",
+ " class I {",
+ " }",
+ "}",
+ "=== B.java ===",
+ "class B extends A {",
+ " interface I {}",
+ "}",
+ "=== C.java ===",
+ "class C extends B {",
+ " @interface I {}",
+ "}",
+ },
+ {
+ // the containing class or interface of Intf.foo is an interface
+ "=== Outer.java ===",
+ "class Outer {",
+ " static class Inner {",
+ " static void foo() {}",
+ " static class Innerer extends Inner {",
+ " interface Intf {",
+ " static void foo() {}",
+ " }",
+ " }",
+ " }",
+ "}",
+ },
+ {
+ // test two top-level classes with the same name
+ "=== one/A.java ===",
+ "package one;",
+ "public class A {",
+ "}",
+ "=== two/A.java ===",
+ "package two;",
+ "public class A {",
+ "}",
+ },
+ };
+ // https://bugs.openjdk.java.net/browse/JDK-8275746
+ if (Runtime.version().feature() >= 11) {
+ inputs =
+ ObjectArrays.concat(
+ inputs,
+ new String[][] {
+ {
+ // interfaces
+ "=== A.java ===",
+ "interface A {",
+ " static void f() {}",
+ " int x = 42;",
+ "}",
+ "=== B.java ===",
+ "interface B extends A {",
+ " static void f() {}",
+ " int x = 42;",
+ "}",
+ }
+ },
+ String[].class);
+ }
+ return stream(inputs)
+ .map(input -> TestInput.parse(Joiner.on('\n').join(input)))
+ .map(x -> new TestInput[] {x})
+ .collect(toImmutableList());
+ }
+
+ private final TestInput input;
+
+ public TurbineElementsHidesTest(TestInput input) {
+ this.input = input;
+ }
+
+ // Compile the test inputs with javac and turbine, and assert that 'hides' returns the same
+ // results under each implementation.
+ @Test
+ public void test() throws Exception {
+ HidesTester javac = runJavac();
+ HidesTester turbine = runTurbine();
+ assertThat(javac.keys()).containsExactlyElementsIn(turbine.keys());
+ for (String k1 : javac.keys()) {
+ for (String k2 : javac.keys()) {
+ expect
+ .withMessage("hides(%s, %s)", k1, k2)
+ .that(javac.test(k1, k2))
+ .isEqualTo(turbine.test(k1, k2));
+ }
+ }
+ }
+
+ static class HidesTester {
+ // The elements for a particular annotation processing implementation
+ final Elements elements;
+ // A collection of Elements to use as test inputs, keyed by unique strings that can be used to
+ // compare them across processing implementations
+ final ImmutableMap<String, Element> inputs;
+
+ HidesTester(Elements elements, ImmutableMap<String, Element> inputs) {
+ this.elements = elements;
+ this.inputs = inputs;
+ }
+
+ boolean test(String k1, String k2) {
+ return elements.hides(inputs.get(k1), inputs.get(k2));
+ }
+
+ public ImmutableSet<String> keys() {
+ return inputs.keySet();
+ }
+ }
+
+ /** Compiles the test input with turbine. */
+ private HidesTester runTurbine() throws IOException {
+ ImmutableList<CompUnit> units =
+ input.sources.entrySet().stream()
+ .map(e -> new SourceFile(e.getKey(), e.getValue()))
+ .map(Parser::parse)
+ .collect(toImmutableList());
+ Binder.BindingResult bound =
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty());
+ Env<ClassSymbol, TypeBoundClass> env =
+ CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv())
+ .append(new SimpleEnv<>(bound.units()));
+ ModelFactory factory = new ModelFactory(env, ClassLoader.getSystemClassLoader(), bound.tli());
+ TurbineTypes turbineTypes = new TurbineTypes(factory);
+ TurbineElements elements = new TurbineElements(factory, turbineTypes);
+ ImmutableList<TurbineTypeElement> typeElements =
+ bound.units().keySet().stream().map(factory::typeElement).collect(toImmutableList());
+ return new HidesTester(elements, collectElements(typeElements));
+ }
+
+ /** Compiles the test input with turbine. */
+ private HidesTester runJavac() throws Exception {
+ DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
+ JavacTask javacTask =
+ IntegrationTestSupport.runJavacAnalysis(
+ input.sources, ImmutableList.of(), ImmutableList.of(), diagnostics);
+ List<TypeElement> typeElements = new ArrayList<>();
+ javacTask.addTaskListener(
+ new TaskListener() {
+ @Override
+ public void started(TaskEvent e) {
+ if (e.getKind().equals(TaskEvent.Kind.ANALYZE)) {
+ typeElements.add(e.getTypeElement());
+ }
+ }
+ });
+ Elements elements = javacTask.getElements();
+ if (!javacTask.call()) {
+ fail(Joiner.on("\n").join(diagnostics.getDiagnostics()));
+ }
+ return new HidesTester(elements, collectElements(typeElements));
+ }
+
+ /** Scans a test compilation for elements to use as test inputs. */
+ private ImmutableMap<String, Element> collectElements(List<? extends TypeElement> typeElements) {
+ Map<String, Element> elements = new HashMap<>();
+ for (TypeElement typeElement : typeElements) {
+ elements.put(key(typeElement), typeElement);
+ new ElementScanner8<Void, Void>() {
+ @Override
+ public Void scan(Element e, Void unused) {
+ Element p = elements.put(key(e), e);
+ if (p != null && !e.equals(p) && !p.getKind().equals(ElementKind.CONSTRUCTOR)) {
+ throw new AssertionError(key(e) + " " + p + " " + e);
+ }
+ return super.scan(e, unused);
+ }
+ }.visit(typeElement);
+ }
+ return ImmutableMap.copyOf(elements);
+ }
+
+ /** A unique string representation of an element. */
+ private static String key(Element e) {
+ ArrayDeque<Name> names = new ArrayDeque<>();
+ Element curr = e;
+ do {
+ if (curr.getSimpleName().length() > 0) {
+ names.addFirst(curr.getSimpleName());
+ }
+ curr = curr.getEnclosingElement();
+ } while (curr != null);
+ String key = e.getKind() + ":" + Joiner.on('.').join(names);
+ if (e.getKind().equals(ElementKind.METHOD)) {
+ key += ":" + e.asType();
+ }
+ return key;
+ }
+}
diff --git a/javatests/com/google/turbine/processing/TurbineElementsTest.java b/javatests/com/google/turbine/processing/TurbineElementsTest.java
index 770e6f6..281bde4 100644
--- a/javatests/com/google/turbine/processing/TurbineElementsTest.java
+++ b/javatests/com/google/turbine/processing/TurbineElementsTest.java
@@ -20,6 +20,8 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.MoreCollectors.onlyElement;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.TruthJUnit.assume;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
@@ -225,6 +227,12 @@ public class TurbineElementsTest {
.isFalse();
assertThat(turbineElements.isDeprecated(turbineElements.getTypeElement("One"))).isFalse();
assertThat(turbineElements.isDeprecated(turbineElements.getTypeElement("Test"))).isTrue();
+ for (Element e : turbineElements.getTypeElement("java.lang.Object").getEnclosedElements()) {
+ assume().that(e.getSimpleName().contentEquals("finalize")).isFalse();
+ assertWithMessage(e.getSimpleName().toString())
+ .that(turbineElements.isDeprecated(e))
+ .isFalse();
+ }
}
@Test
diff --git a/javatests/com/google/turbine/processing/TurbineFilerTest.java b/javatests/com/google/turbine/processing/TurbineFilerTest.java
index 40b78ea..83dcc70 100644
--- a/javatests/com/google/turbine/processing/TurbineFilerTest.java
+++ b/javatests/com/google/turbine/processing/TurbineFilerTest.java
@@ -19,7 +19,7 @@ package com.google.turbine.processing;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
@@ -40,7 +40,7 @@ import javax.lang.model.element.Element;
import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.nullness.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -56,9 +56,8 @@ public class TurbineFilerTest {
public void setup() {
Function<String, Supplier<byte[]>> classpath =
new Function<String, Supplier<byte[]>>() {
- @Nullable
@Override
- public Supplier<byte[]> apply(String input) {
+ public @Nullable Supplier<byte[]> apply(String input) {
return null;
}
};
@@ -98,19 +97,13 @@ public class TurbineFilerTest {
seen.add("com/foo/Bar.java");
seen.add("com/foo/Baz.class");
- try {
- filer.createSourceFile("com.foo.Bar", (Element[]) null);
- fail();
- } catch (FilerException expected) {
- }
- filer.createSourceFile("com.foo.Baz", (Element[]) null);
+ assertThrows(
+ FilerException.class, () -> filer.createSourceFile("com.foo.Bar", (Element[]) null));
+ JavaFileObject unused = filer.createSourceFile("com.foo.Baz", (Element[]) null);
- filer.createClassFile("com.foo.Bar", (Element[]) null);
- try {
- filer.createClassFile("com.foo.Baz", (Element[]) null);
- fail();
- } catch (FilerException expected) {
- }
+ unused = filer.createClassFile("com.foo.Bar", (Element[]) null);
+ assertThrows(
+ FilerException.class, () -> filer.createClassFile("com.foo.Baz", (Element[]) null));
}
@Test
@@ -121,11 +114,7 @@ public class TurbineFilerTest {
StandardLocation.SOURCE_OUTPUT,
StandardLocation.ANNOTATION_PROCESSOR_PATH,
StandardLocation.CLASS_PATH)) {
- try {
- filer.getResource(location, "", "NoSuch");
- fail();
- } catch (FileNotFoundException expected) {
- }
+ assertThrows(FileNotFoundException.class, () -> filer.getResource(location, "", "NoSuch"));
}
}
@@ -135,7 +124,7 @@ public class TurbineFilerTest {
try (Writer writer = classFile.openWriter()) {
writer.write("hello");
}
- filer.finishRound();
+ Collection<SourceFile> unused = filer.finishRound();
FileObject output = filer.getResource(StandardLocation.SOURCE_OUTPUT, "com.foo", "Bar.java");
assertThat(new String(ByteStreams.toByteArray(output.openInputStream()), UTF_8))
@@ -150,7 +139,7 @@ public class TurbineFilerTest {
try (OutputStream os = classFile.openOutputStream()) {
os.write("goodbye".getBytes(UTF_8));
}
- filer.finishRound();
+ Collection<SourceFile> unused = filer.finishRound();
FileObject output = filer.getResource(StandardLocation.CLASS_OUTPUT, "com.foo", "Baz.class");
assertThat(new String(ByteStreams.toByteArray(output.openInputStream()), UTF_8))
diff --git a/javatests/com/google/turbine/processing/TurbineMessagerTest.java b/javatests/com/google/turbine/processing/TurbineMessagerTest.java
index c1e6401..c9ca26f 100644
--- a/javatests/com/google/turbine/processing/TurbineMessagerTest.java
+++ b/javatests/com/google/turbine/processing/TurbineMessagerTest.java
@@ -19,7 +19,11 @@ package com.google.turbine.processing;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static java.util.Comparator.comparing;
+import static java.util.Objects.requireNonNull;
+import static org.junit.Assert.assertThrows;
+import com.google.auto.common.AnnotationMirrors;
+import com.google.auto.common.AnnotationValues;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -133,7 +137,13 @@ public class TurbineMessagerTest {
processingEnv
.getMessager()
.printMessage(
- Diagnostic.Kind.ERROR, String.format("%s %s %s", e, a, av), e, a, av);
+ Diagnostic.Kind.ERROR,
+ String.format(
+ "%s %s %s",
+ e, AnnotationMirrors.toString(a), AnnotationValues.toString(av)),
+ e,
+ a,
+ av);
av.accept(
new SimpleAnnotationValueVisitor8<Void, Void>() {
@Override
@@ -198,35 +208,33 @@ public class TurbineMessagerTest {
.map(TurbineMessagerTest::formatDiagnostic)
.collect(toImmutableList());
- ImmutableList<String> turbineDiagnostics;
ImmutableList<Tree.CompUnit> units =
SOURCES.sources.entrySet().stream()
.map(e -> new SourceFile(e.getKey(), e.getValue()))
.map(Parser::parse)
.collect(toImmutableList());
- try {
- Binder.bind(
- units,
- ClassPathBinder.bindClasspath(ImmutableList.of()),
- Processing.ProcessorInfo.create(
- ImmutableList.of(new DiagnosticTesterProcessor()),
- getClass().getClassLoader(),
- ImmutableMap.of(),
- SourceVersion.latestSupported()),
- TestClassPaths.TURBINE_BOOTCLASSPATH,
- Optional.empty());
- throw new AssertionError();
- } catch (TurbineError e) {
- turbineDiagnostics =
- e.diagnostics().stream()
- .sorted(
- comparing(TurbineDiagnostic::path)
- .thenComparing(TurbineDiagnostic::line)
- .thenComparing(TurbineDiagnostic::column))
- .map(TurbineMessagerTest::formatDiagnostic)
- .collect(toImmutableList());
- }
-
+ TurbineError e =
+ assertThrows(
+ TurbineError.class,
+ () ->
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ Processing.ProcessorInfo.create(
+ ImmutableList.of(new DiagnosticTesterProcessor()),
+ getClass().getClassLoader(),
+ ImmutableMap.of(),
+ SourceVersion.latestSupported()),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ Optional.empty()));
+ ImmutableList<String> turbineDiagnostics =
+ e.diagnostics().stream()
+ .sorted(
+ comparing(TurbineDiagnostic::path)
+ .thenComparing(TurbineDiagnostic::line)
+ .thenComparing(TurbineDiagnostic::column))
+ .map(TurbineMessagerTest::formatDiagnostic)
+ .collect(toImmutableList());
assertThat(turbineDiagnostics).containsExactlyElementsIn(javacDiagnostics).inOrder();
}
@@ -242,7 +250,7 @@ public class TurbineMessagerTest {
private static String shortPath(Diagnostic<? extends JavaFileObject> d) {
return d.getSource() != null
- ? Paths.get(d.getSource().getName()).getFileName().toString()
+ ? requireNonNull(Paths.get(d.getSource().getName()).getFileName()).toString()
: "<>";
}
}
diff --git a/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java b/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java
index 0f9e6a6..b028a81 100644
--- a/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java
+++ b/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java
@@ -17,7 +17,7 @@
package com.google.turbine.processing;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
@@ -83,11 +83,7 @@ public class TurbineTypesFactoryTest {
PrimitiveType type = turbineTypes.getPrimitiveType(kind);
assertThat(type.getKind()).isEqualTo(kind);
} else {
- try {
- turbineTypes.getPrimitiveType(kind);
- fail();
- } catch (IllegalArgumentException expected) {
- }
+ assertThrows(IllegalArgumentException.class, () -> turbineTypes.getPrimitiveType(kind));
}
}
}
@@ -167,11 +163,7 @@ public class TurbineTypesFactoryTest {
public void noType() {
assertThat(turbineTypes.getNoType(TypeKind.VOID).getKind()).isEqualTo(TypeKind.VOID);
assertThat(turbineTypes.getNoType(TypeKind.NONE).getKind()).isEqualTo(TypeKind.NONE);
- try {
- turbineTypes.getNoType(TypeKind.DECLARED);
- fail();
- } catch (IllegalArgumentException expected) {
- }
+ assertThrows(IllegalArgumentException.class, () -> turbineTypes.getNoType(TypeKind.DECLARED));
}
@Test
diff --git a/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java b/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java
index eb5ee6c..00eb571 100644
--- a/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java
+++ b/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java
@@ -18,10 +18,11 @@ package com.google.turbine.processing;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.TruthJUnit.assume;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
+import com.google.turbine.types.Deannotate;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
@@ -68,12 +69,10 @@ public class TurbineTypesUnaryTest extends AbstractTurbineTypesTest {
thrown = e;
}
if (thrown != null) {
- try {
- turbineTypes.unboxedType(turbineA).toString();
- fail(String.format("expected unboxedType(`%s`) to throw", turbineA));
- } catch (IllegalArgumentException expected) {
- // expected
- }
+ assertThrows(
+ String.format("expected unboxedType(`%s`) to throw", turbineA),
+ IllegalArgumentException.class,
+ () -> turbineTypes.unboxedType(turbineA).toString());
} else {
String actual = turbineTypes.unboxedType(turbineA).toString();
assertWithMessage("unboxedClass(`%s`) = unboxedClass(`%s`)", javacA, turbineA)
@@ -121,16 +120,8 @@ public class TurbineTypesUnaryTest extends AbstractTurbineTypesTest {
public void directSupertypesThrows() {
assume().that(UNSUPPORTED_BY_DIRECT_SUPERTYPES).contains(javacA.getKind());
- try {
- javacTypes.directSupertypes(turbineA);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- try {
- turbineTypes.directSupertypes(turbineA);
- fail();
- } catch (IllegalArgumentException expected) {
- }
+ assertThrows(IllegalArgumentException.class, () -> javacTypes.directSupertypes(turbineA));
+ assertThrows(IllegalArgumentException.class, () -> turbineTypes.directSupertypes(turbineA));
}
@Test
@@ -144,4 +135,16 @@ public class TurbineTypesUnaryTest extends AbstractTurbineTypesTest {
.that(actual)
.isEqualTo(expected);
}
+
+ @Test
+ public void deannotate() {
+ String toString = turbineA.toString();
+ String deannotated =
+ Deannotate.deannotate(((TurbineTypeMirror) turbineA).asTurbineType()).toString();
+ if (toString.contains("@")) {
+ assertWithMessage("deannotate(`%s`) = `%s`", toString, deannotated)
+ .that(deannotated)
+ .doesNotContain("@");
+ }
+ }
}
diff --git a/javatests/com/google/turbine/testing/AsmUtils.java b/javatests/com/google/turbine/testing/AsmUtils.java
index 5b5e102..b7e77bc 100644
--- a/javatests/com/google/turbine/testing/AsmUtils.java
+++ b/javatests/com/google/turbine/testing/AsmUtils.java
@@ -27,14 +27,18 @@ import org.objectweb.asm.util.TraceClassVisitor;
* ASM-based test utilities, in their own class mostly to avoid namespace issues with e.g. {@link
* com.google.turbine.bytecode.ClassReader}.
*/
-public class AsmUtils {
- public static String textify(byte[] bytes) {
+public final class AsmUtils {
+ public static String textify(byte[] bytes, boolean skipDebug) {
Printer textifier = new Textifier();
StringWriter sw = new StringWriter();
new ClassReader(bytes)
.accept(
new TraceClassVisitor(null, textifier, new PrintWriter(sw, true)),
- ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE);
+ ClassReader.SKIP_FRAMES
+ | ClassReader.SKIP_CODE
+ | (skipDebug ? ClassReader.SKIP_DEBUG : 0));
return sw.toString();
}
+
+ private AsmUtils() {}
}
diff --git a/javatests/com/google/turbine/testing/TestClassPaths.java b/javatests/com/google/turbine/testing/TestClassPaths.java
index 93be916..56b471c 100644
--- a/javatests/com/google/turbine/testing/TestClassPaths.java
+++ b/javatests/com/google/turbine/testing/TestClassPaths.java
@@ -20,10 +20,10 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Streams;
import com.google.turbine.binder.ClassPath;
import com.google.turbine.binder.ClassPathBinder;
-import com.google.turbine.binder.CtSymClassBinder;
+import com.google.turbine.binder.JimageClassBinder;
+import com.google.turbine.options.LanguageVersion;
import com.google.turbine.options.TurbineOptions;
import java.io.File;
import java.io.IOException;
@@ -33,15 +33,14 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
-public class TestClassPaths {
+public final class TestClassPaths {
private static final Splitter CLASS_PATH_SPLITTER =
Splitter.on(File.pathSeparatorChar).omitEmptyStrings();
- private static final ImmutableList<Path> BOOTCLASSPATH =
- Streams.stream(
- CLASS_PATH_SPLITTER.split(
- Optional.ofNullable(System.getProperty("sun.boot.class.path")).orElse("")))
+ public static final ImmutableList<Path> BOOTCLASSPATH =
+ CLASS_PATH_SPLITTER
+ .splitToStream(Optional.ofNullable(System.getProperty("sun.boot.class.path")).orElse(""))
.map(Paths::get)
.filter(Files::exists)
.collect(toImmutableList());
@@ -53,7 +52,7 @@ public class TestClassPaths {
if (!BOOTCLASSPATH.isEmpty()) {
return ClassPathBinder.bindClasspath(BOOTCLASSPATH);
}
- return CtSymClassBinder.bind("8");
+ return JimageClassBinder.bindDefault();
} catch (IOException e) {
e.printStackTrace();
throw new UncheckedIOException(e);
@@ -70,8 +69,10 @@ public class TestClassPaths {
options.setBootClassPath(
BOOTCLASSPATH.stream().map(Path::toString).collect(toImmutableList()));
} else {
- options.setRelease("8");
+ options.setLanguageVersion(LanguageVersion.fromJavacopts(ImmutableList.of("--release", "8")));
}
return options;
}
+
+ private TestClassPaths() {}
}
diff --git a/javatests/com/google/turbine/testing/TestResources.java b/javatests/com/google/turbine/testing/TestResources.java
new file mode 100644
index 0000000..86c7632
--- /dev/null
+++ b/javatests/com/google/turbine/testing/TestResources.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2021 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.google.turbine.testing;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+
+public final class TestResources {
+
+ public static String getResource(Class<?> clazz, String resource) {
+ return new String(getResourceBytes(clazz, resource), UTF_8);
+ }
+
+ public static byte[] getResourceBytes(Class<?> clazz, String resource) {
+ try (InputStream is = requireNonNull(clazz.getResourceAsStream(resource), resource)) {
+ return ByteStreams.toByteArray(is);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private TestResources() {}
+}
diff --git a/javatests/com/google/turbine/zip/ZipTest.java b/javatests/com/google/turbine/zip/ZipTest.java
index bfc9cdf..e9dfc44 100644
--- a/javatests/com/google/turbine/zip/ZipTest.java
+++ b/javatests/com/google/turbine/zip/ZipTest.java
@@ -18,7 +18,7 @@ package com.google.turbine.zip;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableMap;
import com.google.common.hash.Hashing;
@@ -159,13 +159,9 @@ public class ZipTest {
createEntry(zos, "hello", "world".getBytes(UTF_8));
zos.setComment("this is a comment");
}
- Files.write(path, "trailing garbage".getBytes(UTF_8), StandardOpenOption.APPEND);
+ Files.writeString(path, "trailing garbage", StandardOpenOption.APPEND);
- try {
- actual(path);
- fail();
- } catch (ZipException e) {
- assertThat(e).hasMessageThat().isEqualTo("zip file comment length was 33, expected 17");
- }
+ ZipException e = assertThrows(ZipException.class, () -> actual(path));
+ assertThat(e).hasMessageThat().isEqualTo("zip file comment length was 33, expected 17");
}
}
diff --git a/pom.xml b/pom.xml
index fa923f4..b007e74 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,4 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2020 Google Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -7,19 +23,34 @@
<groupId>com.google.turbine</groupId>
<artifactId>turbine</artifactId>
- <version>0.1-SNAPSHOT</version>
+ <version>HEAD-SNAPSHOT</version>
<name>turbine</name>
- <description>
- turbine is a header compiler for Java
- </description>
+ <description>turbine is a header compiler for Java</description>
+ <url>https://github.com/google/turbine</url>
<properties>
- <asm.version>7.0</asm.version>
- <javac.version>9+181-r4173-1</javac.version>
- <guava.version>27.0.1-jre</guava.version>
+ <asm.version>9.2</asm.version>
+ <guava.version>31.0.1-jre</guava.version>
+ <errorprone.version>2.11.0</errorprone.version>
+ <maven-javadoc-plugin.version>3.3.1</maven-javadoc-plugin.version>
+ <maven-source-plugin.version>3.2.1</maven-source-plugin.version>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <protobuf.version>3.19.2</protobuf.version>
+ <grpc.version>1.43.2</grpc.version>
</properties>
+ <organization>
+ <name>Google Inc.</name>
+ <url>http://www.google.com/</url>
+ </organization>
+
+ <developers>
+ <developer>
+ <name>Liam Miller-Cushon</name>
+ </developer>
+ </developers>
+
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
@@ -27,19 +58,20 @@
<version>${guava.version}</version>
</dependency>
<dependency>
- <groupId>com.google.code.findbugs</groupId>
- <artifactId>jsr305</artifactId>
- <version>2.0.1</version>
- </dependency>
- <dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
- <version>2.0.12</version>
+ <version>${errorprone.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jspecify</groupId>
+ <artifactId>jspecify</artifactId>
+ <version>0.2.0</version>
+ <optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
- <version>3.1.0</version>
+ <version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
@@ -60,39 +92,33 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>com.google.errorprone</groupId>
- <artifactId>javac</artifactId>
- <version>${javac.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <version>4.12</version>
+ <version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
- <version>1.0</version>
+ <version>1.1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.truth.extensions</groupId>
<artifactId>truth-proto-extension</artifactId>
- <version>1.0</version>
+ <version>1.1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.truth.extensions</groupId>
<artifactId>truth-java8-extension</artifactId>
- <version>1.0</version>
+ <version>1.1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.jimfs</groupId>
<artifactId>jimfs</artifactId>
- <version>1.0</version>
+ <version>1.2</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -103,10 +129,16 @@
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
- <artifactId>auto-value</artifactId>
- <version>1.5.3</version>
+ <artifactId>auto-value-annotations</artifactId>
+ <version>1.9</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.google.auto</groupId>
+ <artifactId>auto-common</artifactId>
+ <version>1.2.1</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
@@ -125,32 +157,46 @@
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
- <version>1.4.0.Final</version>
+ <version>1.7.0</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.6.2</version>
+ <version>3.9.0</version>
<configuration>
- <fork>true</fork>
- <source>1.8</source>
- <target>1.8</target>
+ <source>8</source>
+ <target>8</target>
<encoding>UTF-8</encoding>
- <compilerArgument>-parameters</compilerArgument>
- <testCompilerArgument>-parameters</testCompilerArgument>
+ <compilerArgs>
+ <arg>-parameters</arg>
+ <arg>-XDcompilePolicy=simple</arg>
+ <arg>-Xplugin:ErrorProne</arg>
+ </compilerArgs>
+ <annotationProcessorPaths>
+ <path>
+ <groupId>com.google.errorprone</groupId>
+ <artifactId>error_prone_core</artifactId>
+ <version>${errorprone.version}</version>
+ </path>
+ <path>
+ <groupId>com.google.auto.value</groupId>
+ <artifactId>auto-value</artifactId>
+ <version>1.7.4</version>
+ </path>
+ </annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
- <version>0.5.0</version>
+ <version>0.6.1</version>
<configuration>
<protoSourceRoot>proto</protoSourceRoot>
- <protocArtifact>com.google.protobuf:protoc:3.1.0:exe:${os.detected.classifier}</protocArtifact>
+ <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
- <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.0.1:exe:${os.detected.classifier}</pluginArtifact>
+ <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
@@ -164,16 +210,28 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>2.19.1</version>
+ <version>2.22.2</version>
<configuration>
<!-- set heap size to work around http://github.com/travis-ci/travis-ci/issues/3396 -->
- <argLine>-Xmx2g</argLine>
+ <argLine>
+ -Xmx2g
+ --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+ --add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+ --add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
+ </argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
- <version>2.4.3</version>
+ <version>3.2.4</version>
<executions>
<execution>
<id>shade-all-deps</id>
@@ -200,24 +258,77 @@
</execution>
</executions>
</plugin>
-
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>3.3.1</version>
+ <configuration>
+ <source>8</source>
+ <detectJavaApiLink>false</detectJavaApiLink>
+ <notimestamp>true</notimestamp>
+ <doctitle>turbine ${project.version} API</doctitle>
+ </configuration>
+ </plugin>
</plugins>
</build>
+
+ <distributionManagement>
+ <snapshotRepository>
+ <id>sonatype-nexus-snapshots</id>
+ <name>Sonatype Nexus Snapshots</name>
+ <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
+ </snapshotRepository>
+ <repository>
+ <id>sonatype-nexus-staging</id>
+ <name>Nexus Release Repository</name>
+ <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+ </repository>
+ </distributionManagement>
+
<profiles>
<profile>
- <id>java-8</id>
- <activation>
- <jdk>1.8</jdk>
- </activation>
+ <id>sonatype-oss-release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <!-- put javac.jar on bootclasspath when executing tests -->
- <argLine>-Xbootclasspath/p:${settings.localRepository}/com/google/errorprone/javac/${javac.version}/javac-${javac.version}.jar</argLine>
- </configuration>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>${maven-source-plugin.version}</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar-no-fork</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${maven-javadoc-plugin.version}</version>
+ <executions>
+ <execution>
+ <id>attach-javadocs</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>3.0.1</version>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
</plugin>
</plugins>
</build>