Skip to content

Commit b0fd22c

Browse files
cushonJavac Team
authored andcommitted
Handle implicit permits in turbine
> If the declaration of a sealed class C lacks a permits clause, then the permitted direct subclasses of C are as follows: > > If C is not an enum class, then its permitted direct subclasses are those classes declared in the same compilation unit as C (§7.3) which have a canonical name (§6.7) and whose direct superclass is C. > > That is, the permitted direct subclasses are inferred as the classes in the same compilation unit that specify C as their direct superclass. The requirement for a canonical name means that no local classes or anonymous classes will be considered. https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.1.1.2 PiperOrigin-RevId: 689011907
1 parent ffafaff commit b0fd22c

File tree

4 files changed

+120
-1
lines changed

4 files changed

+120
-1
lines changed

java/com/google/turbine/binder/Binder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ static BindingResult bind(
171171
henv,
172172
CompoundEnv.<ClassSymbol, HeaderBoundClass>of(classPathEnv).append(henv));
173173

174+
tenv = PermitsBinder.bindPermits(syms, tenv);
175+
174176
tenv =
175177
constants(
176178
syms,
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2024 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.turbine.binder;
18+
19+
import com.google.common.collect.ImmutableList;
20+
import com.google.common.collect.ImmutableSet;
21+
import com.google.common.collect.ListMultimap;
22+
import com.google.common.collect.MultimapBuilder;
23+
import com.google.turbine.binder.bound.SourceTypeBoundClass;
24+
import com.google.turbine.binder.env.Env;
25+
import com.google.turbine.binder.env.SimpleEnv;
26+
import com.google.turbine.binder.sym.ClassSymbol;
27+
import com.google.turbine.model.TurbineFlag;
28+
import java.util.HashSet;
29+
import java.util.List;
30+
import java.util.Set;
31+
32+
final class PermitsBinder {
33+
34+
/**
35+
* Given the classes in the current compilation, finds implicit permitted subtypes of sealed
36+
* classes.
37+
*
38+
* <p>See JLS §8.1.1.2 for details of implicit permits.
39+
*
40+
* @param syms the set of classes being compiled in this compilation unit
41+
* @param tenv the environment of the current compilation unit only. Dependencies from the
42+
* classpath or bootclasspath are not required by this pass, because any implicitly permitted
43+
* subtypes are required to be in the same compilation unit as their supertype.
44+
*/
45+
static Env<ClassSymbol, SourceTypeBoundClass> bindPermits(
46+
ImmutableSet<ClassSymbol> syms, Env<ClassSymbol, SourceTypeBoundClass> tenv) {
47+
Set<ClassSymbol> sealedClassesWithoutExplicitPermits = new HashSet<>();
48+
for (ClassSymbol sym : syms) {
49+
SourceTypeBoundClass info = tenv.getNonNull(sym);
50+
if (((info.access() & TurbineFlag.ACC_SEALED) == TurbineFlag.ACC_SEALED)
51+
&& info.permits().isEmpty()) {
52+
sealedClassesWithoutExplicitPermits.add(sym);
53+
}
54+
}
55+
if (sealedClassesWithoutExplicitPermits.isEmpty()) {
56+
// fast path if there were no sealed types with an empty 'permits' clause
57+
return tenv;
58+
}
59+
ListMultimap<ClassSymbol, ClassSymbol> permits =
60+
MultimapBuilder.hashKeys().arrayListValues().build();
61+
for (ClassSymbol sym : syms) {
62+
SourceTypeBoundClass info = tenv.getNonNull(sym);
63+
// Check if the current class has a direct supertype that is a sealed class with an empty
64+
// 'permits' clause.
65+
ClassSymbol superclass = info.superclass();
66+
if (superclass != null && sealedClassesWithoutExplicitPermits.contains(superclass)) {
67+
permits.put(superclass, sym);
68+
}
69+
for (ClassSymbol i : info.interfaces()) {
70+
if (sealedClassesWithoutExplicitPermits.contains(i)) {
71+
permits.put(i, sym);
72+
}
73+
}
74+
}
75+
SimpleEnv.Builder<ClassSymbol, SourceTypeBoundClass> builder = SimpleEnv.builder();
76+
for (ClassSymbol sym : syms) {
77+
List<ClassSymbol> thisPermits = permits.get(sym);
78+
SourceTypeBoundClass base = tenv.getNonNull(sym);
79+
if (thisPermits.isEmpty()) {
80+
builder.put(sym, base);
81+
} else {
82+
builder.put(
83+
sym,
84+
new SourceTypeBoundClass(
85+
/* interfaceTypes= */ base.interfaceTypes(),
86+
/* permits= */ ImmutableList.copyOf(thisPermits),
87+
/* superClassType= */ base.superClassType(),
88+
/* typeParameterTypes= */ base.typeParameterTypes(),
89+
/* access= */ base.access(),
90+
/* components= */ base.components(),
91+
/* methods= */ base.methods(),
92+
/* fields= */ base.fields(),
93+
/* owner= */ base.owner(),
94+
/* kind= */ base.kind(),
95+
/* children= */ base.children(),
96+
/* typeParameters= */ base.typeParameters(),
97+
/* enclosingScope= */ base.enclosingScope(),
98+
/* scope= */ base.scope(),
99+
/* memberImports= */ base.memberImports(),
100+
/* annotationMetadata= */ base.annotationMetadata(),
101+
/* annotations= */ base.annotations(),
102+
/* source= */ base.source(),
103+
/* decl= */ base.decl()));
104+
}
105+
}
106+
return builder.build();
107+
}
108+
109+
private PermitsBinder() {}
110+
}

javatests/com/google/turbine/lower/LowerIntegrationTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ public class LowerIntegrationTest {
6464
entry("sealed_nested.test", 17),
6565
entry("textblock.test", 15),
6666
entry("textblock2.test", 15),
67-
entry("B306423115.test", 15));
67+
entry("B306423115.test", 15),
68+
entry("permits.test", 17));
6869

6970
private static final ImmutableSet<String> SOURCE_VERSION_PREVIEW = ImmutableSet.of();
7071

@@ -273,6 +274,7 @@ public static Iterable<Object[]> parameters() {
273274
"packagedecl.test",
274275
"packageprivateprotectedinner.test",
275276
"param_bound.test",
277+
"permits.test",
276278
"prim_class.test",
277279
"private_member.test",
278280
"privateinner.test",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
=== A.java ===
2+
sealed interface A {
3+
final class B implements A {}
4+
record C() implements A {}
5+
}

0 commit comments

Comments
 (0)