diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-11 05:13:56 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-11 05:13:56 +0000 |
commit | ef0f2ca72e351217f1422d85850b6010764750d0 (patch) | |
tree | b7c0ba640fd38e96068089eb744b59b100cfe192 | |
parent | 8a8c1f2648124e7e7612fdc620ee25b5e8e3e902 (diff) | |
parent | 75c0b4f87df6bec9cdd5fbab2b29acf33128926c (diff) | |
download | turbine-android13-mainline-networking-release.tar.gz |
Snap for 8570526 from 75c0b4f87df6bec9cdd5fbab2b29acf33128926c to mainline-networking-releaseaml_net_331910030aml_net_331812010aml_net_331710000aml_net_331610000aml_net_331412000aml_net_331313030aml_net_331313010aml_net_331110020aml_net_331011030aml_net_330910010aml_net_330811010android13-mainline-networking-release
Change-Id: I05003cd1bb11a6b89bea9b94e5bb6903dc03ccd6
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"); } } @@ -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> |