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 __totype(string) expressions #11797

Closed
wants to merge 1 commit into from

Conversation

WalterBright
Copy link
Member

This adds a new Type construct:

__totype("i") x;

where __totype takes as its argument a string that is a mangled type, and produces the corresponding type (in this case int), which can be used where-ever a Type is used.

This, in effect, closes the circle where .mangleof produces a string literal representing the type, and __typeof correspondingly converts the string back into a type. It's an obvious (in retrospect) building block in D's metaprogramming.

What's it good for? Quite simply, it enables a type to be treated as an expression, along with everything an expression can do. Being a compile-time construct, the expression handed to it is fully evaluated by CTFE. It has the potential to completely obsolete typeid and all the compiler magic that comes with it, i.e. a major simplification and a big boost in metaprogramming power.

This is a prototype for experimenting and for proof of concept. It's kinda shocking how little code is required for it.

@WalterBright WalterBright added the Needs Spec PR A PR updating the language specification needs to be submitted to dlang.org label Sep 26, 2020
@dlang-bot
Copy link
Contributor

Thanks for your pull request, @WalterBright!

Bugzilla references

Your PR doesn't reference any Bugzilla issue.

If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog.

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub run digger -- build "master + dmd#11797"

@thewilsonator thewilsonator added the Needs Changelog A changelog entry needs to be added to /changelog label Sep 26, 2020
@UplinkCoder
Copy link
Member

This is a polymorphic (shape changing) expression.

If you used this in a ctfe function, and declared a variable with the resulting type;
you change the stack frame size, which is what I call a shape change.

The shape of the memory or instruction layout changes.

@ibuclaw
Copy link
Member

ibuclaw commented Sep 26, 2020

Is there a bugzilla, dip, or any other reference available to track where this request came from?

Copy link
Member

@UplinkCoder UplinkCoder left a comment

Choose a reason for hiding this comment

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

