Suggestion: override compiler type names

Anyone who has used crates like diesel or frunk is aware of the long error messages which can be generated. Here is an example frunk error from a real project:

error[E0599]: no method named convert found for type frunk_core::hlist::HCons<(conversion::v4_0::Out<'_>, frunk_core::labelled::Field<(frunk_core::labelled::chars::c, frunk_core::labelled::chars::a, frunk_core::labelled::chars::t, frunk_core::labelled::chars::e, frunk_core::labelled::chars::g, frunk_core::labelled::chars::o, frunk_core::labelled::chars::r, frunk_core::labelled::chars::y, frunk_core::labelled::chars::__, frunk_core::labelled::chars::n, frunk_core::labelled::chars::a, frunk_core::labelled::chars::m, frunk_core::labelled::chars::e), &std::option::Option<std::string::String>>), frunk_core::hlist::HCons<(conversion::v4_0::Out<'_>, frunk_core::labelled::Field<(frunk_core::labelled::chars::m, frunk_core::labelled::chars::a, frunk_core::labelled::chars::t, frunk_core::labelled::chars::c, frunk_core::labelled::chars::h, frunk_core::labelled::chars::e, frunk_core::labelled::chars::r, frunk_core::labelled::chars::__, frunk_core::labelled::chars::n, frunk_core::labelled::chars::a, frunk_core::labelled::chars::m, frunk_core::labelled::chars::e), &std::option::Option<std::string::String>>), frunk_core::hlist::HCons<(conversion::v4_0::Out<'_>, frunk_core::labelled::Field<(frunk_core::labelled::chars::m, frunk_core::labelled::chars::e, frunk_core::labelled::chars::s, frunk_core::labelled::chars::s, frunk_core::labelled::chars::a, frunk_core::labelled::chars::g, frunk_core::labelled::chars::e), &std::option::Option<std::string::String>>), frunk_core::hlist::HCons<(conversion::v4_0::Out<'_>, frunk_core::labelled::Field<(frunk_core::labelled::chars::t, frunk_core::labelled::chars::y, frunk_core::labelled::chars::p, frunk_core::labelled::chars::e, frunk_core::labelled::chars::__), &std::option::Option<String>>), frunk_core::hlist::HNil>>>> in the current scope

In this case, it would dramatically improve the error message if frunk could override the way that the Field and HCons types are displayed inside error messages, turning the above message into something like this:

