Skip to content

#1589: Improve error message for WrongNumberOfArgs #1623

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 3 commits into from
Oct 25, 2016

Conversation

ShaneDelmore
Copy link
Contributor

No description provided.

hl"""|You have not supplied enough type parameters
| If you specify one type parameter then you need to specify every type parameter.""".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 message for too many type args

-- [E018] Syntax Error: test.scala -------------------------------------------------------------------------------------
16 |  var bad = List[Int, String]
   |            ^^^^^^^^^^^^^^^^^
   |Too many type arguments for ([scala$collection$immutable$List$apply$$A] => (xs: A*)scala.collection.immutable.List[A])(List.apply)
   |expected: [A]
   |actual:   [Int, String]

Explanation
===========
You have supplied too many type parameters

For example List takes a single type parameter (List[A])
  If you need to hold more types in a list then you need to combine them
  into another data type that can contain the number of types you need,
  In this example one solution would be to use a Tuple:
  val tuple2: Tuple2[Int, String = (1, "one)
    List[(Int, String)] = List(tuple2)

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 message for not enough type args

-- [E018] Syntax Error: test.scala -------------------------------------------------------------------------------------
17 |  var bad2 = Map[(Int, String)]
   |             ^^^^^^^^^^^^^^^^^^
   |Not enough type arguments for ([scala$collection$generic$GenMapFactory$apply$$A, scala$collection$generic$GenMapFactory$apply$$B] =>
   |  (elems: (A, B)*)scala.collection.immutable.Map[A, B]
   |)(Map.apply)
   |expected: [A, B]
   |actual:   [Tuple2[Int, String]]

Explanation
===========
You have not supplied enough type parameters
  If you specify one type parameter then you need to specify every type parameter.

| case _ => 0
| }
|
|""".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.

Due to #1612 I am unable to actually reach this block of code right now so I kept the message simple. Once #1612 is fixed I would like to revisit this message and generate the additional case statements for the user from their alternatives.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's fine :) Also, if you want - you can take a stab at fixing #1612 as a more challenging task!

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 was hoping I could fix this but so far don't have a good enough idea of the flow through the compiler to know where the right fix is. I will spend some time on it this weekend most likely though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just ping us in the gitter channel and we'll try to help :)

@ShaneDelmore ShaneDelmore force-pushed the 1589_Missing_error_messages branch from 179a0fe to 3790e93 Compare October 25, 2016 02:01
Copy link
Contributor

@felixmulder felixmulder left a comment

Choose a reason for hiding this comment

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

Great explanations all around! I'm not too much of a fan of the left whitespace padding - but maybe you have some idea with this that I'm not getting?

Some idiomatic things to change and we're good to go :)

val kind = "Syntax"
val expectedCount = expectedArgs.length
val actualCount = actual.length
val msgPrefix = if (actualCount > expectedCount) "Too many" else "Not enough"
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 wrap this in NoColor so that it's not highlighted in the msg.

val expectedArgString = (fntpe.widen match {
case pt: MethodOrPoly => pt //ensure we return a type that will have useful typeParms
case _ => fntpe
}).typeParams.map(_.paramName.show.split("\\$").last).mkString("[", ", ", "]")
Copy link
Contributor

Choose a reason for hiding this comment

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

this match expression is the same as just taking: fntpe.widen.typeParams

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice simplification

val actualArgString = actual.map(_.show.split("\\.").last).mkString("[", ", ", "]")

val msg =
hl"""|$msgPrefix ${argKind} arguments for $fntpe
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of $fntpe, I would do: ${fntpe.termSymbol.showFullName} which will give you something like: scala.collection.immutable.List.apply which you can then add the correct parameter list to (if you want).

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 is great, I was hunting around for something like it but didn't find it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No luck, it doesn't return the same result. on the below code

object Test {
  case class Bar[D, E](baz: D, qux: E)
  var bad3: Bar[Int] = null
}

My original solution returned "Test.Bar" where fntpe.termSymbol returns nonSensicalType

| into another data type that can contain the number of types you need,
| In this example one solution would be to use a Tuple:
| val tuple2: Tuple2[Int, String = (1, "one)
| List[(Int, String)] = List(tuple2)""".stripMargin
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe kill the left padding on these lines and add a newline before the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No problem. I was trading off indentation for vertical space as I worried we didn't want the messages too long. Newlines work just as well for me though.

| If you need to hold more types in a list then you need to combine them
| into another data type that can contain the number of types you need,
| In this example one solution would be to use a Tuple:
| val tuple2: Tuple2[Int, String = (1, "one)
Copy link
Contributor

Choose a reason for hiding this comment

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

missing ] and "?

Also perhaps add ${"..."} around the code to get highlighting, or just move it to variable and interpolate that :)

@@ -512,4 +513,64 @@ object messages {
|}""".stripMargin
}

case class WrongNumberOfArgs(fntpe: Type, argKind: String, expectedArgs: List[TypeParamInfo], actual: List[untpd.Tree])(implicit ctx: Context)
extends Message(18) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This will be number 19 if merged first :)

| case _ => 0
| }
|
|""".stripMargin
Copy link
Contributor

Choose a reason for hiding this comment

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

That's fine :) Also, if you want - you can take a stab at fixing #1612 as a more challenging task!

| case (1, n) => n
| case (n, 1) => n
| case _ => 0
| }
Copy link
Contributor

Choose a reason for hiding this comment

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

Interpolate the code in this guy too? We love syntax highlighting 🦄 🌈

| If you need to hold more types in a list then you need to combine them
| into another data type that can contain the number of types you need,
| In this example one solution would be to use a Tuple:
| val tuple2: Tuple2[Int, String = (1, "one)
Copy link
Member

Choose a reason for hiding this comment

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

I would just write val tuple2 = (1, "one"), even if you wanted to write the type explicitly you would write (Int, String), not Tuple2[Int, String]

Choose a reason for hiding this comment

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

  •         |  val tuple2: Tuple2[Int, String = (1, "one) 
    
    Shouldnt it be
  •         |  val tuple2: Tuple2[Int, String] = (1, "one) 
    
    ?

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 was unsure on this one, was trying to place myself in the shoes of a beginner who would even need -explain for this and wasn't sure if they would understand that (1 -> "one) is tuple.

val expectedArgString = (fntpe.widen match {
case pt: MethodOrPoly => pt //ensure we return a type that will have useful typeParms
case _ => fntpe
}).typeParams.map(_.paramName.show.split("\\$").last).mkString("[", ", ", "]")
Copy link
Member

Choose a reason for hiding this comment

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

As much as possible, we should avoid playing with regexps on names in error messages, this way lies madness. If there are issues with the way we print names, they should be fixed in a common place, not worked around in every error message.
A slightly nicer way:

import NameOps._
//...
}).typeParams.map(_.paramName.unexpandedName.show).....

Technically this is still incorrect, unexpandedName should only be called on a name of a symbol with the flag ExpandedName but we have no way of checking that here, I think the correct solution would be to add a def simpleParamName method to TypeParamInfo, but this could be mentioned in a TODO comment and done later.

Copy link
Contributor

Choose a reason for hiding this comment

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

I second the TODO approach - and perhaps an issue?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Any reason for me not to just add def simpleParamName in this PR?

Copy link
Member

Choose a reason for hiding this comment

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

You can if you figure out how (you need an implementation in Symbol and one in LambdaParam, for Symbol you check if this.is(ExpandedName), for LambdaParam you just use the regular paramName which should never be expanded (I think?)

case _ => fntpe
}).typeParams.map(_.paramName.show.split("\\$").last).mkString("[", ", ", "]")

val actualArgString = actual.map(_.show.split("\\.").last).mkString("[", ", ", "]")
Copy link
Member

Choose a reason for hiding this comment

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

This is also a dubious usage of a regexp, what if the user wrote List[Foo.A, Bar.A]? You just rewrote this to List[A, A] which is bound to be more confusing. We don't have a good solution to prefix-eliding right now but this isn't it.

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 can't think of a great solution for prefix eliding without knowing what is in scope which feels way beyond me right now. Any other ideas, or do you prefer the full verbose including $$ name? To handle the case you mentioned maybe I could find some way to get distinct types and then only keep enough of the prefix to disambiguate them? If I just had Foo.A and Bar.B I would just show A, B but if I had Foo.A and Bar.A I would show Foo.A and Bar.A?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering how much of the functionality from asSeenFrom can be used here...I say, show the full path (without $$$$$) otherwise.

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 remember why I had no luck with asSeenFrom, one of the four callers of this function has an untyped arg tree. Should I split the message into two, one for typed callers and one for untyped callers, or just leave the full path?

Copy link
Contributor

Choose a reason for hiding this comment

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

If it doesn't have a type (there's a typeOpt function available to trees as well as hasType) - go for the full type - otherwise asSeenFrom?

| into another data type that can contain the number of types you need,
| In this example one solution would be to use a Tuple:
| val tuple2: Tuple2[Int, String = (1, "one)
| List[(Int, String)] = List(tuple2)""".stripMargin
Copy link
Member

