Skip to content

Improved error messages in Desugar.scala #1611

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

Merged
merged 1 commit into from
Oct 19, 2016

Conversation

ShaneDelmore
Copy link
Contributor

No description provided.

|
|Consider these alternative solutions:
| Extend the object with the desired type
| Use a trait or a class instead of an object""".stripMargin
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this reasonable advice?

Copy link
Contributor

Choose a reason for hiding this comment

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

As this is how you'd solve the problem, I think it is totally reasonable advice :)

When you say "Extend the object with the desired type", are you implying trait T; object O extends T and placing the self type in T?

If so, perhaps phrase it as "Let the object extend a trait containing the self type"

val explanation =
hl"""|implicit classes may not be case classes. Instead use a plain class:
|
|implicit class ${cdef.name}...""".stripMargin
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I try to create the class definition here using args from the AST? Or is that excessive/difficult?

|then import the members of the object at the use site if needed, for example:
|
|package object Implicits {
| implicit class ${cdef.name}...
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would have liked to add the args here but did not see how to grab them. Can I use scala.meta for this?

Copy link
Contributor

@felixmulder felixmulder Oct 18, 2016

Choose a reason for hiding this comment

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

Currently, using scala.meta for this is not possible. It might be in the future!

Hmm, I can't verify this right now but something like:

1. check if cdef.rhs is a Template
2. if so get the constr (a DefDef)

Check what .show reveals on the constructor. Should be something like: def <init>(param1: X, param2: Y)

See bellow instead :)

| implicit class ${cdef.name}...
|}
|
|import Implicits.${cdef.name}""".stripMargin
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! I like the usage of the programmers own code here - makes for something that's easy to grok.

val explanation =
hl"""|implicit classes may not be case classes. Instead use a plain class:
|
|implicit class ${cdef.name}...""".stripMargin
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps just give a quick reason as to why - i.e. "There may not be any method, member or object in scope with the same name as the implicit class and a case class automatically gets a companion object with the same name."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's great, I did not know the reasoning behind this until you explained it.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe you could link to this page which lists all the restrictions of implicit classes: http://docs.scala-lang.org/overviews/core/implicit-classes.html

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great idea, I will add this.

|
|Consider these alternative solutions:
| Extend the object with the desired type
| Use a trait or a class instead of an object""".stripMargin
Copy link
Contributor

Choose a reason for hiding this comment

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

As this is how you'd solve the problem, I think it is totally reasonable advice :)

When you say "Extend the object with the desired type", are you implying trait T; object O extends T and placing the self type in T?

If so, perhaps phrase it as "Let the object extend a trait containing the self type"

@felixmulder
Copy link
Contributor

@ShaneDelmore - wait, scratch that. You're in the classDef method, and on Desugar.scala:253 you already have basically all you'll need at least for the implicit class one.

|Declare ${cdef.name} inside of an ${"object"} or ${"package object"}
|then import the members of the object at the use site if needed, for example:
|
|package object Implicits {
Copy link
Member

@smarter smarter Oct 18, 2016

Choose a reason for hiding this comment

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

I wouldn't recommend (or even mention) package objects, they have tricky semantics and are not usually what you want to do.

@ShaneDelmore ShaneDelmore force-pushed the 1589_Missing_error_messages branch from 084cff8 to 126d126 Compare October 19, 2016 01:55

val implicitClassRestrictionsText =
"""For a full list of restrictions on implicit classes visit
|http://docs.scala-lang.org/overviews/core/implicit-classes.html""".stripMargin
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a highlight available for URLs? The highlighting on this treats http: as plain text and //restOfTheUrl as a comment.

Also, do highlighted sections compose?

Copy link
Contributor

Choose a reason for hiding this comment

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

All interpolated strings in hl"..." get syntax highlighting. If you want to interpolate implicitClassRestrictions in you highlighted block I'd wrap it in a NoColor(...) Highlight. Already "colored" strings do not get interpolated by the interpolator.

See: https://github.com/lampepfl/dotty/blob/master/src/dotty/tools/dotc/printing/Highlighting.scala#L55

val msg = hl"""|An ${"implicit class"} may not be top-level"""

