Skip to content

Fix #3797: Add support for @implicitAmbiguous #4007

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 4 commits into from
Mar 21, 2018

Conversation

Jasper-M
Copy link
Contributor

@Jasper-M Jasper-M commented Feb 16, 2018

There are no tests yet. Can I add a test to https://github.com/lampepfl/dotty/blob/master/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala if this error doesn't have a dedicated case class yet?

Dotc will not warn like scalac when the user defined error message references a non-existent type parameter, but that's consistent with @implicitNotFound in dotty.

Copy link
Member

@dottybot dottybot left a 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! ☀️

raw,
pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
pt.argInfos)
alt.tstate.commit()
Copy link
Member

Choose a reason for hiding this comment

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

We should try avoiding committing a TyperState with errors if possible. Instead you might be able to make a new Context with the correct TyperState to report the error: val altCtx = ctx.fresh.setTyperState(alt.tstate), and then move the code below to be in a method that takes an implicit ctx: Context and give it altCtx as argument.

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.

@smarter
Copy link
Member

smarter commented Feb 16, 2018

f this error doesn't have a dedicated case class yet?

I suggest just making a case class for the error, it doesn't have to be very fancy, e.g. it's okay if def explanation is left empty.

@smarter
Copy link
Member

smarter commented Feb 16, 2018

Dotc will not error out like scalac when the user defined error message references a non-existent type parameter, but that's consistent with @implicitnotfound in dotty.

Could you open an issue for that?

@Jasper-M
Copy link
Contributor Author

I suggest just making a case class for the error, it doesn't have to be very fancy, e.g. it's okay if def explanation is left empty.

That would be a general ImplicitNotFound message then? Untangling the different subtypes of errors in missingArgMsg looks difficult to me, especially as I don't really understand the distinction between case arg: Trees.SearchFailureIdent[_] and case _ underneath it.

@Jasper-M
Copy link
Contributor Author

Could you open an issue for that?

It's actually a warning apparently. But I'll open an issue.

@smarter
Copy link
Member

smarter commented Feb 16, 2018

That would be a general ImplicitNotFound message then?

Sure, but you could have an AmbiguousImplicit subclass or something.

@@ -696,22 +697,46 @@ trait Implicits { self: Typer =>
}
}
def location(preposition: String) = if (where.isEmpty) "" else s" $preposition $where"
def userDefinedMessage(annot: Annotation, params: List[String], args: List[Type]): Option[String] =
for(Trees.Literal(Constant(raw: String)) <- annot.argument(0)) yield {
Copy link
Member

Choose a reason for hiding this comment

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

space after for

case TypeApply(_, targs) if params.nonEmpty =>
def resolveTypes(targs: List[tpd.Tree])(implicit ctx: Context) = {
targs.map(a => fullyDefinedType(a.tpe, "type parameter", a.pos))
}
Copy link
Member

Choose a reason for hiding this comment

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

No need for braces since the body is an expression.

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 thought it was better for readability in this case, but your call.

@Jasper-M
Copy link
Contributor Author

I added tests, but I think it's better if I don't add a half assed custom Message case class in this pull request. I think it's ready for (final) review.

@@ -159,11 +159,11 @@ object ErrorReporting {
TypeMismatch(found2, expected2, whyNoMatchStr(found, expected), postScript)
}

/** Format `raw` implicitNotFound argument, replacing all
* occurrences of `${X}` where `X` is in `paramNames` with the
/** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like this function might be useful for more annotations (@smarter mentioned a couple more ones on Gitter), as suggested by the new name you picked, but not by the comment. What do you think?

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 sort of agree. I'm open to suggestions 😅

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's not worth overgeneralizing, we can change this when we need to.

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 may have misinterpreted your question a little bit. It is useful for all annotations whose messages can reference type parameters, which could potentially be more than the 2 listed here, but not all annotations carrying a custom message. I'm open to suggestions for a better name.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, I suspect the name is fine and would write "Format raw user-defined error message" in the doc. WDYT? Anyway, this isn't critical, just nice to have. (Warning, I've been told I'm a perfectionist).

@@ -696,22 +697,45 @@ trait Implicits { self: Typer =>
}
}
def location(preposition: String) = if (where.isEmpty) "" else s" $preposition $where"
def userDefinedMessage(annot: Annotation, params: List[String], args: List[Type]): Option[String] =
for (Trees.Literal(Constant(raw: String)) <- annot.argument(0)) yield {
Copy link
Contributor

Choose a reason for hiding this comment

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

Wondering about the magic constant 0, I guess because the string is the 0th argument for all annotations currently supported? But if you try to make userDefinedMessage a good abstraction, maybe annot.argument(0) should move to the call site (even if, indeed, it gets duplicated).

ambi.alt2.ref.symbol.getAnnotation(defn.ImplicitAmbiguousAnnot).map(
(_, ambi.alt2)
)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, can this be abstracted with for { alt <- List(ambi.alt1, ambi.alt2) } .... alt.ref.symbol...? Or better, in terms of a new ambi.alts?

(Except if people feel this is too slow and we're in a performance-critical path).

Actually surprised the error only lists two implicits, apparently since this class' birth (in ea640a3) only the first two candidate implicits are reported.

Potential bug (not blocking the PR): if you have three equally good candidates that are ambiguous, the @implicitAmbiguous annotation will be ignored on the third candidate, which is against the specification in http://www.scala-lang.org/api/2.12.3/scala/annotation/implicitAmbiguous.html. If that bug can indeed be triggered (haven't tried, but it's not clear why that shouldn't happen), fixing it seems to require a refactoring of the search algorithm to return all the ambiguous candidates — which is, again, out of scope for the PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I also thought about this. I think the Scalac implementation has the same limitation. I suspect the most realistic fix would probably be in the documentation of @implicitAmbiguous.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, fair enough — this wasn't a well-known issue around here. Not sure how to specify it,
so for now I've opened scala/bug#10739?

For the PR, please go ahead!

val userDefined = maybeAnnot.flatMap { case (annot, alt) =>
val params = alt.ref.underlying match {
case lambda: TypeLambda => lambda.typeParams.map(_.paramName.unexpandedName.toString)
case _ => Nil
Copy link
Contributor

Choose a reason for hiding this comment

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

@allanrenucci Couldn't quite review code from here on, please take a look. Otherwise LGTM.

)
val userDefined = maybeAnnot.flatMap { case (annot, alt) =>
val params = alt.ref.underlying match {
case lambda: TypeLambda => lambda.typeParams.map(_.paramName.unexpandedName.toString)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you can do:

case p: PolyType => p.paramNames.map(_.toString)

pt.argInfos)
val args = alt.tree match {
case TypeApply(_, targs) if params.nonEmpty =>
def resolveTypes(targs: List[tpd.Tree])(implicit ctx: Context) =
Copy link
Contributor

Choose a reason for hiding this comment

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

You can remove the tpd prefix

}
userDefinedMessage(annot, params, args)
}
userDefined.map(msg(_)()).getOrElse(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think here .map(msg(_)()) does nothing and can be removed

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 very likely. Is the same true for the ImplicitNotFound case?

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually I am not sure anymore. I revert what I said before. Let's be conservative and also wrap the user defined message in msg(_)()

pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
pt.argInfos)
val args = alt.tree match {
case TypeApply(_, targs) if params.nonEmpty =>
Copy link
Contributor

Choose a reason for hiding this comment

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

ambi.alt2.ref.symbol.getAnnotation(defn.ImplicitAmbiguousAnnot).map(
(_, ambi.alt2)
)
)
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 do it the other way around. First look for a user defined annotation message and then try try to recover type params only if needed:

def userDefinedMsg(sym: Symbol, cls: Symbol) = for {
  ann <- symbol.getAnnotation(cls)
  Trees.Literal(Constant(msg: String)) <- ann.argument(0)
} yield msg

object AmbiguousImplicitMsg {
  def unapply(search: SearchSuccess) =
    userDefinedMsg(search.ref.symbol, defn.ImplicitAmbiguousAnnot)
}

(ambi.alt1, ambi.alt2) match {
  case (AmbiguousImplicitMsg(msg), _) => ... // recover type params from alt1
  case (_, AmbiguousImplicitMsg(msg)) => ... // recover type params from alt2
  case _ =>
    msg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")(
        s"ambiguous implicit arguments of type ${pt.show} found${location("for")}")
}

@Jasper-M
Copy link
Contributor Author

Jasper-M commented Feb 21, 2018

How are you supposed to handle LazyTrees? If I assign them a type ascription in a pattern match (rhs: Tree) I get an error because of an unchecked pattern. If I try to match it with a pattern (Apply(TypeApply(_, targs), _)) and then do targs.map(_.tpe) I get a ClassCastException: TypeVar cannot be cast to Null$, nor can I use targs where a List[tpd.Tree] is expected (while it obviously is, hence the exception).

Am I just supposed to asInstanceOf it? Is this a bug that I should file? (it sure looks like it... but I can't be the first to bump into this?)

@Jasper-M Jasper-M force-pushed the topic/implicit-ambiguous branch from 93e7013 to 4261c6a Compare February 21, 2018 12:34
@Jasper-M Jasper-M force-pushed the topic/implicit-ambiguous branch from 4261c6a to a550d09 Compare March 6, 2018 10:40
@Jasper-M
Copy link
Contributor Author

Jasper-M commented Mar 6, 2018

As far as I'm concerned this is pretty much ready (except for the cast maybe). Please let me know if there's still something that is blocking. Or if there's a better way to handle the LazyTree thing.

@Blaisorblade
Copy link
Contributor

Blaisorblade commented Mar 6, 2018

@Jasper-M what about the CI failure?
(EDIT: we might also be late, and sorry about that.)

@Jasper-M
Copy link
Contributor Author

Jasper-M commented Mar 6, 2018

what about the CI failure?

Not a clue. There should be no difference between this commit and the one that previously succeeded.
My guess is either it's spurious or a rebase or somesuch is required. Someone who understands the CI job and test framework better will have to point me in the right direction.

@Blaisorblade
Copy link
Contributor

OK, we’ll take a look on our side. Delays are possible, but we’ll do our best.

@Blaisorblade
Copy link
Contributor

(Build restarted, it was weird that only test failed.)

@allanrenucci allanrenucci force-pushed the topic/implicit-ambiguous branch from a550d09 to d656ff2 Compare March 20, 2018 16:53
@allanrenucci allanrenucci force-pushed the topic/implicit-ambiguous branch from cbe5d9c to a1732a7 Compare March 20, 2018 17:00
@allanrenucci allanrenucci force-pushed the topic/implicit-ambiguous branch from e0ffb5e to 9498dee Compare March 21, 2018 13:48
pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
pt.argInfos)
object AmbiguousImplicitMsg {
def unapply(search: SearchSuccess) =
Copy link
Member

Choose a reason for hiding this comment

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

Write down the result type of this method for clarity.

userDefinedMsg(search.ref.symbol, defn.ImplicitAmbiguousAnnot)
}

def userDefinedAmbiguousImplicitMsg(alt: SearchSuccess, raw: String) = {
Copy link
Member

Choose a reason for hiding this comment

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

Add a documentation comment for this method.

@@ -685,22 +686,54 @@ trait Implicits { self: Typer =>
}
}
def location(preposition: String) = if (where.isEmpty) "" else s" $preposition $where"

def userDefinedMsg(sym: Symbol, cls: Symbol) = for {
Copy link
Member

Choose a reason for hiding this comment

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

Add a documentation comment for this method.

}

(ambi.alt1, ambi.alt2) match {
case (AmbiguousImplicitMsg(msg), _) =>
Copy link
Member

Choose a reason for hiding this comment

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

If you write alt @ AmbiguousImplicitMsg(msg), you can use alt below instead of ambi.alt1, same in the next case.

@allanrenucci allanrenucci force-pushed the topic/implicit-ambiguous branch from 9498dee to 86ecede Compare March 21, 2018 14:00
@allanrenucci
Copy link
Contributor

Merging! Thanks @Jasper-M 🎉

@allanrenucci allanrenucci merged commit 0c5e39d into scala:master Mar 21, 2018
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.

5 participants