Skip to content

Commit

Permalink
Sealed types & switches related fixes
Browse files Browse the repository at this point in the history
* Incorrect exhaustiveness check leads to MatchException at runtime
* A sealed interface with generic causes IllegalStateException

* Fixes #3009
* Fixes #3031
  • Loading branch information
srikanth-sankaran authored Oct 1, 2024
1 parent 497233a commit b88a5b3
Show file tree
Hide file tree
Showing 6 changed files with 498 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public class BinaryTypeBinding extends ReferenceBinding {
protected ReferenceBinding superclass;
protected ReferenceBinding enclosingType;
protected ReferenceBinding[] superInterfaces;
protected ReferenceBinding[] permittedSubtypes;
protected ReferenceBinding[] permittedTypes;
protected FieldBinding[] fields;
protected RecordComponentBinding[] components;
protected MethodBinding[] methods;
Expand Down Expand Up @@ -258,7 +258,7 @@ public BinaryTypeBinding(BinaryTypeBinding prototype) {
this.superclass = prototype.superclass;
this.enclosingType = prototype.enclosingType;
this.superInterfaces = prototype.superInterfaces;
this.permittedSubtypes = prototype.permittedSubtypes;
this.permittedTypes = prototype.permittedTypes;
this.fields = prototype.fields;
this.components = prototype.components;
this.methods = prototype.methods;
Expand Down Expand Up @@ -438,7 +438,7 @@ void cachePartsFrom(IBinaryType binaryType, boolean needFieldsAndMethods) {
// and still want to use binaries passed that point (e.g. type hierarchy resolver, see bug 63748).
this.typeVariables = Binding.NO_TYPE_VARIABLES;
this.superInterfaces = Binding.NO_SUPERINTERFACES;
this.permittedSubtypes = Binding.NO_PERMITTEDTYPES;
this.permittedTypes = Binding.NO_PERMITTEDTYPES;

// must retrieve member types in case superclass/interfaces need them
this.memberTypes = Binding.NO_MEMBER_TYPES;
Expand Down Expand Up @@ -545,30 +545,30 @@ void cachePartsFrom(IBinaryType binaryType, boolean needFieldsAndMethods) {
this.tagBits |= TagBits.HasUnresolvedSuperinterfaces;
}

this.permittedSubtypes = Binding.NO_PERMITTEDTYPES;
this.permittedTypes = Binding.NO_PERMITTEDTYPES;
if (!wrapper.atEnd()) {
// attempt to find each permitted type if it exists in the cache (otherwise - resolve it when requested)
java.util.ArrayList types = new java.util.ArrayList(2);
short rank = 0;
do {
types.add(this.environment.getTypeFromTypeSignature(wrapper, typeVars, this, missingTypeNames, toplevelWalker.toSupertype(rank++, wrapper.peekFullType())));
} while (!wrapper.atEnd());
this.permittedSubtypes = new ReferenceBinding[types.size()];
types.toArray(this.permittedSubtypes);
this.permittedTypes = new ReferenceBinding[types.size()];
types.toArray(this.permittedTypes);
this.extendedTagBits |= ExtendedTagBits.HasUnresolvedPermittedSubtypes;
}

}
// fall back, in case we haven't got them from signature
char[][] permittedSubtypeNames = binaryType.getPermittedSubtypeNames();
if (this.permittedSubtypes == Binding.NO_PERMITTEDTYPES && permittedSubtypeNames != null) {
if (this.permittedTypes == Binding.NO_PERMITTEDTYPES && permittedSubtypeNames != null) {
this.modifiers |= ExtraCompilerModifiers.AccSealed;
int size = permittedSubtypeNames.length;
if (size > 0) {
this.permittedSubtypes = new ReferenceBinding[size];
this.permittedTypes = new ReferenceBinding[size];
for (short i = 0; i < size; i++)
// attempt to find each superinterface if it exists in the cache (otherwise - resolve it when requested)
this.permittedSubtypes[i] = this.environment.getTypeFromConstantPoolName(permittedSubtypeNames[i], 0, -1, false, missingTypeNames, toplevelWalker.toSupertype(i, null));
this.permittedTypes[i] = this.environment.getTypeFromConstantPoolName(permittedSubtypeNames[i], 0, -1, false, missingTypeNames, toplevelWalker.toSupertype(i, null));
}
}
boolean canUseNullTypeAnnotations = this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled && this.environment.globalOptions.sourceLevel >= ClassFileConstants.JDK1_8;
Expand All @@ -582,7 +582,7 @@ void cachePartsFrom(IBinaryType binaryType, boolean needFieldsAndMethods) {
break;
}
}
for (TypeBinding permsub : this.permittedSubtypes) {
for (TypeBinding permsub : this.permittedTypes) {
if (permsub.hasNullTypeAnnotations()) {
this.externalAnnotationStatus = ExternalAnnotationStatus.TYPE_IS_ANNOTATED;
break;
Expand Down Expand Up @@ -2570,13 +2570,13 @@ public ReferenceBinding[] superInterfaces() {
public ReferenceBinding[] permittedTypes() {

if (!isPrototype()) {
return this.permittedSubtypes = this.prototype.permittedTypes();
return this.permittedTypes = this.prototype.permittedTypes();
}
for (int i = this.permittedSubtypes.length; --i >= 0;)
this.permittedSubtypes[i] = (ReferenceBinding) resolveType(this.permittedSubtypes[i], this.environment, false);
for (int i = this.permittedTypes.length; --i >= 0;)
this.permittedTypes[i] = (ReferenceBinding) resolveType(this.permittedTypes[i], this.environment, false);

// Note: unlike for superinterfaces() hierarchy check not required here since these are subtypes
return this.permittedSubtypes;
return this.permittedTypes;
}
@Override
public TypeVariableBinding[] typeVariables() {
Expand Down Expand Up @@ -2646,13 +2646,13 @@ public String toString() {
buffer.append("NULL SUPERINTERFACES"); //$NON-NLS-1$
}

if (this.permittedSubtypes != null) {
if (this.permittedSubtypes != Binding.NO_PERMITTEDTYPES) {
if (this.permittedTypes != null) {
if (this.permittedTypes != Binding.NO_PERMITTEDTYPES) {
buffer.append("\n\tpermits : "); //$NON-NLS-1$
for (int i = 0, length = this.permittedSubtypes.length; i < length; i++) {
for (int i = 0, length = this.permittedTypes.length; i < length; i++) {
if (i > 0)
buffer.append(", "); //$NON-NLS-1$
buffer.append((this.permittedSubtypes[i] != null) ? this.permittedSubtypes[i].debugName() : "NULL TYPE"); //$NON-NLS-1$
buffer.append((this.permittedTypes[i] != null) ? this.permittedTypes[i].debugName() : "NULL TYPE"); //$NON-NLS-1$
}
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1687,7 +1687,7 @@ private ReferenceBinding findPermittedtype(TypeReference typeReference) {
}
typeReference.bits |= ASTNode.IgnoreRawTypeCheck;
ReferenceBinding permittedType = (ReferenceBinding) typeReference.resolveType(this);
return permittedType;
return permittedType != null ? permittedType.actualType() : permittedType; // while permitted classes/interfaces cannot be parameterized with type arguments, they are not raw either
} catch (AbortCompilation e) {
SourceTypeBinding sourceType = this.referenceContext.binding;
if (sourceType.permittedTypes == null) sourceType.setPermittedTypes(Binding.NO_PERMITTEDTYPES);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public MissingTypeBinding(PackageBinding packageBinding, char[][] compoundName,
this.modifiers = ClassFileConstants.AccPublic;
this.superclass = null; // will be fixed up using #setMissingSuperclass(...)
this.superInterfaces = Binding.NO_SUPERINTERFACES;
this.permittedSubtypes = Binding.NO_PERMITTEDTYPES;
this.permittedTypes = Binding.NO_PERMITTEDTYPES;
this.typeVariables = Binding.NO_TYPE_VARIABLES;
this.memberTypes = Binding.NO_MEMBER_TYPES;
this.fields = Binding.NO_FIELDS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
Expand Down Expand Up @@ -1107,26 +1109,71 @@ public boolean isRawSubstitution() {

@Override
public ReferenceBinding[] permittedTypes() {
ReferenceBinding[] permTypes = this.type.permittedTypes();
List<ReferenceBinding> applicablePermTypes = new ArrayList<>();
for (ReferenceBinding pt : permTypes) {
ReferenceBinding permittedTypeAvatar = pt;
if (pt.isRawType()) {
ReferenceBinding ptRef = pt.actualType();
ReferenceBinding enclosingType1 = ptRef.enclosingType();
if (enclosingType1 != null) {
// don't use TypeSystem.getParameterizedType below as this is just for temporary check.
ParameterizedTypeBinding ptb = new ParameterizedTypeBinding(ptRef, this.arguments, ptRef.enclosingType(), this.environment);
ptb.superclass();
ptb.superInterfaces();
permittedTypeAvatar = ptb;
List<ReferenceBinding> permittedTypes = new ArrayList<>();
NextPermittedType:
for (ReferenceBinding pt : this.type.permittedTypes()) {
// Step 1: Gather all type variables that would need to be solved.
Map<TypeVariableBinding, TypeBinding> map = new HashMap<>();
TypeBinding current = pt;
do {
if (current.kind() == Binding.GENERIC_TYPE) {
for (TypeVariableBinding tvb : current.typeVariables()) {
map.put(tvb, null);
}
}
}
if (permittedTypeAvatar.isCompatibleWith(this))
applicablePermTypes.add(pt);
current = current.enclosingType();
} while (current != null);

// Step 2: Collect substitutes
current = this;
TypeBinding sooper = pt.findSuperTypeOriginatingFrom(this);
do {
if (sooper.isParameterizedType()) {
if (current.isParameterizedType()) {
for (int i = 0, length = sooper.typeArguments().length; i < length; i++) {
TypeBinding t = sooper.typeArguments()[i];
if (t instanceof TypeVariableBinding tvb) {
map.put(tvb, current.typeArguments()[i]);
} else if (TypeBinding.notEquals(t, this.typeArguments()[i])) {
continue NextPermittedType;
}
}
}
}
current = current.enclosingType();
sooper = sooper.enclosingType();
} while (current != null);

Substitution substitution = new Substitution() {
@Override
public LookupEnvironment environment() {
return ParameterizedTypeBinding.this.environment;
}
@Override
public boolean isRawSubstitution() {
return false;
}
@Override
public TypeBinding substitute(TypeVariableBinding typeVariable) {
TypeBinding retVal = map.get(typeVariable.unannotated());
if (retVal == null) {
retVal = ParameterizedTypeBinding.this.environment.createWildcard((ReferenceBinding) typeVariable.declaringElement, typeVariable.rank, null, null, Wildcard.UNBOUND);
map.put(typeVariable, retVal);
}
return retVal;
}
};

// Step 3: compute subtype with parameterizations if any.
pt = (ReferenceBinding) Scope.substitute(substitution, pt);

if (pt.isCompatibleWith(this))
permittedTypes.add(pt);
}
return applicablePermTypes.toArray(new ReferenceBinding[0]);

return permittedTypes.toArray(new ReferenceBinding[0]);
}

@Override
public TypeBinding unannotated() {
return this.hasTypeAnnotations() ? this.environment.getUnannotatedType(this) : this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2104,11 +2104,6 @@ public ReferenceBinding superclass() {
return null;
}

@Override
public ReferenceBinding[] permittedTypes() {
return Binding.NO_PERMITTEDTYPES;
}

@Override
public ReferenceBinding[] superInterfaces() {
return Binding.NO_SUPERINTERFACES;
Expand Down
Loading

0 comments on commit b88a5b3

Please sign in to comment.