error[E0599]: no method named convert found for type hlist![(conversion::v4_0::Out<'_>, field!(category_name, &std::option::Option<std::string::String>), (conversion::v4_0::Out<'_>, field!(matcher_name, &std::option::Option<std::string::String>), (conversion::v4_0::Out<'_>, field!(message, &std::option::Option<std::string::String>), (conversion::v4_0::Out<'_>, field!(type_, &std::option::Option<std::string::String>)] in the current scope

This is still valid rust syntax, it's just "re-macroing" the types to save space.

One way this could be implemented is via a special trait with a const fn method to return the type name. Frunk could implement this method for the HCons, HNil, Field types to provide better human-readable names, although this would require the ability to construct strings inside const functions.

1 Like

Hello

I don’t really want to be the pessimist, but that idea kind of scares me. It increases the language size/surface and weirdness and feels like a hack. Also, I suspect that this’ll not really be effective. You’re likely to get these error messages if the type can be named, but „something is off“ with it, so it doesn’t implement the traits the user would like it to, therefore it is missing a method or something. Any reason why this „being off“ should not cause this special trait to be not implemented too?

What if the error message is caused not by a complex type but a complex trait? You can’t implement a trait on a trait.

It also feels a bit like:

  • Someone abused the type system in a way it was not meant to (certainly in very interesting way).
  • As a result, the error messages don’t look nice, because they were not designed for that abuse.
  • So we introduce another hack, abusing a trait (which describes how the program should behave after it compiles) to produce error messages if it doesn’t compile.
6 Likes

It increases the language size/surface and weirdness and feels like a hack.

Given that it only affects error messages, it seems like a case where you only pay for what you use: it's not like println! where a complex feature (string formatting and macro expansion) needs to be understood by everyone. If you're not aware of the feature, it has precisely zero impact on the way you write code.

Any reason why this „being off“ should not cause this special trait to be not implemented too?

The special trait can be implemented for all HCons lists, the implementation is much less "fragile" because it doesn't rely on traits being implemented by the items inside the list. Compare that to eg. Clone where it would recursively depend on every type inside the list also implementing Clone, and it can be difficult to tell which item is missing the required implementation.

  • Someone abused the type system in a way it was not meant to (certainly in very interesting way).

I'm not sure I agree with the term "abuse". This kind of meta-programming is very common in rust code, and serves an important purpose. The "Field" encoding in frunk is more special, but that wasn't even the only case in my example above.

If the consensus is that this kind of meta-programming should be discouraged in Rust, then the language designers need to come up with another way to achieve the same goals.

  1. The idea of supplying hints for better error messages seems good.

  2. Ideally, I’d like to see native type-level strings and type-level lists, which would address cases like this.

3 Likes

This cannot be implemented as a trait due to the high probability of there being generic type parameters and unknown inference variables.

error[E0599]: no method named `lol` found for type `std::vec::Vec<_>` in the current scope
  --> src/main.rs:31:44
   |
31 |     std::iter::empty().collect::<Vec<_>>().lol();
   |                                            ^^^

Would a special attribute work?

In the specific case of frunk::LabelledGeneric, the name encoding should probably go away pretty trivially once even non-manipulable &'static str are usable as const generics.

Any proposal for aliasing compiler type outputs should probably focus on Diesel types instead, as there a clear reasoning for the types exists that isn’t a “hack” around the lack of const generics.

A minimal improvement in some cases would to be for the compiler to use type aliases when they exist to shrink the size of displayed type names.

1 Like

error[E0599]: no method named convert found for type frunk_core::hlist::HCons<(conversion::v4_0::Out<'_>, frunk_core::labelled::Field<"category_name", &std::option::Option<std::string::String>>), frunk_core::hlist::HCons<(conversion::v4_0::Out<'_>, frunk_core::labelled::Field<"matcher_name", &std::option::Option<std::string::String>>), frunk_core::hlist::HCons<(conversion::v4_0::Out<'_>, frunk_core::labelled::Field<"message", &std::option::Option<std::string::String>>), frunk_core::hlist::HCons<(conversion::v4_0::Out<'_>, frunk_core::labelled::Field<"type_", &std::option::Option<String>>), frunk_core::hlist::HNil>>>> in the current scope

Well, that's kinda better, though those HLists still risk turning into a modern-day version of the Cold War Lisp joke.

Moreover... I really don't see why all those prefixes have to be there. I wish it would use the names visible in scope. Even if nothing is imported we could get:

error[E0599]: no method named convert found for type frunk_core::hlist::HCons<(conversion::v4_0::Out<'_>, frunk_core::labelled::Field<"category_name", &Option<String>>), frunk_core::hlist::HCons<(conversion::v4_0::Out<'_>, frunk_core::labelled::Field<"matcher_name", &Option<String>>), frunk_core::hlist::HCons<(conversion::v4_0::Out<'_>, frunk_core::labelled::Field<"message", &Option<String>>), frunk_core::hlist::HCons<(conversion::v4_0::Out<'_>, frunk_core::labelled::Field<"type_", &Option<String>>), frunk_core::hlist::HNil>>>> in the current scope

but this is such low hanging fruit that I can only imagine it has been discussed to death before and is off the table. (regardless, I can't imagine that the rare instance where you are helped by seeing the full path of some type is enough to offset the increased burden in reading every single error message...)

2 Likes

This is a long-standing issue that I've been nagging @ekuber about for a while about now... It's one of the worst parts of rustc's otherwise good diagnostics.

1 Like

IIRC, the last time this was brought up, it was determined that the error reporting framework just isn't anywhere close to set up to support this yet, and even the std/core rewriting is done via textual mangling rather than intelligently.

It's definitely on the "sometime" list (at the very least for prelude types), but nobody has had the time to prioritize it yet.

4 Likes

Interesting! I wouldn’t have expected this to be a limitation of the diagnostics framework, as I would’ve expected that the code using the diagnostics API would have no problem adjusting these paths before constructing the messages.

Do you have a link?

1 Like

This is one of the annoyances I found while experimenting with type-level stuff in type_level_values.

The workaround I had for long error messages was to use the

[^\(\)\[\]{}>`<,= ]+::

regex to clean up the error message.

This is the error message from frunk,filtered with that regex

error[E0599]: no method named convert found for type HCons<(Out<'>, Field<(c, a, t, e, g, o, r, y, __, n, a, m, e), Option>), HCons<(Out<'>, Field<(m, a, t, c, h, e, r, __, n, a, m, e), Option>), HCons<(Out<'>, Field<(m, e, s, s, a, g, e), Option>), HCons<(Out<'>, Field<(t, y, p, e, __), Option>), HNil>>>> in the current scope


I would prefer some of these things instead of the proposed solution:

  • An attribute to specify that the typename be printed without printing the module path.

  • An attribute to specify how formatting is done for generic parameters, similar to how std::fmt formatters have an alternate flag.

  • To print all typenames without their module path, so long as no other typename exists with the same name.

The proper solution is to use the local identifier for any type in scope.

Beyond that, we can be more aggressive in the way we hide type arguments that are not relevant, as right now the logic only checks for simple errors (like Option/Result transposition) and hides common subelements, but it doesn’t look at all for cases where all the type arguments on both sides are irrelevant. We could do that, but we should still have a way of emitting that information in the json structured output.

One thing to keep in mind is that we do not have the enclosing context in all errors where types are displayed, so the fully qualified path will likely still appear in at least a subset of errors.

4 Likes

Maybe not a trait then... An attribute on the type could still work, similar to how custom derive works - a function could be referenced which manipulates a token stream.

Note that diesel may shortly depend on frunk directly, and use it to map structs to database result sets (there is a WIP PR implementing this).

I think it would be nice if these were not just "magic" though, and worked in a way such that crates could achieve the same nice error messages for their own types.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.