Skip to content

Fix #4709: report if local implicits need type annotation #4729

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 9 commits into from
Aug 11, 2018

Conversation

Blaisorblade
Copy link
Contributor

@Blaisorblade Blaisorblade commented Jun 27, 2018

Fix #4709 and #3253, and revisit fixes to #2001.

Copy link
Contributor

@allanrenucci allanrenucci left a comment

Choose a reason for hiding this comment

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

I would do a more generic solution that also handles #3253

def test(ctx0: Context) = {
implicit val ctx = { ctx0.settings; ??? } // error
}
}
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 make it an error message test as well

@Blaisorblade
Copy link
Contributor Author

Memo: both here and in #3253, implicit search fails because it tries to force the type of the member we're typechecking — whether the method is actually implicit or not. In #3253, we produce OverloadedOrRecursiveMethodNeedsResultType without checking if there's actually any overload/recursion. Ditto for RecursiveValueNeedsResultType.

It seems we should check, instead, if the failure is in implicit resolution or not (not sure how, ctx.searchHistory.searchDepth > 0 only finds nested implicit searches; I might have to record more info), maybe what tree we're looking at. If we're not doing implicit search, then we can use the current errors.

@Blaisorblade Blaisorblade self-assigned this Jun 27, 2018
@Blaisorblade Blaisorblade force-pushed the fix-4709 branch 3 times, most recently from 1be9a81 to 7c51b08 Compare July 5, 2018 19:46
@Blaisorblade

This comment has been minimized.

@Blaisorblade
Copy link
Contributor Author

Memo for future explorers: if you really really want, it might be possible to find all the symbols involved in a cycle, but at least for now we decided not to try.

This would work following the idea of #4385: we'd recording more information on the CyclicReference as it traverses the stack outward — one would have to catch it and rethrow it in SymDenotation.completeFrom.

@@ -49,7 +49,8 @@ object NameOps {
}
}

implicit class NameDecorator[N <: Name](val name: N) extends AnyVal {
implicit class NameDecorator[N <: Name](val name_ : N) extends AnyVal {
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 make it private:

-  implicit class NameDecorator[N <: Name](val name: N) extends AnyVal {
+  implicit class NameDecorator[N <: Name](private val name: N) extends AnyVal {
     import nme._
 
     def testSimple(f: SimpleName => Boolean): Boolean = name match {
@@ -233,12 +233,12 @@ object NameOps {
       case nme.clone_ => nme.clone_
     }
 
-    def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): name.ThisName = {
+    def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): N = {
 
       val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => defn.typeTag(x._1))
       val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => defn.typeTag(x._1))
 
-      name.likeSpaced(name ++ nme.specializedTypeNames.prefix ++
+      likeSpaced(name ++ nme.specializedTypeNames.prefix ++
         methodTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.separator ++
         classTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix)

class CyclicReference private (val denot: SymDenotation) extends TypeError {
var inImplicitSearch: Boolean = false
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you make this a parameter of the class and then do something like:

def withinImplicitSearch =
  if (inImplicitSearch) this
  else new CyclicReference(denot, inImplicitSearch = true)

...

} catch {
  case ce: CyclicReference =>
    throw ce.withinImplicitSearch
}

I'm not the best to come up with method 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.

The new Exception has a different stack trace and other state. I could try to copy the various relevant fields (I spent some time investigating), but that seems more fragile than having one mutable field.

}
} catch {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need to wrap the whole method? Can't you do:

val result =
  try new ImplicitSearch(pt, argument, pos).bestImplicit(contextual = true)
  catch {
    case ce: CyclicReference =>
      ...
  }

val unsafeFlags = cycleSym.flagsUNSAFE
val isMethod = unsafeFlags.is(Method)
val isVal = !isMethod && cycleSym.isTerm
val isImplicit = unsafeFlags.is(Implicit)
Copy link
Contributor

Choose a reason for hiding this comment

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

unused?

@Blaisorblade

This comment has been minimized.

Copy link
Contributor

@allanrenucci allanrenucci 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. Sorry for the late review @Blaisorblade

else if (isVal)
RecursiveValueNeedsResultType(tree.name, cycleSym)
else
errorMsg(cx.outer)
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes I think we should remove all of this if tree.name is unused and we only use the cycleSym

val explanation =
hl"""The definition of `${value}` is recursive and you need to specify its type.
hl"""The definition of `${cycleSym}` is recursive and you need to specify its type.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: here and in some of the error messages above s/${cycleSym}/$cycleSym

@allanrenucci allanrenucci removed their assignment Aug 3, 2018
@Blaisorblade Blaisorblade self-assigned this Aug 3, 2018
@@ -1260,26 +1260,27 @@ object messages {
|""".stripMargin
}

case class OverloadedOrRecursiveMethodNeedsResultType(method: Names.TermName)(implicit ctx: Context)
case class OverloadedOrRecursiveMethodNeedsResultType(methodName: Names.TermName, cycleSym: Symbol)(implicit ctx: Context)
extends Message(OverloadedOrRecursiveMethodNeedsResultTypeID) {
val kind = "Syntax"
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, and we should probably classify OverloadedOrRecursiveMethodNeedsResultType, RecursiveValueNeedsResultType, TermMemberNeedsResultTypeForImplicitSearch, CyclicReferenceInvolvingImplicit and CyclicReferenceInvolving as Cyclic errors not Syntax

Tentative advice copied from `CyclicReferenceInvolvingImplicit`.
…better

- Record in CyclicReference if it's emitted during implicit search.
- Before checking if the error is on an implicit, check if we have an error
  during implicit search that happens while inferring a return type; that can
  happen whether the member we're typechecking is implicit or not, and needs the
  same cure.
- Add ErrorMessagesTests as requested

This improves the error message, and also puts more effort into diagnosing the
cycle: we now use similar logic as for recursive/overloaded members to check
we're indeed dealing with a (potentially) recursive implicit.

Remaining `CyclicReferenceInvolvingImplicit` are unexpected and might involve
bugs, so make the error message and explanation more tentative, following
`CyclicReferenceInvolving`.

*However*, this does not yet handle mutually recursive methods in a reasonable
way.
Make reports about cycleSym not tree.name. We need then to use flagsUNSAFE, see
description.

* Store cycleSym in all relevant errors
* Mention cycleSym instead of tree.name in errors

Tests pass, and have the correct expectations both for `tree.name` and
`cycleSym.name`, tho we just show `cycleSym.name` and `tree.name` is less
accurate.

But at this point, I might be able to stop using `tree.name` altogether.
@Blaisorblade
Copy link
Contributor Author

All comments addressed, so merging.

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.

Cyclic reference involving implicit value
2 participants