Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for more types #51

Closed
wants to merge 14 commits into from
Original file line number Diff line number Diff line change
@@ -25,6 +25,8 @@

package java.lang.reflect.code.parser.impl;

import java.lang.reflect.code.parser.impl.Tokens.Token;
import java.lang.reflect.code.parser.impl.Tokens.TokenKind;
import java.lang.reflect.code.type.*;
import java.lang.reflect.code.TypeElement;
import java.lang.reflect.code.type.RecordTypeRef;
@@ -33,6 +35,7 @@
import java.lang.reflect.code.type.impl.RecordTypeRefImpl;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public final class DescParser {
private DescParser() {}
@@ -85,14 +88,26 @@ public static RecordTypeRef parseRecordTypeRef(String desc) {
}

public static TypeDefinition parseTypeDefinition(Lexer l) {
// Type
Tokens.Token t = l.accept(Tokens.TokenKind.IDENTIFIER);
StringBuilder identifier = new StringBuilder();
identifier.append(t.name());
while (l.acceptIf(Tokens.TokenKind.DOT)) {
identifier.append(Tokens.TokenKind.DOT.name);
t = l.accept(Tokens.TokenKind.IDENTIFIER);
identifier.append(t.name());
if (l.token().kind == TokenKind.HASH) {
// Quoted identifier
l.accept(TokenKind.HASH);
Token t = l.token();
while (t.kind != TokenKind.LT) {
identifier.append(t.kind == TokenKind.IDENTIFIER ? t.name() : t.kind.name);
l.nextToken();
t = l.token();
}
} else {
// Qualified identifier
Tokens.Token t = l.accept(TokenKind.IDENTIFIER,
TokenKind.PLUS, TokenKind.SUB);
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());
}
}

// Type parameters
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@

package java.lang.reflect.code.parser.impl;

import java.lang.reflect.code.parser.impl.Tokens.TokenKind;
import java.util.ArrayList;
import java.util.List;

@@ -860,6 +861,11 @@ public Tokens.Token readToken() {
tk = Tokens.TokenKind.GT;
break loop;

case '#':
next();
tk = TokenKind.HASH;
break loop;

case '+':
next();
tk = Tokens.TokenKind.PLUS;
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@
package java.lang.reflect.code.parser.impl;

import java.lang.reflect.code.parser.impl.Position.LineMap;
import java.lang.reflect.code.parser.impl.Tokens.Token;
import java.util.Arrays;

/**
* The lexical analyzer maps an input stream consisting of ASCII
@@ -99,6 +101,21 @@ default Tokens.Token accept(Tokens.TokenKind tk) {
}
}

default Tokens.Token accept(Tokens.TokenKind... tks) {
Token t = token();
for (Tokens.TokenKind tk : tks) {
if (acceptIf(tk)) {
return t;
}
}
// @@@ Exception
LineMap lineMap = getLineMap();
int lineNumber = lineMap.getLineNumber(t.pos);
int columnNumber = lineMap.getColumnNumber(t.pos);
throw new IllegalArgumentException("Expected one of " + Arrays.toString(tks) + " but observed " + t.kind +
" " + lineNumber + ":" + columnNumber);
}

default boolean acceptIf(Tokens.TokenKind tk) {
Tokens.Token t = token();
if (t.kind == tk) {
Original file line number Diff line number Diff line change
@@ -101,6 +101,7 @@ public enum TokenKind implements Predicate<TokenKind> {
AMP("&"),
CARET("^"),
MONKEYS_AT("@"),
HASH("#"),
CUSTOM;

public final String name;
Original file line number Diff line number Diff line change
@@ -85,6 +85,11 @@ public int hashCode() {
return 17 * componentType.hashCode();
}

@Override
public JavaType erasure() {
return JavaType.array(componentType.erasure());
}

@Override
public JavaType toBasicType() {
return JavaType.J_L_OBJECT;
Original file line number Diff line number Diff line change
@@ -84,6 +84,11 @@ public int hashCode() {
return result;
}

@Override
public JavaType erasure() {
return rawType();
}

@Override
public boolean isArray() {
return false;
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

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

@@ -114,6 +115,29 @@ public TypeElement constructType(TypeDefinition tree) {
}
typeArguments.add(a);
}
if (identifier.equals("+") || identifier.equals("-")) {
// wildcard type
BoundKind kind = identifier.equals("+") ?
BoundKind.EXTENDS : BoundKind.SUPER;
return JavaType.wildcard(kind, typeArguments.get(0));
} else if (identifier.startsWith("#")) {
// type-var
if (typeArguments.size() != 1) {
throw new IllegalArgumentException("Bad type-variable bounds: " + tree);
}
String[] parts = identifier.split("::");
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this is the duplicate parsing logic (although here we already know if it's a method or a class type-variable based on the number of ::)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if instead we can check #, and the parsers job is dumbly accumulate all valid characters (selected tokens and identifiers) up to but not including the < token. We could even check if there is quoted string for the type identifier.

Note the special code for arrays in the parser was added only to avoid updating many tests.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can take a look

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uploaded a new iteration with this simplification (which looks much nicer than what I had):

8ad6110

Note that if we wanted a truly general "quoting" mechanism we'd need both a prefix and a suffix token. Otherwise one can only use quotes if there's some nested type-definition with <>. Your idea of using just strings (e.g. surrounded with ") seems a powerful one (and more robust in the long run), because it would make the desc parsing logic a lot less opinionated (e.g. we wouldn't even need to special case qualified identifiers).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's much simpler. We can iterate further afterwards if need be. I believe you can now replace identifier.contains("::") with identifier.startsWith("#")?

if (parts.length == 2) {
// class type-var
return JavaType.typeVarRef(parts[1],
(JavaType)constructType(parseTypeDef(parts[0])),
typeArguments.get(0));
} else {
// method type-var
return JavaType.typeVarRef(parts[2],
parseMethodRef(String.format("%s::%s", parts[0], parts[1])),
typeArguments.get(0));
}
}
JavaType t = switch (identifier) {
case "boolean" -> JavaType.BOOLEAN;
case "byte" -> JavaType.BYTE;
@@ -141,4 +165,14 @@ public TypeElement constructType(TypeDefinition tree) {
* may contain instances of those types.
*/
public static final TypeElementFactory CORE_TYPE_FACTORY = codeModelTypeFactory(JAVA_TYPE_FACTORY);

// Copied code in jdk.compiler module throws UOE
static MethodRef parseMethodRef(String desc) {
/*__throw new UnsupportedOperationException();__*/ return java.lang.reflect.code.parser.impl.DescParser.parseMethodRef(desc);
}

// Copied code in jdk.compiler module throws UOE
static TypeDefinition parseTypeDef(String desc) {
/*__throw new UnsupportedOperationException();__*/ return java.lang.reflect.code.parser.impl.DescParser.parseTypeDefinition(desc);
}
}
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@
import java.lang.constant.ClassDesc;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.code.TypeElement;
import java.lang.reflect.code.type.WildcardType.BoundKind;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -36,7 +37,8 @@
* The symbolic description of a Java type.
*/
// @@@ Extend from this interface to model Java types with more fidelity
public sealed interface JavaType extends TypeElement permits ClassType, ArrayType, PrimitiveType {
public sealed interface JavaType extends TypeElement permits ClassType, ArrayType,
PrimitiveType, WildcardType, TypeVarRef {

// @@@ Share with general void type?
JavaType VOID = new PrimitiveType("void");
@@ -147,6 +149,11 @@ default Class<?> resolve(MethodHandles.Lookup l) throws ReflectiveOperationExcep

boolean isPrimitive();

/**
* {@return the erasure of this Java type, as per JLS 4.6}
*/
JavaType erasure();

// Factories

static JavaType type(Class<?> c) {
@@ -254,6 +261,46 @@ static ArrayType array(JavaType elementType, int dims) {
return array(elementType);
}

/**
* Constructs an unbounded wildcard type.
*
* @return an unbounded wildcard type.
*/
static WildcardType wildcard() {
return new WildcardType(BoundKind.EXTENDS, JavaType.J_L_OBJECT);
}

/**
* Constructs a bounded wildcard type of the given kind.
*
* @return a bounded wildcard type.
*/
static WildcardType wildcard(BoundKind kind, JavaType bound) {
return new WildcardType(kind, bound);
}

/**
* Constructs a reference to a class type-variable.
*
* @param bound the type-variable bound.
* @param owner the class where the type-variable is declared.
* @return a type-variable reference.
*/
static TypeVarRef typeVarRef(String name, JavaType owner, JavaType bound) {
return new TypeVarRef(name, owner, bound);
}

/**
* Constructs a reference to a method type-variable.
*
* @param bound the type-variable bound.
* @param owner the method where the type-variable is declared.
* @return a type-variable reference.
*/
static TypeVarRef typeVarRef(String name, MethodRef owner, JavaType bound) {
return new TypeVarRef(name, owner, bound);
}

// Copied code in jdk.compiler module throws UOE
static JavaType ofString(String s) {
/*__throw new UnsupportedOperationException();__*/ return (JavaType) CoreTypeFactory.JAVA_TYPE_FACTORY.constructType(java.lang.reflect.code.parser.impl.DescParser.parseTypeDefinition(s));
Original file line number Diff line number Diff line change
@@ -64,6 +64,11 @@ public int hashCode() {
return type.hashCode();
}

@Override
public JavaType erasure() {
return this;
}

@Override
public boolean isArray() {
return false;
Loading