Choose a reason for hiding this comment

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

Extra indentation of two spaces compared to the previous line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops. I was using indentation to try to group related or nested lines of code, but is the concensus that I should just left align everything?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes please :) That's been the policy thus far.

val prettyName = fntpe.termSymbol match {
case NoSymbol => fntpe.show
case symbol => symbol.showFullName
}
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 like to make another PR to add def prettyName (or something like it) to a few types. I will leave it as a follow up unless you prefer it as part of this PR.

With these changes

object Test {
  var bad = List[Int, String]
  case class Bar[D, E](baz: D, qux: E)
  var bad3: Bar[Int] = null
}

results in

-- [E019] Syntax Error: test.scala -------------------------------------------------------------------------------------
16 |  var bad = List[Int, String]
   |            ^^^^^^^^^^^^^^^^^
   |            Too many type arguments for scala.collection.immutable.List.apply[A]
   |            expected: [A]
   |            actual:   [Int, String]

Explanation
===========
You have supplied too many type parameters

For example List takes a single type parameter (List[A])
If you need to hold more types in a list then you need to combine them
into another data type that can contain the number of types you need,
In this example one solution would be to use a Tuple:

val tuple2: (Int, String) = (1, "one")
val list: List[(Int, String)] = List(tuple2)

-- [E019] Syntax Error: test.scala -------------------------------------------------------------------------------------
19 |  var bad3: Bar[Int] = null
   |            ^^^^^^^^
   |            Not enough type arguments for Test.Bar[D, E]
   |            expected: [D, E]
   |            actual:   [Int]

