Asking the compiler to prefer an alias for error messages

Recently, I've toyed with the idea of implementing Ada-style constraint subtyping using newtypes and macros for syntactic sugar. This results in code that looks like:

type SimpleAlias = macro_generated_ugly_module::MacroGeneratedUglyType<UnimportantArgs>;

When users write code, they can use the quite readable SimpleAlias. When the compiler issues an error message, though, they see the ugly path in all its painful detail.

I think it would make sense to introduce some kind of annotation to instruct the compiler to prefer the alias when possible.

#[preferred_type_name]
type SimpleAlias = ...

or perhaps

mod macro_generated_ugly_module {
  #[private_type_name]
  pub struct MacroGeneratedUglyType<T> {
     // ...
  }
}

I realize it may be tricky to implement (type aliases are immediately resolved by the compiler, right? I remember reading something about lazy aliases, don't know if that's related).

What do you think? Food for a pre-RFC?

edit See also The compiler should report publicly exported type names if possible · Issue #21934 · rust-lang/rust · GitHub .

6 Likes

I think this is an uncontroversial change, it should be the default (not an attribute), and it's a generally part of "better pathing in error messages". I would say it's food for a tracking issue that lists the problematic cases (type aliases, reexports (e.g. error messages sometimes refer to something in core, when they should typically direct you to std); there are a couple other things in your linked issue), and tracking the ground work needed to make those changes.

3 Likes

I could see this being annoying for non-opaque aliases in my dependencies. It's hard to tell what's going on when relevant information is hidden -- e.g. that "type" is actually a trait projection.

A related issue I've wanted is for diagnostics to be smarter about what names are in scope. I've seen both full paths for names in scope and no paths for things not in scope. (Also, use my name if I renamed on import.)

Then if I didn't import the alias, don't use the alias in diagnostics.

5 Likes

I think the ideal is "use the name the developer used, and also, if they're different, the actual type relevant to the error and how they're related" - but there's lots of subtleties like associated traits and macros that make that a tar pit.

4 Likes

There were issues before with rustc often mentioning types as exported from ::serde::private::std::* instead of ::std::*. (Note that serde is lexographically ordered before std.) I think serde mitigated it by only reëxporting exactly what it was using, and rustc at some point learned to prefer the canonical path instead of choosing unpredictably.

Whatever scheme is used, it needs to be able to avoid accidentally preferring non-canonical paths over proper paths due to the fact they accidentally happen to score better on some heuristic. e.g. I want to see std::sync::atomic::AtomicU32, not lib::__::AtomicU32, just because I happen to have lib available, it has macros that need access to AtomicU32, and the path to the symbol is shorter (thus it surely would be preferable to the longer path).

Also, when an error has multiple source spans, it can be unclear which one is the primary source of the error. (E.g. Cargo can say which crates are nonlocal and thus likely not the culprit, but rustc doesn't have that information. The best it has is which crate is currently being compiled, but legitimately sometimes the error is better blamed on an "upstream" but still local crate, and rustc doesn't have that information.) Most errors are obvious where should host the blame (e.g. the code of the function being typechecked, not the signatures of the functions its using) but it's not always straightforward (e.g. conflicting impls).

It's fairly self evident the priority of names to use, it's just not simple to actually follow that guideline.

  • The name used in the code causing the error, if present.
  • The name in scope at the source of the error, if present.
  • The "most canonical" path for the name from the defining crate, if available to the crate causing the error.
  • The "most canonical" path that the name is accessible to the crate causing the error by, if available.
  • The strictly canonical type definition path name.
4 Likes

The compiler currently also suggests unstable paths on stable (core::error::Error); imo that should part of the algorithm too.

2 Likes

#[doc(hidden)] should also affect suggestion priority, IMO. That would hide macro reexports

2 Likes