spoon icon indicating copy to clipboard operation
spoon copied to clipboard

Generics Type Variable Resolution

Open mgood7123 opened this issue 1 year ago • 2 comments

consider the following

class Generic <T> {
    public T field;
}

class UseGeneric extends Generic<String> {
    public Generic<Integer> f;
}

is there a way, via TypeFactory (reflection), to obtain UseGeneric.field with T resolved to the correct type (String)?

the intent is for type resolution of Generics without knowing in advanced what the Type Variable actually is, but being infered from some root class such as UseGeneric

eg

UseGeneric would resolve

UseGeneric u = new UseGeneric();
u.field == String
u.f.field == Integer

either by supplying UseGeneric.class or by converting UseGeneric.class into abstract source code (with no decompilation of method bodies as we do not need them) which is then analyzed by spoon's java code type analyser to obtain correct types

mgood7123 avatar Mar 04 '23 14:03 mgood7123

the entire class and dependant classes/types can be converted into abstract source code via

CtType<Object> objectCtType = new TypeFactory().get(UseGeneric.class);
List<? extends CtType<?>> collect = objectCtType.getReferencedTypes().parallelStream()
        // reduce generics to single declaration
        //   toString replaces Foo<String> with Foo<T> leading to multiple declarations of class Foo<T>
        .map(ctTypeReference -> ctTypeReference.getTypeDeclaration().getReference())
        .distinct()
        .map(CtTypeReference::getTypeDeclaration)
        .collect(Collectors.toList());
for (CtType<?> ctType : collect) {
    println("TYPE:\n" + ctType);
}

which outputs (without the map(CtTypeReference::getTypeDeclaration))

TYPE:
java.lang.Float
TYPE:
java.lang.Integer
TYPE:
void
TYPE:
java.lang.String
TYPE:
smallville7123.Main.UseGeneric
TYPE:
smallville7123.Main.Generic

(with the map(CtTypeReference::getTypeDeclaration))

TYPE:
@jdk.internal.ValueBased
public final class Float extends java.lang.Number implements java.lang.constant.ConstantDesc , java.lang.Comparable<java.lang.Float> , java.lang.constant.Constable {
    @java.lang.Deprecated(forRemoval = true, since = "9")
    public Float(java.lang.String arg0) throws java.lang.NumberFormatException {
    }

    @java.lang.Deprecated(forRemoval = true, since = "9")
    public Float(double arg0) {
    }

// ...


    public boolean isInfinite() {
    }

    public static boolean isInfinite(float arg0) {
    }

    public static boolean isFinite(float arg0) {
    }

    public static final float POSITIVE_INFINITY = InfinityF;

    public static final float NEGATIVE_INFINITY = -InfinityF;

    public static final float NaN = NaNF;

    public static final float MAX_VALUE = 3.4028235E38F;

    public static final float MIN_NORMAL = 1.17549435E-38F;

    public static final float MIN_VALUE = 1.4E-45F;

    public static final int MAX_EXPONENT = 127;

    public static final int MIN_EXPONENT = -126;

    public static final int SIZE = 32;

    public static final int BYTES = 4;

    public static final java.lang.Class<java.lang.Float> TYPE;

    private final float value;

    private static final long serialVersionUID;
}
TYPE:
@jdk.internal.ValueBased
public final class Integer extends java.lang.Number implements java.lang.constant.ConstantDesc , java.lang.Comparable<java.lang.Integer> , java.lang.constant.Constable {
    @java.lang.Deprecated(forRemoval = true, since = "9")
    public Integer(java.lang.String arg0) throws java.lang.NumberFormatException {
    }

    @java.lang.Deprecated(forRemoval = true, since = "9")
    public Integer(int arg0) {
    }

    @jdk.internal.vm.annotation.IntrinsicCandidate
    public static int numberOfLeadingZeros(int arg0) {
    }

// and so on

unfortunately it does not correctly stub it (as return types require actual returns, such as return null or return 0)

but still, it should be sufficent

mgood7123 avatar Mar 05 '23 02:03 mgood7123

and we can collect all types including superclasses and interfaces by searching the types recursively

    private static List<?> collectTypesRecursive(Class<?> clazz) {
        CtType<Object> objectCtType = getCtType(clazz);
        List<CtTypeReference<?>> objects = new ArrayList<>();
        collectTypesRecursive(objectCtType, objects, 0);
        return objects.stream().map(CtTypeReference::getTypeDeclaration).collect(Collectors.toList());
    }

    private static void collectTypesRecursive(CtType<?> objectCtType, List<CtTypeReference<?>> collected, int depth) {
        if (objectCtType == null) return;

        Set<CtTypeReference<?>> collect = objectCtType.getReferencedTypes();

        // remove already collected
        Set<CtTypeReference<?>> tmp = new HashSet<>();
        for (CtTypeReference<?> ctTypeReference : collect) {
            boolean f = false;
            for (CtTypeReference<?> typeReference : collected) {
                String targ = ctTypeReference.getQualifiedName();
                String coll = typeReference.getQualifiedName();
                if (targ.contentEquals(coll)) {
                    f = true;
                }
            }
            if (!f) {
                tmp.add(ctTypeReference);
            }
        }

        // remove duplicates
        Set<CtTypeReference<?>> tmp1 = new HashSet<>();
        for (CtTypeReference<?> ctTypeReference : tmp) {
            boolean f = false;
            for (CtTypeReference<?> typeReference : tmp1) {
                String targ = ctTypeReference.getQualifiedName();
                String coll = typeReference.getQualifiedName();
                if (targ.contentEquals(coll)) {
                    f = true;
                }
            }
            if (!f) {
                tmp1.add(ctTypeReference);
            }
        }
        collect = tmp1;

        // collect whatever is left
        if (collect.size() == 0) {
            return;
        }

        collected.addAll(collect);

        println("COLLECTING: " + objectCtType.getReference().getQualifiedName());

        for (CtTypeReference<?> ctType : collect) {
            collectTypesRecursive(ctType.getTypeDeclaration(), collected, depth+1);
        }
    }

    private static CtType<Object> getCtType(Class<?> clazz) {
        return new TypeFactory().get(clazz);
    }

mgood7123 avatar Mar 05 '23 03:03 mgood7123