This is introducing a polymorphic/ add a test which uses __totype within a function and depends on CTFE (runtime) argument, and you'll see it doesn't work.
(for the same reason that a string mixin depending on a runtime argument doesn't work)

It would be somewhat useless if wanting to remove dependence on recursive templates is the goal.

@andralex
Copy link
Member

It has the potential to completely obsolete typeid and all the compiler magic that comes with it, i.e. a major simplification and a big boost in metaprogramming power.

So... does that mean my beloved typeid is not salvageable? :o|

This is definitely something powerful for a budget of 172 lines. I have a question: in an AliasSeq one could put types, symbols, or expressions - e.g. AliasSeq!(int, xyz, 1+1). With mangleof and __totype we get the full circle for types. How about symbols and expressions though? I noticed that .stringof for symbols and expressions return that thing as a string, but I'm not sure it works in all cases (e.g. taking a symbol to another scope). What would it take to close the circle with symbols and expressions as well? Thanks.

@andralex
Copy link
Member

This is a fix for https://issues.dlang.org/show_bug.cgi?id=9945 it seems.

@quickfur
Copy link
Member

quickfur commented Sep 26, 2020

This is a polymorphic (shape changing) expression.

If you used this in a ctfe function, and declared a variable with the resulting type;
you change the stack frame size, which is what I call a shape change.

The shape of the memory or instruction layout changes.

Not really. The argument to __totype must be known at compile-time, that is, at template expansion time, so it cannot be a variable within the current function, but must be a fixed compile-time value. I.e., you cannot do this:

auto ctfeFunc(Args...) {
    string x = /* some string expression */;
    return __totype(x).init; // compile error: x cannot be read at [AST] compile-time
}

Instead, you have to do this:

string ctfeFunc(Args...) {
    string x = /* some complicated expression */;
    return __totype(x); // compile error: x cannot be read at [AST] compile-time
}
alias T = __totype(ctfeFunc(...)); // OK

(@UplinkCoder wanted to quote and accidentally edited the comment)
@quickfur please re-add.

@andralex
Copy link
Member

If you used this in a ctfe function, and declared a variable with the resulting type; you change the stack frame size, which is what I call a shape change.

That would be a real code generation bug that probably affects mixin code as well. Can you please give an example? Thanks.

@adamdruppe
Copy link
Contributor

i wanna follow this. it is basically just mixin that skips the import scopes but that's legit useful i think. but slight concern on dependency tracking and linker errors though; it might work in dmd, but not right with make and ld (well dmd -i really) and such exactly because those are more likely to follow the import path and thus not realize these dependencies necessarily even exist.

@UplinkCoder
Copy link
Member

This is a polymorphic (shape changing) expression.
If you used this in a ctfe function, and declared a variable with the resulting type;
you change the stack frame size, which is what I call a shape change.
The shape of the memory or instruction layout changes.

Not really. The argument to __totype must be known at compile-time, that is, at template expansion time, so it cannot be a variable within the current function, but must be a fixed compile-time value. I.e., you cannot do this:

auto ctfeFunc(Args...) {
    string x = /* some string expression */;
    return __totype(x).init; // compile error: x cannot be read at [AST] compile-time
}

Instead, you have to do this:

string ctfeFunc(Args...) {
    string x = /* some complicated expression */;
    return __totype(x); // compile error: x cannot be read at [AST] compile-time
}
alias T = __totype(ctfeFunc(...)); // OK

This is exactly what polymorphic is.
polymorphism in this case means => it has to happen at "template expansion time"

@UplinkCoder
Copy link
Member

If you used this in a ctfe function, and declared a variable with the resulting type; you change the stack frame size, which is what I call a shape change.

That would be a real code generation bug that probably affects mixin code as well. Can you please give an example? Thanks.

I can't give an example because it doesn't work.
I won't compile if you try that.

The compiler has semantics which include an invariant that ensures functions are mono-morphic.

@WalterBright
Copy link
Member Author

You're right, the string can't be used as a runtime argument to a function. But it can be used as a compile-time argument:

int foo(string s1, string s2)()
{
    __totype(s1) x = 4;
    mixin(s2) y = 3;
    return x + y;
}

enum i = foo!("i", "int")();

just like mixins.

@UplinkCoder
Copy link
Member

We will see how far you get with that.

It's a step.

@WalterBright WalterBright removed the Needs Changelog A changelog entry needs to be added to /changelog label Sep 27, 2020
@WalterBright WalterBright dismissed UplinkCoder’s stale review September 27, 2020 04:30

What change is requested?

@WalterBright
Copy link
Member Author

macOS 10.15 x64 heisenbug is a constant annoyance.

@WalterBright
Copy link
Member Author

macOS 10.15 passing now, proving it was another heisenbug, as no changes were made.

@AndrejMitrovic
Copy link
Contributor

and __typeof correspondingly converts the string back into a type

did you mean __totype?

@WalterBright
Copy link
Member Author

did you mean __totype?

Yes.

@WalterBright
Copy link
Member Author

Now an unrelated buildkite failure:

Running the testsuite | 0s
 | --
  | ~> case "$REPO_FULL_NAME" in
  | ~> export TRAVIS_OS_NAME=none
  | ~> TRAVIS_OS_NAME=none
  | ~> use_travis_test_script
  | ~> echo 'set -xeu'
  | ~> /var/lib/buildkite-agent/builds/ci-agent-6226e41f-df0b-4254-85f7-af720b10947d-2/dlang/dmd/build/buildkite/travis_get_script
  | ~> bash
  | ~> sed -e /meson/d
  | + [[ '' == \l\d\c\2 ]]
  | bash: line 2: TRAVIS_CPU_ARCH: unbound variable
  | 🚨 Error: The command exited with status 1


public Type decoToType(const(char)[] deco)
{
auto sv = Type.stringtable.lookup(deco);
Copy link
Member

Choose a reason for hiding this comment

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

I think this is way too simplistic: you cannot synthesize new types and you cannot create types that the compiler hasn't yet seen, but are in other modules, structs or similar.

I don't yet see how __totype does anything more than optimize mixin(core.demangle(deco)) (but allowing access to private types).

Copy link
Member

Choose a reason for hiding this comment

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

Ah, so that only works with strings that were previously returned by .mangleof. Yes that may be problematic.

@andralex
Copy link
Member

All in all this is nice and dandy but I have one objection and one suggestion:

  • Whenever we introduced similar facilities in D, we used __traits. This stands out, particularly because it's definitely not to be used naively so verbosity is a nonissue. I suggest packaging the feature as __traits(mangleToType, str).

  • The facility should accept multiple items and return a built-in tuple of types: __traits(mangleToType, "i", "i") should yield AliasSeq!(int, int).

I'll also note we don't have a solution to converting (reliably) non-type aliases to strings and back.

@adamdruppe
Copy link
Contributor

adamdruppe commented Sep 28, 2020 via email

@adamdruppe
Copy link
Contributor

BTW a general facility of array (obviously at least length known at compile time, but contents may or may not be known there; ideally, ONLY the length would be CT required, so it can be used on runtime params too) to tuple would also be really useful. Doing that in the library is a mess with random limitations, but the compiler could make it easy.

Manu's ... static map proposal of course does that and more - so I'm still in favor of that - but if we just had some facility for array to tuple then you wouldn't have to handle that inside this specific trait.

@BorisCarvajal
Copy link
Member

But why not just create a demangle property for strings and use mixins?
mixin("i".demangle) var;
That should work with symbols too.

You can mark StringExp as a decoded type to reduce any overhead in the mixin logic.

@andralex
Copy link
Member

But why not just create a demangle property for strings and use mixins?
mixin("i".demangle) var;
That should work with symbols too.

You can mark StringExp as a decoded type to reduce any overhead in the mixin logic.

Hm, I thought this doesn't work, but it does:

mixin("int") x;

@thewilsonator
Copy link
Contributor

Hm, I thought this doesn't work,

It used to not. It was changed a couple of years ago.

@adamdruppe
Copy link
Contributor

adamdruppe commented Sep 28, 2020 via email

@adamdruppe
Copy link
Contributor

It used to not. It was changed a couple of years ago.

that's cool. btw if void mixin(name)() {} worked you'd cut my number of larger string mixins in like half instantly. just fyi :)

@andralex
Copy link
Member

I just realized that this feature is not synonymous to mixin("int") because it operates on the mangled name, not the clear string.

Different topic - here are a few __traits that are akin to __totype. These strengthen the case on why we should package this feature as a trait:

  • __traits(getAliasThis, T) yields a type
  • __traits(getMember, s, "T") where T is a member type of typeof(s) yields a type
  • Similarly with parent and child

@TurkeyMan
Copy link
Contributor

TurkeyMan commented Sep 29, 2020

This, in effect, closes the circle where .mangleof produces a string literal representing the type

This wouldn't necessarily be true if I manage any traction with this: https://issues.dlang.org/show_bug.cgi?id=21203
Which hasn't received any attention... and I think it's critical to close the outstanding issues with C++ interop.

@BorisCarvajal
Copy link
Member

This PR basically is mixin, so we can reason about it by analogy in terms of what we know, but it does have this very important key difference in that it skips language scoping rules. So this particular problem is bypassed.

@adamdruppe That could be implemented as another feature, something that let you use/create types (or forward references of them) from strings, not necessarily from a mangled one.

@UplinkCoder
Copy link
Member

@BorisCarvajal creation of types is quite problematic, because they need a mangle.
If you use a function to create a type it needs to be strongly pure, and even then you to have access to where-ever it was created from.
Which is the reason why I omitted this feature from type functions.

@UplinkCoder
Copy link
Member

UplinkCoder commented Sep 30, 2020

@WalterBright

we should package this feature as a trait

While I seldom agree with his views, Andrei is right about this one.

If we do it at all.

@12345swordy
Copy link
Contributor

ping

@WalterBright
Copy link
Member Author

I think I agree with @andralex that this should be re-implemented as a __trait. It'll be drastically simpler.

@WalterBright
Copy link
Member Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Spec PR A PR updating the language specification needs to be submitted to dlang.org
Projects
None yet