Affects: All versions

I am using SpEL in a platform I have built where users are able to set expressions which refer to dynamic types also created by them.

So, for example, consider the following structure is available at evaluation runtime:

public class Type implements java.lang.reflect.Type {
}
public class Object extends Type {

    private String name;
    private List<ObjectProperty> properties = new ArrayList<>();

and

public class ObjectProperty {

    private String name;
    private Type type;
public class EnumType extends Type {

    private final String name;
    private final List<String> values;

And, for example, the following dynamic types defined (defined statically in this example but created by user at runtime via a UI):

    private static final EnumType genre = new EnumType("Genre", List.of("FICTION", "STORY_TELLING", "DRAMA"));
    private static final Object authorType = new Object("Author",List.of(
            new ObjectProperty("id", new BasicType(Long.class)),
            new ObjectProperty("name", new BasicType(String.class))
    ));
    private static final Object bookType = new Object("Book",
            List.of(
                    new ObjectProperty("id", new BasicType(Long.class)),
                    new ObjectProperty("name", new BasicType(String.class)),
                    new ObjectProperty("author", authorType),
                    new ObjectProperty("genre", genre)
            ));
SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
Expression expression = spelExpressionParser.parseExpression("#book");

StandardEvaluationContextcontext = new StandardEvaluationContext();
context.setVariable("book", bookType);

return expression.getValueTypeDescriptor(context);

I would want this to return a TypeDescriptorthat describes the Book dynamic Type, but unfortunately it does not due to the implementation in VariableReference:

@Override
    public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
        if (this.name.equals(THIS)) {
            return state.getActiveContextObject();
        }
        if (this.name.equals(ROOT)) {
            TypedValue result = state.getRootContextObject();
            this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(result.getValue());
            return result;
        }
        TypedValue result = state.lookupVariable(this.name);
//////// Perhaps I misunderstand something, but shouldn't the exitTypeDescriptor be taken
//////// directly from result.typeDescriptor?
        Object value = result.getValue();
        if (value == null || !Modifier.isPublic(value.getClass().getModifiers())) {
            // If the type is not public then when generateCode produces a checkcast to it
            // then an IllegalAccessError will occur.
            // If resorting to Object isn't sufficient, the hierarchy could be traversed for
            // the first public type.
            this.exitTypeDescriptor = "Ljava/lang/Object";
        }
        else {
            this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(value);
        }
        // a null value will mean either the value was null or the variable was not found
        return result;
    }

There is also the issue of TypeLocator not supporting java.lang.reflect.Type but rather java.lang.Class directly. This would help as I could do:

if (type instanceof Object object) {
    return new TypedValue(object, new TypeDescriptor(ResolvableType.forType(object), null, null));
}

The use case being this expression: T(Genre).FICTION.

I was able to get all this working using extenders and reflection hacking.

Comment From: sbrannen

java //////// Perhaps I misunderstand something, but shouldn't the exitTypeDescriptor be taken //////// directly from result.typeDescriptor?

No.

result.typeDescriptor is a TypeDescriptor; whereas, exitTypeDescriptor is a String which is included in the byte code generated during expression compilation.

Comment From: sbrannen

Hi @asafbennatan,

Congratulations on submitting your first proposal for the Spring Framework! 👍

That's certainly an interesting idea; however, support for dynamic type systems is beyond the scope of the Spring Expression Language (SpEL).

In light of that, I am closing this issue.