Skip to content

Commit

Permalink
Add support for enclosing class types
Browse files Browse the repository at this point in the history
Reviewed-by: psandoz
  • Loading branch information
mcimadamore committed May 31, 2024
1 parent 7c83887 commit ba4e9a1
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,17 @@ public static TypeElement.ExternalizedTypeElement parseExTypeElem(Lexer l) {
t = l.token();
}
} else {
// Qualified identifier
// type element identifier
Tokens.Token t = l.accept(TokenKind.IDENTIFIER,
TokenKind.PLUS, TokenKind.SUB);
TokenKind.PLUS, TokenKind.SUB, TokenKind.DOT);
identifier.append(t.kind == TokenKind.IDENTIFIER ? t.name() : t.kind.name);
while (l.acceptIf(Tokens.TokenKind.DOT)) {
identifier.append(Tokens.TokenKind.DOT.name);
t = l.accept(Tokens.TokenKind.IDENTIFIER);
identifier.append(t.name());
if (t.kind == TokenKind.IDENTIFIER) {
// keep going if we see "."
while (l.acceptIf(Tokens.TokenKind.DOT)) {
identifier.append(Tokens.TokenKind.DOT.name);
t = l.accept(Tokens.TokenKind.IDENTIFIER);
identifier.append(t.name());
}
}
}

Expand All @@ -122,8 +125,6 @@ public static TypeElement.ExternalizedTypeElement parseExTypeElem(Lexer l) {
args = List.of();
}

// @@@ Enclosed/inner classes, separated by $ which may also be parameterized

