Could compiler reports use imports in the printed output?

When writing type-level code (and to a lesser extend always) complex types tend to accrue a lot of constant prefixes. Could the compiler, when reporting errors, at reaching a certain threshold (type length as a string, number of referenced types/number of types from a single package, number of occurrences of the same type - to be decided) add import clauses before/after the error report, and reference the types in the report through those imports? This would immensly improve the readability of the report. In many cases, this would only add a single line, as a great chunk of all code deals with classes from the same package which could be imported together with the brace syntax.

What is the view of movers and shakers on this feature? Does it look like a possible first issue? If such a change would be welcome and someone pointed me in the right direction, I could perhpaps give it a go.

4 Likes

dotty does the following:

3 Likes

I meant the opposite of that, really. Here it would be something like

Found:    bar.Baz
Required: foo.BazĀ²

where:   
    import very.long.prefix.that.contains.{foo, bar}

In the vast majority of practical applications, all types could be imported at the very least to their most-outer anchored path element (class/object).
So

Found:    my.app.has.infix.classes.Link[my.app.has.inifix.classes.A, my.app.has.infix.classes.Link[my.app.has.infix.classes.B, my.app.has.infix.classes.Link[my.app.has.infix.classes.C, my.app.has.infix.classes.End]
Required: something else

Would become:

Found: Link[A, Link[B, Link[C, End]]]
Required: something else
where
     import my.app.has.infix.classes.{A, B, C, Link, End}

The aggresiveness of importing is of course a matter of debate, but I would lean towards importing everything down to the shortest unique suffix. The fact that the ā€˜import sectionā€™ grows is of little relevance, as in most cases there would be no need for the programmer to even look at it, as the location in the code and locally unique type names should be uniquely mentally resolvable.

Of course, fine tuning makes sense - perhaps refrain from importing type aliases, or member types. But even stripping the redundant packages would considerably improve readability.

3 Likes

Iā€™ve always thought this would be a good idea.

The same mechanism would be extremely useful in pretty-printing program fragments, in the context of metaprogramming, or program dumps by the compiler.

We just need a mechanism to compute maximal import statements without introducing any ambiguities (we should not import the same names several times), and this mechanism could be reused in error messages, code pretty-printing, and compiler dumps.

3 Likes

Perhaps the compiler could use the same imports used in the source file, since those are the ones that are most natural to Scala users. However, when it comes to stuff inside a different source file or inside a jar, Iā€™m not sure how that would work.

1 Like

IntelliJā€™s approach might be a useful source of inspiration here.

When generating a type annotation to a method, they try to get it down to a direct import. Thatā€™s not always possible so in the case of a naming collision they import the longest possible prefix.

For example, adding a type annotation to a method which returns a scala.collection.mutable.Map would generate an import statement for scala.collection.mutable and the type added would be mutable.Map.

So if the compiler preferentially takes the imports in the file (optimizing for familiarity), and gives a best-effort at maximizing imports for types that donā€™t already have them (optimizing for readability), it should produce very readable results. There are some corner cases, as itā€™ll occasionally annotate a String as _root_.java.lang.String - usually when itā€™s a nested type parameter, however I think the approach is pretty reasonable.

3 Likes

This would indeed be awesome, as it would resemble the offending code the most. I do not know though if this kind of information is retained at the point of generating a report (because the best place to introduce this is late, so it covers all bugs, not only from the typer? - discuss). Or can it this information be somehow linked again at this stage?

@morgen-peschke - your Map example relies on the configurable set of ā€˜never directly import types from this packageā€™. Does making it a compiler parameter make sense? The algorithm you describe (regardless if with the first stage or without it) sounds extremely simple. Two issues I am not sure about are:

  • renaming imports in the source file (would be great to honour them, will that information be available?
  • type aliases resolving to the same type; I vote thay should stay as they are, without being resolved; Double imports arenā€™t allowed, are they?
  • chained imports: import net.noresthterein.slang; import slang._
  • off by one errors

@LPTK - what ambiguities do you have in mind? Simply importing different entities with the same name but different package, or something I am unaware?

The ā€˜maximal path without ambiguities based on scala.reflect.runtime.universe.Typeā€™ seems not difficult and I assume is directly applicable to compiler internals. Not trivial because of kinds - I have no idea how T(some other), [O] some.class[O(in method m)] or existentials are modeled. Probably solvable by simply trying that out, though.

I know nothing however about where to look for imports of the relevant file and how the message printing API looks like. For all I know, the task may be basically impossible, if parts of the messages are constructed at usage site.

If someone in the know opinionates that its doable and points me at least to the place where it should be implemented, I could give it a go. It would need some serious code review and input on test cases though - I donā€™t know how complete my code would be, as I am very likely to miss some cases (I have no idea what they even look like ATM).

Also: is anyone here familiar with scaladocs and if the same APIs apply? I would be even more interested in making links using imports from the source file work, as it would undoubtedly lock in my place in the heavens.

1 Like

Map was probably a bad example for what I was trying to convey. That being said, having a configurable list of ā€œnever truncate types further than this packageā€ would probably be useful.

Iā€™ll give describing what IntelliJ does another shot:

Assuming the existence of example.foo.Baz and example.bar.Baz, and that example.foo.Baz has already directly imported in this source file, adding a type annotation to a method which returns example.bar.Baz would generate an import statement for example.bar and the type added would be bar.Baz.