diff --git a/java/com/google/turbine/binder/Binder.java b/java/com/google/turbine/binder/Binder.java index d437eea3..ee2a6745 100644 --- a/java/com/google/turbine/binder/Binder.java +++ b/java/com/google/turbine/binder/Binder.java @@ -171,6 +171,8 @@ static BindingResult bind( henv, CompoundEnv.of(classPathEnv).append(henv)); + tenv = PermitsBinder.bindPermits(syms, tenv); + tenv = constants( syms, diff --git a/java/com/google/turbine/binder/PermitsBinder.java b/java/com/google/turbine/binder/PermitsBinder.java new file mode 100644 index 00000000..06063d37 --- /dev/null +++ b/java/com/google/turbine/binder/PermitsBinder.java @@ -0,0 +1,110 @@ +/* + * Copyright 2024 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.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.MultimapBuilder; +import com.google.turbine.binder.bound.SourceTypeBoundClass; +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.model.TurbineFlag; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +final class PermitsBinder { + + /** + * Given the classes in the current compilation, finds implicit permitted subtypes of sealed + * classes. + * + *

See JLS ยง8.1.1.2 for details of implicit permits. + * + * @param syms the set of classes being compiled in this compilation unit + * @param tenv the environment of the current compilation unit only. Dependencies from the + * classpath or bootclasspath are not required by this pass, because any implicitly permitted + * subtypes are required to be in the same compilation unit as their supertype. + */ + static Env bindPermits( + ImmutableSet syms, Env tenv) { + Set sealedClassesWithoutExplicitPermits = new HashSet<>(); + for (ClassSymbol sym : syms) { + SourceTypeBoundClass info = tenv.getNonNull(sym); + if (((info.access() & TurbineFlag.ACC_SEALED) == TurbineFlag.ACC_SEALED) + && info.permits().isEmpty()) { + sealedClassesWithoutExplicitPermits.add(sym); + } + } + if (sealedClassesWithoutExplicitPermits.isEmpty()) { + // fast path if there were no sealed types with an empty 'permits' clause + return tenv; + } + ListMultimap permits = + MultimapBuilder.hashKeys().arrayListValues().build(); + for (ClassSymbol sym : syms) { + SourceTypeBoundClass info = tenv.getNonNull(sym); + // Check if the current class has a direct supertype that is a sealed class with an empty + // 'permits' clause. + ClassSymbol superclass = info.superclass(); + if (superclass != null && sealedClassesWithoutExplicitPermits.contains(superclass)) { + permits.put(superclass, sym); + } + for (ClassSymbol i : info.interfaces()) { + if (sealedClassesWithoutExplicitPermits.contains(i)) { + permits.put(i, sym); + } + } + } + SimpleEnv.Builder builder = SimpleEnv.builder(); + for (ClassSymbol sym : syms) { + List thisPermits = permits.get(sym); + SourceTypeBoundClass base = tenv.getNonNull(sym); + if (thisPermits.isEmpty()) { + builder.put(sym, base); + } else { + builder.put( + sym, + new SourceTypeBoundClass( + /* interfaceTypes= */ base.interfaceTypes(), + /* permits= */ ImmutableList.copyOf(thisPermits), + /* superClassType= */ base.superClassType(), + /* typeParameterTypes= */ base.typeParameterTypes(), + /* access= */ base.access(), + /* components= */ base.components(), + /* methods= */ base.methods(), + /* fields= */ base.fields(), + /* owner= */ base.owner(), + /* kind= */ base.kind(), + /* children= */ base.children(), + /* typeParameters= */ base.typeParameters(), + /* enclosingScope= */ base.enclosingScope(), + /* scope= */ base.scope(), + /* memberImports= */ base.memberImports(), + /* annotationMetadata= */ base.annotationMetadata(), + /* annotations= */ base.annotations(), + /* source= */ base.source(), + /* decl= */ base.decl())); + } + } + return builder.build(); + } + + private PermitsBinder() {} +} diff --git a/javatests/com/google/turbine/lower/LowerIntegrationTest.java b/javatests/com/google/turbine/lower/LowerIntegrationTest.java index 234cd1c2..bac2b5a3 100644 --- a/javatests/com/google/turbine/lower/LowerIntegrationTest.java +++ b/javatests/com/google/turbine/lower/LowerIntegrationTest.java @@ -64,7 +64,8 @@ public class LowerIntegrationTest { entry("sealed_nested.test", 17), entry("textblock.test", 15), entry("textblock2.test", 15), - entry("B306423115.test", 15)); + entry("B306423115.test", 15), + entry("permits.test", 17)); private static final ImmutableSet SOURCE_VERSION_PREVIEW = ImmutableSet.of(); @@ -273,6 +274,7 @@ public static Iterable parameters() { "packagedecl.test", "packageprivateprotectedinner.test", "param_bound.test", + "permits.test", "prim_class.test", "private_member.test", "privateinner.test", diff --git a/javatests/com/google/turbine/lower/testdata/permits.test b/javatests/com/google/turbine/lower/testdata/permits.test new file mode 100644 index 00000000..420bc158 --- /dev/null +++ b/javatests/com/google/turbine/lower/testdata/permits.test @@ -0,0 +1,5 @@ +=== A.java === +sealed interface A { + final class B implements A {} + record C() implements A {} +} \ No newline at end of file