// Parse array-like syntax []+
int dims = 0;
while (l.acceptIf(Tokens.TokenKind.LBRACKET)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,26 @@
* A class type.
*/
public final class ClassType implements TypeVarRef.Owner, JavaType {
// Enclosing class type (might be null)
private final ClassType enclosing;
// Fully qualified name
private final ClassDesc type;

private final List<JavaType> typeArguments;

ClassType(ClassDesc type) {
this(type, List.of());
this(null, type);
}

ClassType(ClassDesc type, List<JavaType> typeArguments) {
ClassType(ClassType encl, ClassDesc type) {
this(encl, type, List.of());
}

ClassType(ClassType encl, ClassDesc type, List<JavaType> typeArguments) {
if (!type.isClassOrInterface()) {
throw new IllegalArgumentException("Invalid base type: " + type);
}
this.enclosing = encl;
this.type = type;
this.typeArguments = List.copyOf(typeArguments);
}
Expand All @@ -63,14 +70,16 @@ public Type resolve(Lookup lookup) throws ReflectiveOperationException {
for (JavaType typearg : typeArguments) {
resolvedTypeArgs.add(typearg.resolve(lookup));
}
Type encl = enclosing != null ?
enclosing.resolve(lookup) : null;
return resolvedTypeArgs.isEmpty() ?
baseType :
makeReflectiveParameterizedType(baseType,
resolvedTypeArgs.toArray(new Type[0]), baseType.getDeclaringClass()); // @@@: generic owner is erased here
resolvedTypeArgs.toArray(new Type[0]), encl);
}

// Copied code in jdk.compiler module throws UOE
private static ParameterizedType makeReflectiveParameterizedType(Class<?> base, Type[] typeArgs, Class<?> owner) {
private static ParameterizedType makeReflectiveParameterizedType(Class<?> base, Type[] typeArgs, Type owner) {
/*__throw new UnsupportedOperationException();__*/ return sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.make(base, typeArgs, owner);
}

Expand All @@ -81,6 +90,9 @@ public ExternalizedTypeElement externalize() {
.toList();

ExternalizedTypeElement td = new ExternalizedTypeElement(toClassName(), args);
if (enclosing != null) {
td = new ExternalizedTypeElement(".", List.of(enclosing.externalize(), td));
}
return td;
}

Expand Down Expand Up @@ -156,6 +168,13 @@ public List<JavaType> typeArguments() {
return typeArguments;
}

/**
* {@return the enclosing type associated with this class type (if any)}
*/
public Optional<ClassType> enclosingType() {
return Optional.ofNullable(enclosing);
}

@Override
public JavaType toBasicType() {
return JavaType.J_L_OBJECT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import java.lang.constant.ClassDesc;
import java.lang.reflect.code.TypeElement;
import java.lang.reflect.code.TypeElement.ExternalizedTypeElement;
import java.lang.reflect.code.type.WildcardType.BoundKind;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public final class CoreTypeFactory {

Expand Down Expand Up @@ -90,72 +92,105 @@ public TypeElement constructType(TypeElement.ExternalizedTypeElement tree) {
*/
public static final TypeElementFactory JAVA_TYPE_FACTORY = new TypeElementFactory() {
@Override
public TypeElement constructType(TypeElement.ExternalizedTypeElement tree) {
public JavaType constructType(TypeElement.ExternalizedTypeElement tree) {
String identifier = tree.identifier();
int dimensions = 0;

if (identifier.startsWith("[")) {
// Array types are "flattened". Skip over '[', but keep track of them in 'dimensions'
if (tree.arguments().size() != 1) {
throw new IllegalArgumentException("Bad type: " + tree);
throw badType(tree, "array type");
}
for (int i = 1; i < identifier.length(); i++) {
if (identifier.charAt(i) != '[') {
throw new IllegalArgumentException("Bad type: " + tree);
throw badType(tree, "array type");
}
}
dimensions = identifier.length();
tree = tree.arguments().getFirst();
identifier = tree.identifier();
}

List<JavaType> typeArguments = new ArrayList<>(tree.arguments().size());
for (TypeElement.ExternalizedTypeElement child : tree.arguments()) {
TypeElement t = JAVA_TYPE_FACTORY.constructType(child);
if (!(t instanceof JavaType a)) {
throw new IllegalArgumentException("Bad type: " + tree);
}
typeArguments.add(a);
}
if (identifier.equals("+") || identifier.equals("-")) {
JavaType elemType = constructType(tree.arguments().getFirst());
return JavaType.array(elemType, identifier.length());
} else if (identifier.equals("+") || identifier.equals("-")) {
// wildcard type
if (tree.arguments().size() != 1) {
throw badType(tree, "wildcard type argument");
}
BoundKind kind = identifier.equals("+") ?
BoundKind.EXTENDS : BoundKind.SUPER;
return JavaType.wildcard(kind, typeArguments.get(0));
return JavaType.wildcard(kind,
constructTypeArgument(tree, 0, NO_WILDCARDS));
} else if (identifier.startsWith("#")) {
// type-var
if (typeArguments.size() != 1) {
throw new IllegalArgumentException("Bad type-variable bounds: " + tree);
if (tree.arguments().size() != 1) {
throw badType(tree, "type variable");
}
String[] parts = identifier.substring(1).split("::");
if (parts.length == 2) {
// class type-var
return JavaType.typeVarRef(parts[1],
(ClassType)constructType(parseExTypeElem(parts[0])),
typeArguments.get(0));
} else {
constructTypeArgument(tree, 0, NO_WILDCARDS));
} else if (parts.length == 3) {
// method type-var
return JavaType.typeVarRef(parts[2],
parseMethodRef(String.format("%s::%s", parts[0], parts[1])),
typeArguments.get(0));
constructTypeArgument(tree, 0, NO_WILDCARDS));
} else {
throw badType(tree, "type variable");
}
} else if (identifier.equals(".")) {
// qualified type
if (tree.arguments().size() != 2) {
throw badType(tree, "qualified type");
}
ClassType enclType = (ClassType)constructTypeArgument(tree, 0, CLASS);
ClassType innerType = (ClassType)constructTypeArgument(tree, 1, CLASS);
// the inner class name is obtained by subtracting the name of the enclosing type
// from the name of the inner type (and also dropping an extra '$')
String innerName = innerType.toNominalDescriptor().displayName()
.substring(enclType.toNominalDescriptor().displayName().length() + 1);
JavaType qual = JavaType.qualified(enclType, innerName);
return (innerType.hasTypeArguments()) ?
JavaType.parameterized(qual, innerType.typeArguments()) : qual;
} else {
// primitive or reference
JavaType t = switch (identifier) {
case "boolean" -> JavaType.BOOLEAN;
case "byte" -> JavaType.BYTE;
case "char" -> JavaType.CHAR;
case "short" -> JavaType.SHORT;
case "int" -> JavaType.INT;
case "long" -> JavaType.LONG;
case "float" -> JavaType.FLOAT;
case "double" -> JavaType.DOUBLE;
case "void" -> JavaType.VOID;
default -> JavaType.type(ClassDesc.of(identifier));
};
if (!tree.arguments().isEmpty()) {
if (t instanceof PrimitiveType) {
throw new IllegalArgumentException("primitive type: " + tree);
}
return JavaType.parameterized(t,
tree.arguments().stream().map(this::constructType).toList());
} else {
return t;
}
}
JavaType t = switch (identifier) {
case "boolean" -> JavaType.BOOLEAN;
case "byte" -> JavaType.BYTE;
case "char" -> JavaType.CHAR;
case "short" -> JavaType.SHORT;
case "int" -> JavaType.INT;
case "long" -> JavaType.LONG;
case "float" -> JavaType.FLOAT;
case "double" -> JavaType.DOUBLE;
case "void" -> JavaType.VOID;
default -> JavaType.type(ClassDesc.of(identifier));
};
if (!typeArguments.isEmpty()) {
t = JavaType.parameterized(t, typeArguments);
}

static IllegalArgumentException badType(ExternalizedTypeElement tree, String str) {
return new IllegalArgumentException(String.format("Bad %s: %s", str, tree));
}

private JavaType constructTypeArgument(ExternalizedTypeElement element, int index, Predicate<JavaType> filter) {
ExternalizedTypeElement arg = element.arguments().get(index);
JavaType type = constructType(arg);
if (!filter.test(type)) {
throw new IllegalArgumentException(String.format("Unexpected argument %s", element));
} else {
return type;
}
return dimensions == 0 ?
t : JavaType.array(t, dimensions);
}

private static Predicate<JavaType> NO_WILDCARDS = t -> !(t instanceof WildcardType);
private static Predicate<JavaType> CLASS = t -> t instanceof ClassType;
};


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,11 @@ public sealed interface JavaType extends TypeElement permits ClassType, ArrayTyp
static JavaType type(Type reflectiveType) {
return switch (reflectiveType) {
case Class<?> c -> type(c.describeConstable().get());
case ParameterizedType pt -> parameterized(type(pt.getRawType()),
case ParameterizedType pt when pt.getOwnerType() == null -> parameterized(type(pt.getRawType()),
Stream.of(pt.getActualTypeArguments()).map(JavaType::type).toList());
case ParameterizedType pt -> parameterized(
qualified(type(pt.getOwnerType()), ((Class<?>)pt.getRawType()).getSimpleName()),
Stream.of(pt.getActualTypeArguments()).map(JavaType::type).toList());
case java.lang.reflect.WildcardType wt -> wt.getLowerBounds().length == 0 ?
wildcard(BoundKind.EXTENDS, type(wt.getUpperBounds()[0])) :
wildcard(BoundKind.SUPER, type(wt.getLowerBounds()[0]));
Expand Down Expand Up @@ -236,10 +239,24 @@ static JavaType type(ClassDesc desc) {
return array(type(desc.componentType()));
} else {
// class
return new ClassType(desc, List.of());
return new ClassType(null, desc, List.of());
}
}

/**
* Constructs a qualified Java type.
*
* @param enclosing the enclosing type
* @param nestedName the nested class name
* @throws IllegalArgumentException if {@code enclosing} is not a class type
*/
static ClassType qualified(JavaType enclosing, String nestedName) {
if (!(enclosing instanceof ClassType ct)) {
throw new IllegalArgumentException("Not a class type: " + enclosing);
}
return new ClassType(ct, ct.toNominalDescriptor().nested(nestedName));
}

/**
* Constructs a parameterized class type.
*
Expand Down Expand Up @@ -271,7 +288,7 @@ static ClassType parameterized(JavaType type, List<JavaType> typeArguments) {
if (ct.hasTypeArguments()) {
throw new IllegalArgumentException("Already parameterized: " + type);
}
return new ClassType(type.toNominalDescriptor(), typeArguments);
return new ClassType(ct.enclosingType().orElse(null), type.toNominalDescriptor(), typeArguments);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2271,8 +2271,14 @@ JavaType typeToTypeElement(Type t) {
typeToTypeElement(t.getUpperBound()));
case CLASS -> {
Assert.check(!t.isIntersection() && !t.isUnion());
// @@@ Need to clean this up, probably does not work inner generic classes
// whose enclosing class is also generic
JavaType typ;
if (t.getEnclosingType() != Type.noType) {
Name innerName = t.tsym.flatName().subName(t.getEnclosingType().tsym.flatName().length() + 1);
typ = JavaType.qualified(typeToTypeElement(t.getEnclosingType()), innerName.toString());
} else {
typ = JavaType.type(ClassDesc.of(t.tsym.flatName().toString()));
}

List<JavaType> typeArguments;
if (t.getTypeArguments().nonEmpty()) {
typeArguments = new ArrayList<>();
Expand All @@ -2284,7 +2290,7 @@ JavaType typeToTypeElement(Type t) {
}

// Use flat name to ensure demarcation of nested classes
yield JavaType.parameterized(JavaType.type(ClassDesc.of(t.tsym.flatName().toString())), typeArguments);
yield JavaType.parameterized(typ, typeArguments);
}
default -> {
throw new UnsupportedOperationException("Unsupported type: kind=" + t.getKind() + " type=" + t);
Expand Down
Loading

0 comments on commit ba4e9a1

Please sign in to comment.