val explanation = {
val TypeDef(name, impl @ Template(constr0, parents, self, _)) = cdef
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is duplicated from Desugar.scala. Is it preferred to duplicate it here to keep the coupling looser or should I instead add more parameters to the case class and pass along explicitely what I need, name, impl, constr0?

Copy link
Contributor

Choose a reason for hiding this comment

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

Good question, I think there's a risk that more people will need to print the class def and as such perhaps we should factor this out into a method call that takes the cdef - a small bit of duplication here is fine in order for it to be easy to use for new contributors IMHO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now that the explanation body has been simplified there isn't really much here worth extracting.

@ShaneDelmore ShaneDelmore force-pushed the 1589_Missing_error_messages branch from 126d126 to 7df6770 Compare October 19, 2016 02:02
@ShaneDelmore ShaneDelmore changed the title Improved error messages in Desugar.scala WIP: Improved error messages in Desugar.scala Oct 19, 2016
@ShaneDelmore ShaneDelmore force-pushed the 1589_Missing_error_messages branch from 7df6770 to 47cf8fd Compare October 19, 2016 02:41
start = " {\n ",
sep = "\n\n ",
end = "\n }"
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This feels ugly but I couldn't think of a better way to do it. Should I extract this or keep it contained and out of the way since it won't be needed once we can use scala.meta?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would actually prefer this to have an else clause that is something like {\n ...\n }.

My motivation for this is if we have an implicit class with a lot of members, they will all get printed and bloat the message. For copy-pastability (is that even a word?), I think it's fine if we leave out the complete method body.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good. I will try to make the suggested updates this evening.

|
|import Implicits.${cdef.name}""".stripMargin
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Example output:

-- [E010] Syntax Error: test.scala -------------------------------------------------------------------------------------
2 |implicit class Foo(n: Int)
  |^^^^^^^^^^^^^^^^^^^^^^^^^^
  |An implicit class may not be top-level

Explanation
===========
There may not be any method, member or object in scope with the same name as the
implicit class and a case class automatically gets a companion object with the same name
created by the compiler which would cause a naming conflict if it were allowed.

For a full list of restrictions on implicit classes visit
http://docs.scala-lang.org/overviews/core/implicit-classes.html

To resolve the conflict declare Foo inside of an object then import the class
from the object at the use site if needed, for example:

object Implicits {
  implicit class Foo(val n: Int)
}

import Implicits.Foo
-- [E010] Syntax Error: test.scala -------------------------------------------------------------------------------------
4 |implicit class Foo(bar: Int, baz: String) {
  |^
  |An implicit class may not be top-level
5 |  def baz = bar.toString
6 |
7 |  def qux = baz.take(5)
8 |}

Explanation
===========
There may not be any method, member or object in scope with the same name as the
implicit class and a case class automatically gets a companion object with the same name
created by the compiler which would cause a naming conflict if it were allowed.

For a full list of restrictions on implicit classes visit
http://docs.scala-lang.org/overviews/core/implicit-classes.html

To resolve the conflict declare Foo inside of an object then import the class
from the object at the use site if needed, for example:

object Implicits {
  implicit class Foo(val bar: Int, val baz: String) {
    def baz = bar.toString

    def qux = baz.take(5)
  }
}

import Implicits.Foo

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated error on def with body to prevent overly long error explanation:

-- [E010] Syntax Error: test.scala -------------------------------------------------------------------------------------
4 |implicit class Foo(bar: Int, baz: String) {
  |^
  |An implicit class may not be top-level
5 |  def baz = bar.toString
6 |
7 |  def qux = baz.take(5)
8 |}

Explanation
===========
There may not be any method, member or object in scope with the same name as the
implicit class and a case class automatically gets a companion object with the same name
created by the compiler which would cause a naming conflict if it were allowed.

For a full list of restrictions on implicit classes visit
  http://docs.scala-lang.org/overviews/core/implicit-classes.html

To resolve the conflict declare Foo inside of an object then import the class
from the object at the use site if needed, for example:

object Implicits {
  implicit class Foo(val bar: Int, val baz: String){
 ...
 }
}

import Implicits.Foo

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 it would be nice to make it more explicit that the import Implicits.Foo is a at the use site, for example by writing:

object Implicits {
  implicit class Foo(val bar: Int, val baz: String){
 ...
 }
}

...

// At the use site:
import Implicits.Foo

| example: implicit class ${cdef.name}...
|
|$implicitClassRestrictionsText""".stripMargin
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

-- [E011] Syntax Error: test.scala -------------------------------------------------------------------------------------
14 |  implicit case class Foo(bar: Int, baz: String)
   |  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |  A case class may not be defined as implicit

Explanation
===========
implicit classes may not be case classes. Instead use a plain class:
  example: implicit class Foo...

For a full list of restrictions on implicit classes visit
http://docs.scala-lang.org/overviews/core/implicit-classes.html

| - Let the object extend a trait containing the self type:
| example: object $name extends ${selfTpt.show}""".stripMargin
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

-- [E012] Syntax Error: test.scala -------------------------------------------------------------------------------------
13 |  self: Foo =>
   |  ^^^^^^^^^
   |  objects must not have a self type

Explanation
===========
objects must not have a self type:

Consider these alternative solutions:
  - Create a trait or a class instead of an object
  - Let the object extend a trait containing the self type:
      example: object Test extends Foo

|
| ((${nestedRepresentation}))""".stripMargin
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

-- [E013] Syntax Error: test.scala -------------------------------------------------------------------------------------
24 |  val largeTuple = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23)
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                   A tuple cannot have more than 22 members

Explanation
===========
This restriction will be removed in the future.
Currently it is possible to use nested tuples when more than 22 are needed, for example:

  ((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22)(23))

@ShaneDelmore ShaneDelmore changed the title WIP: Improved error messages in Desugar.scala Improved error messages in Desugar.scala Oct 19, 2016
@felixmulder
Copy link
Contributor

felixmulder commented Oct 19, 2016

Super impressive work, keep up the good work :)

@ShaneDelmore ShaneDelmore force-pushed the 1589_Missing_error_messages branch from 47cf8fd to 15d4dc4 Compare October 19, 2016 17:59
@ShaneDelmore ShaneDelmore force-pushed the 1589_Missing_error_messages branch from 15d4dc4 to 2a310ac Compare October 19, 2016 19:25
|
|// At the use site:
|import Implicits.${cdef.name}""".stripMargin
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

-- [E010] Syntax Error: test.scala -------------------------------------------------------------------------------------
4 |implicit class Foo(bar: Int, baz: String) {
  |^
  |An implicit class may not be top-level
5 |  def baz = bar.toString
6 |
7 |  def qux = baz.take(5)
8 |}

Explanation
===========
There may not be any method, member or object in scope with the same name as the
implicit class and a case class automatically gets a companion object with the same name
created by the compiler which would cause a naming conflict if it were allowed.

For a full list of restrictions on implicit classes visit
  http://docs.scala-lang.org/overviews/core/implicit-classes.html

To resolve the conflict declare Foo inside of an object then import the class
from the object at the use site if needed, for example:

object Implicits {
  implicit class Foo(val bar: Int, val baz: String){
 ...
 }
}

// At the use site:
import Implicits.Foo

@felixmulder felixmulder merged commit 0d1721c into scala:master Oct 19, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants