-
Notifications
You must be signed in to change notification settings - Fork 1.1k
#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
#1589: Improve error message for WrongNumberOfArgs #1623
Conversation
hl"""|You have not supplied enough type parameters | ||
| If you specify one type parameter then you need to specify every type parameter.""".stripMargin | ||
} | ||
} |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)
179a0fe
to
3790e93
Compare
There was a problem hiding this 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" |
There was a problem hiding this comment.
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("[", ", ", "]") |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 | ||
| } |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
Shouldnt it be
| val tuple2: Tuple2[Int, String = (1, "one)
-
?
| val tuple2: Tuple2[Int, String] = (1, "one)
There was a problem hiding this comment.
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("[", ", ", "]") |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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("[", ", ", "]") |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this 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("[", ", ", "]") |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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 👍
8f0dc96
to
a83725a
Compare
@ShaneDelmore - seems there is still a conflict in |
a83725a
to
aaae563
Compare
I rebased and resolved the merge conflict. |
No description provided.