TypeFunction example creatiing a conversion matrix

Adam D. Ruppe destructionator at gmail.com
Thu Oct 1 23:02:58 UTC 2020


On Thursday, 1 October 2020 at 19:15:10 UTC, Andrei Alexandrescu 
wrote:
> Problem definition: Implement Variant.get correctly. :o)
>
> E.g. this produces a type error somewhere in the innards of 
> std.variant: https://run.dlang.io/is/joIbeV. I'm not sure how 
> many other cases are out there.


So here's my proof of concept that this is doable today:


---------------

import std.typecons; // for Rebindable

interface MyTypeInfo {
	const pure nothrow: // yadda yadda yada
	bool implicitlyConvertsTo(const MyTypeInfo t);
	bool isNullable();
	string toiString();
}

interface MyTypeInfoFunction {
	const pure nothrow:

	immutable(MyTypeInfo) returnType();
	immutable(MyTypeInfo)[] paramsTypes();
}

interface MyTypeInfoClass {
	const pure nothrow:

	immutable(MyTypeInfo)[] bases();
}

class MyTypeInfoImpl_FunctionPointer(T) : MyTypeInfo, 
MyTypeInfoFunction {
	const:
	bool isNullable() { return true; }
	string toiString() { return T.stringof; }

	static if(is(T == R function(Params), R, Params...)) {
		immutable(MyTypeInfo) returnType() {
			return mytypeid!R;
		}

		immutable(MyTypeInfo)[] paramsTypes() pure nothrow {
			import std.meta; // I wouldn't normally use this but meh it is 
a demo
			static immutable MyTypeInfo[] result = [staticMap!(mytypeid, 
Params)];

			return result[];
		}
	} else static assert(0, "mistake in " ~ T.stringof);

	bool implicitlyConvertsTo(const MyTypeInfo t) {
		if(this is t)
			return true;

		if(t is mytypeid!(typeof(null)))
			return true;

		auto fp = cast(MyTypeInfoFunction) t;
		if(fp is null)
			return false;

		if(!returnType.implicitlyConvertsTo(fp.returnType))
			return false;

		if(paramsTypes.length != fp.paramsTypes.length)
			return false;

		foreach(idx, arg; fp.paramsTypes)
			if(!arg.implicitlyConvertsTo(paramsTypes[idx]))
				return false;

		return true;
	}
}

class MyTypeInfoImpl_Class(T) : MyTypeInfo, MyTypeInfoClass {
	bool isNullable() const { return true; }
	string toiString() const { return T.stringof; }

	static if(is(T Super == super))
	immutable(MyTypeInfo)[] bases() const {
		import std.meta;
		static immutable MyTypeInfo[] result = [staticMap!(mytypeid, 
Super)];
		return result[];
	}

	bool implicitlyConvertsTo(const MyTypeInfo t) const {
		if(this is t)
			return true;

		if(t is mytypeid!(typeof(null)))
			return true;

		foreach(base; bases)
			if(t is base)
				return true;
		return false;
	}
}

class MyTypeInfoImpl(T) : MyTypeInfo {
	bool isNullable() const {
		return is(typeof(null) : T);
	}

	string toiString() const {
		return T.stringof;
	}

	bool implicitlyConvertsTo(const MyTypeInfo t) const {
		static if(is(T == typeof(null)))
			return t.isNullable();
		return this is t;
	}
}

template mytypeid(T) {
	static if(is(T == return))
		immutable MyTypeInfo mytypeid = new immutable 
MyTypeInfoImpl_FunctionPointer!T;
	else static if(is(T == class))
		immutable MyTypeInfo mytypeid = new immutable 
MyTypeInfoImpl_Class!T;
	else {
		//pragma(msg, "generic " ~ T.stringof);
		immutable MyTypeInfo mytypeid = new immutable MyTypeInfoImpl!T;
	}
}

struct MyVariant {
	this(T)(T t) {
		opAssign(t);
	}

	void opAssign(T)(T t) {
		storedType = mytypeid!T;

		// I know this impl sux but std.variant already solved it so 
not the point here.
		union SD {
			StoredData sd;
			T b;
		}
		SD sd;
		sd.b = t;
		storedData = sd.sd;
	}

	T get(T)() {
		if(storedType is null)
			static if(is(T : typeof(null)))
				return null;
			else
				throw new Exception("null cannot become " ~ T.stringof);
		if(storedType.implicitlyConvertsTo(mytypeid!(T))) {
			union SD {
				StoredData sd;
				T b;
			}
			SD sd;
			sd.sd = storedData;
			return sd.b;
		} else
			throw new Exception(storedType.toiString() ~ " cannot become " 
~ T.stringof);
	}

	Rebindable!(immutable(MyTypeInfo)) storedType;
	// std.variant already has a better impl of this
	struct StoredData {
		void* b1;
		void* b2;
	}
	StoredData storedData;
}


class A {}
class B : A {}
import std.stdio;
A func() { writeln("func A"); return null; }
B func2() { writeln("func2 B"); return null; }

void main() {
	MyVariant v = &func2;
	auto test = &func2;
	typeof(&func) test2 = test;
	A function() a = v.get!(typeof(&func));

	a();
	test2();
}

----------------


I understand that's a decent chunk of code and it is very 
incomplete - it is basically the minimum to achieve the 
challenge. And I think I missed up the contravariant parameter 
implementation there, I always forget the exact rule without 
looking it up. But nevertheless that's an implementation bug, not 
a language barrier.

I hope it shows that this isn't hopeless with today's D. All that 
compile time knowledge we need *is* available to Variant itself, 
it just needs to dig a little deeper to put it together.... and 
then reimplement the compiler's rules, hopefully correctly.

Of course it would be better if we could just reuse dmd's 
implementation! But *that* I don't think is possible because 
Variant crosses the runtime barrier.... even with a type function 
I think it'd need a *pointer* to a typefunction which again hits 
that RT/CT barrier.


More information about the Digitalmars-d mailing list