-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add error message for missing unapply on pattern matching #5180
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
Add error message for missing unapply on pattern matching #5180
Conversation
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.
Hello, and thank you for opening this PR! 🎉
All contributors have signed the CLA, thank you! ❤️
Have an awesome day! ☀️
Note that I wanted to include only the class name which does not declare the method, so I could use in the explanation section, but I could not find a way from acquiring it from Also, I had to add one missing parameter at Any notes on how I can improve this and especially the explanation let me know! Thanks <3 |
vscode-dotty/package-lock.json
Outdated
"fast-deep-equal": "^1.0.0", | ||
"fast-json-stable-stringify": "^2.0.0", | ||
"json-schema-traverse": "^0.3.0" | ||
"co": "4.6.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.
Could you remove this part of the diff?
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.
Done! 😄
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.
LGTM otherwise
| | ||
| class Foo(val name: String) | ||
| object Foo { | ||
| def unapply(foo: Foo): Option[String] = Option(foo.name) |
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 put Some(foo.name)
here.
@@ -896,7 +896,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => | |||
val Apply(qual, args) = tree | |||
|
|||
def notAnExtractor(tree: Tree) = | |||
errorTree(tree, s"${qual.show} cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method") | |||
errorTree(tree, NotAnExtractor(tree), tree.pos) |
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.
Why change from qual
to tree
? Also, no need to specify the position. It defaults to tree.pos
.
@@ -843,7 +843,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => | |||
|
|||
/** Overridden in ReTyper to handle primitive operations that can be generated after erasure */ | |||
protected def handleUnexpectedFunType(tree: untpd.Apply, fun: Tree)(implicit ctx: Context): Tree = | |||
throw new Error(i"unexpected type.\n fun = $fun,\n methPart(fun) = ${methPart(fun)},\n methPart(fun).tpe = ${methPart(fun).tpe},\n tpe = ${fun.tpe}") | |||
throw new Error(i"unexpected type.\n fun = $fun,\n methPart(fun) = ${methPart(fun)},\n methPart(fun).tpe = ${methPart(fun).tpe},\n tpe = ${fun.tpe}", tree.pos) |
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 change is very suspicious. This is not the same Error
as before. This is due to the wildcard import:
import dotty.tools.dotc.reporting.diagnostic.messages._
Only import what you need
@@ -2117,4 +2117,22 @@ object messages { | |||
override def msg: String = hl"""normal case class cannot extend an enum. case $cls in ${cls.owner} is extending enum ${parent.name}.""" | |||
override def explanation: String = "" | |||
} | |||
|
|||
case class NotAnExtractor(tree: tpd.Tree)(implicit ctx: Context) extends Message(NotAnExtractorID) { | |||
override def msg: String = hl"""${tree.show} cannot be used as an extractor in a pattern because it is not implemented""" |
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 need for triple quote. Also .show
is not needed with the hl
interpolator
hl"""| ${tree.show} is not a valid extractor for use in pattern matching, | ||
| this happens when the method unapply or unapplySeq it is not declared | ||
| on it's object companion. | ||
| An unapply method take an object and tries to give back the arguments |
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 one use case among others. E.g.
object PositiveInt {
def unapply(x: Int) = x >= 0
}
def foo(x: Int) = x match { case PositiveInt() => ...
I would replace your explanation and example by something that explain what is a valid extractor object. A brief summary of https://docs.scala-lang.org/tour/extractor-objects.html would be awesome
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 replaced the example with some usages of Extractors, I did not include code examples for each to not become length. Also, I thought about adding the required return types but this error is already checked at Applications.scala#L99
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, I didn't mean to add more use cases but explain how one can write a valid extractor object. E.g.
def explanation =
hl"""|An `unapply` method should be defined as follow:
| - If it is just a test, return a `Boolean`. For example `case even()`
| - If it returns a single sub-value of type T, return an `Option[T]`
| - If it returns several sub-values T1,...,Tn, group them in an optional tuple `Option[(T1,...,Tn)]`
|
|Sometimes, the number of sub-values isn’t fixed and we would like to return a sequence.
|For this reason, you can also define patterns through `unapplySeq` which returns `Option[Seq[T]]`.
|This mechanism is used for instance in pattern `case List(x1, ..., xn)`"""
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.
Got it! Should we also point that the unapply method should be defined in an object ? E.g.
An `unapply` method should be defined in an `object` as follow:
I think of cases where the user uses a plain class instead of an object, then tries to define the method inside the class and still get the error
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, sounds good!
}.expect { (ictx, messages) => | ||
implicit val ctx: Context = ictx | ||
assertMessageCount(2, messages) | ||
assertTrue(messages(1).isInstanceOf[NotAnExtractor]) |
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.
We also try to test the content of the error message. In your case, you could do something like
val NotAnExtractor(tree) = messages.head
assertEqual("Foo", tree.show)
Thanks for the reviews guys, I'll look into each and push the changes |
@allanrenucci Any further tips, especially on the explanation, are welcome! |
@@ -2118,21 +2118,19 @@ object messages { | |||
override def explanation: String = "" | |||
} | |||
|
|||
case class NotAnExtractor(tree: tpd.Tree)(implicit ctx: Context) extends Message(NotAnExtractorID) { | |||
override def msg: String = hl"""${tree.show} cannot be used as an extractor in a pattern because it is not implemented""" | |||
case class NotAnExtractor(tree: Trees.Tree[Untyped])(implicit ctx: Context) extends Message(NotAnExtractorID) { |
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.
Trees.Tree[Untyped]
is untpd.Tree
override def kind: String = "Syntax" | ||
override def explanation: String = | ||
hl"""| ${tree.show} is not a valid extractor for use in pattern matching, | ||
hl"""| $tree is not a valid extractor for use in pattern matching, | ||
| this happens when the method unapply or unapplySeq it is not declared | ||
| on it's object companion. |
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 remove the first sentence of the explanation. When the error is reported with -explain
, we print both msg
and explanation
. So the explanation should give additional information and not rephrase msg
hl"""| ${tree.show} is not a valid extractor for use in pattern matching, | ||
| this happens when the method unapply or unapplySeq it is not declared | ||
| on it's object companion. | ||
| An unapply method take an object and tries to give back the arguments |
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, I didn't mean to add more use cases but explain how one can write a valid extractor object. E.g.
def explanation =
hl"""|An `unapply` method should be defined as follow:
| - If it is just a test, return a `Boolean`. For example `case even()`
| - If it returns a single sub-value of type T, return an `Option[T]`
| - If it returns several sub-values T1,...,Tn, group them in an optional tuple `Option[(T1,...,Tn)]`
|
|Sometimes, the number of sub-values isn’t fixed and we would like to return a sequence.
|For this reason, you can also define patterns through `unapplySeq` which returns `Option[Seq[T]]`.
|This mechanism is used for instance in pattern `case List(x1, ..., xn)`"""
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.
LGTM. Thanks @sleepiejohn 🎉! Can you squash your commits into a single one?
this commit adds a message and explanation by unapply method on class' object companion, causing the compiler not founding a way to extract the arguments
Done! 😀 |
this commit adds a message and explanation by
#1589 instructions for when the user do not implement the
unapply method on class' object companion, causing the compiler
not founding a way to extract the arguments.