Explanation
===========
You have not supplied enough type parameters
If you specify one type parameter then you need to specify every type parameter.

Copy link
Contributor

@felixmulder felixmulder left a comment

Choose a reason for hiding this comment

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

Otherwise LGTM, after the minor changes I think we should merge this and let you move on to new adventures :)

val actualCount = actual.length
val msgPrefix = if (actualCount > expectedCount) "Too many" else "Not enough"

val expectedArgString = fntpe.widen.typeParams.map(_.paramName.unexpandedName.show).mkString("[", ", ", "]")
Copy link
Contributor

Choose a reason for hiding this comment

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

This was supposed to have a //TODO right?

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 added it now.

@@ -512,4 +513,75 @@ object messages {
|}""".stripMargin
}

case class WrongNumberOfArgs(fntpe: Type, argKind: String, expectedArgs: List[TypeParamInfo], actual: List[untpd.Tree])(implicit ctx: Context)
extends Message(19) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The errorId has to be updated - to 21 I think - so 22 for the other one

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, merged @AndrewZurn's, so 22 and 23 - you're next I promise :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

220 and 230 it is 👍

@ShaneDelmore ShaneDelmore force-pushed the 1589_Missing_error_messages branch from 8f0dc96 to a83725a Compare October 25, 2016 21:22
@felixmulder
Copy link
Contributor

felixmulder commented Oct 25, 2016

@ShaneDelmore - seems there is still a conflict in messages.scala, rebase? (no need to squash)

@ShaneDelmore ShaneDelmore force-pushed the 1589_Missing_error_messages branch from a83725a to aaae563 Compare October 25, 2016 21:37
@ShaneDelmore
Copy link
Contributor Author

I rebased and resolved the merge conflict.

@felixmulder felixmulder merged commit aae4019 into scala:master Oct 25, 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.

4 participants