Skip to content

Implement witness discovery at the module level #27

Description

@bowbahdoe

I am leaving this here to nerd snipe you into trying it out as a prototype: Right now you discover witnesses by looking at annotated methods in the types involved in a generic expression.

What if, instead, you discovered witnesses via the service provider mechanism?

package dev.mccue.types;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public abstract class TypeProvider<For, Providing> {
    private final Type for_;
    private final Type providing;

    record TypePair(Type a, Type b) {
        TypePair(TypeProvider<?, ?> provider) {
            this(provider.for_, provider.providing);
        }

        TypePair(Ty<?, ?> ty) {
            this(ty.a(), ty.b());
        }
    }

    private static final ConcurrentHashMap<TypePair, Object> CACHE = new ConcurrentHashMap<>();
    private static final List<TypeProvider<?, ?>> PROVIDERS = findProviders();

    private static List<TypeProvider<?, ?>> findProviders() {
        var m = new HashSet<TypePair>();
        var providers = new ArrayList<TypeProvider<?, ?>>();
        ServiceLoader.load(TypeProvider.class)
                .forEach(provider -> {
                    if (!m.add(new TypePair(provider.for_, provider.providing))) {
                        throw new IllegalStateException("Found more than one provider providing " + provider.providing + " for " + provider.for_);
                    }
                    providers.add(provider);
                });
        return providers;
    }

    protected TypeProvider() {
        var thisClass = this.getClass();
        var superClass = thisClass.getGenericSuperclass();

        if (!(this.getClass().getSuperclass() == TypeProvider.class)) {
            throw new IllegalStateException("TypeProvider can only be extended one level.");
        }

        if (!(superClass instanceof ParameterizedType parameterizedSuperType)) {
            throw new IllegalStateException("TypeProvider must be given generic parameters");
        }

        var typeArguments = parameterizedSuperType.getActualTypeArguments();

        this.for_ = typeArguments[0];
        this.providing = typeArguments[1];
    }

    public abstract Providing provide(For for_);

    public Type forType() {
        return for_;
    }

    public Type providingType() {
        return providing;
    }

    @Override
    public String toString() {
        return "TypeProvider[for=" + for_ + ", providing=" + providing;
    }

    @SuppressWarnings("unchecked")
    public static <A, B> B provide(Ty<A, B> types, A instance) {
        return (B) CACHE.computeIfAbsent(new TypePair(types), _ -> {
            for (var provider : PROVIDERS) {
                if (provider.for_.equals(types.a())) {
                    return ((TypeProvider<A, B>) provider).provide(instance);
                }
            }

            throw new IllegalStateException("No TypeProvider found providing " + types.b() + " for " + types.a());
        });
    }
}
public class IntDisplay implements Display {
    private final int value;

    public IntDisplay(int value) {
        this.value = value;
    }

    @Override
    public void display() {
        IO.println(value);
    }
}
public class IntDisplayProvider extends TypeProvider<Integer, Display> {
    @Override
    public Display provide(Integer for_) {
        return new IntDisplay(for_);
    }
}
module dev.mccue.types {
    exports dev.mccue.types;
    exports dev.mccue.types.examples;

    provides TypeProvider with IntDisplayProvider;

    uses TypeProvider;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions