8000 SI-10154 Fix implicit search regression for term-owned objects by retronym · Pull Request #5654 · scala/scala · GitHub
[go: up one dir, main page]

Skip to content

SI-10154 Fix implicit search regression for term-owned objects #5654

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 1 commit into from
Feb 15, 2017

Conversation

retronym
Copy link
Member
@retronym retronym commented Jan 19, 2017

A recent change to fix lookup of companion implicits of term-owned
classes (#5550) caused a regression in the enclosed test case. The
previous approach of calling Scope#lookup(companionName) was
replaced by a lookup of the scope entry of the original name followed
by a narrower search lookup for the companion name, to ensure
that it was a true companion, and not just a same-named module
from defined at a different nested scope.

However, module class symbols are not themselves entered into
scopes, so the first part of the new scheme fails. We need to
add a special case modules here.

I've chosen to just call .sourceModule on module classes.
For module classes in the current run (all term owned symbols
will fall into this category), this amounts to using the value
of the field ModuleClassSymbol#module.

JIRA: https://issues.scala-lang.org/browse/SI-10154

A recent change  to fix lookup of companion implicits of term-owned
classes (scala#5550) caused a regression in the enclosed test case. The
previous approach of calling `Scope#lookup(companionName)` was
replaced by a lookup of the scope entry of the original name followed
by a narrower search lookup for the companion name, to ensure
that it was a true companion, and not just a same-named module
from defined at a different nested scope.

However, module class symbols are not themselves entered into
scopes, so the first part of the new scheme fails. We need to
add a special case modules here.

I've chosen to just call `.sourceModule` on module classes.
For module classes in the current run (all term owned symbols
will fall into this category), this amounts to using the value
of the field `ModuleClassSymbol#module`.
@scala-jenkins scala-jenkins added this to the 2.12.2 milestone Jan 19, 2017
@retronym
Copy link
Member Author

Trying a release build (which should include the community build)

@retronym retronym requested a review from adriaanm January 20, 2017 00:09
SethTisue added a commit to scala/community-build that referenced this pull request Jan 20, 2017
This reverts commit 6d935d4.

the problem Adriaan was investigating here turned out to be a Scala
regression, not a Shapeless bug.  see
scala/scala#5654
@SethTisue
Copy link
Member
SethTisue commented Jan 20, 2017

in Jenkins the repo_ref setting propagated also to the community build settings, which isn't right. manually triggered rebuild with repo_ref set to 2.12.x: https://scala-ci.typesafe.com/job/scala-2.12.x-integrate-community-build/1203/consoleFull

@SethTisue
Copy link
Member
[info] >>> The dbuild result is------------: FAILED (failed: spray-json-shapeless, argonaut, upickle-pprint)

the upickle-pprint failure is almost certainly unrelated.

the other two, it's harder to be sure. but, Shapeless being broken took a pretty big swath of the community build out for a couple months or so, so it wouldn't be surprising at all if a few projects randomly broke during that time for unrelated reasons and we're just learning about it now.

@SethTisue
Copy link
Member
SethTisue commented Jan 23, 2017

@sbuzzard has experienced some regressions in 2.12.1 that he's documented in this gist: https://gist.github.com/sbuzzard/e43ddf1c4c7c3a85e192785fc27dc220 (thanks for the careful minimization!). Steve, can you test your code against this PR and see if it fixes your issues? you should be able to use:

resolvers += "pr" at "https://scala-ci.typesafe.com/artifactory/scala-pr-validation-snapshots/"
scalaVersion := "2.12.2-1d41aef-SNAPSHOT"

@SethTisue
Copy link
Member

@fommil what do you make of the spray-json-shapeless failure...?

@SethTisue
Copy link
Member

I think the Argonaut failure cannot be this PR's fault — it's just failing to find a Scala-version-specific source directory.

@sbuzzard
Copy link
sbuzzard commented Jan 23, 2017

Still received the errors with this build. https://gist.github.com/sbuzzard/671bf1d7ee677151e52cedab94a8afed

@fommil
Copy link
Contributor
fommil commented Jan 24, 2017

Hmm, I'll need to investigate further. This is almost certainly going to cause trouble for typeclass derivation, but I'd be amazed if sjs fails but something like circe passes. Could be I was relying on something really fragile?

@sbuzzard
Copy link

I will say that we have similar constructs throughout our code base, with implicits for both scodec Codecs and circe Encoders and Decoders in the companions. We also have hundreds of instances where we have ADTs defined in the same compilation unit. But in only a few cases do we see the direct known subclass error and only in one case do we get the no such element exception. Not sure what it is about the structure of my minimized test case that causes this. In addition to the work arounds in my original gist, if the maybeOptionCodec in the minimized reproduction is not defined in the object where it is used but elsewhere, the no such element exception does not occur.

@retronym
Copy link
Member Author
retronym commented Jan 24, 2017

I've reduced @sbuzzard's test that results in the NoSuchElementException to:

import scodec.Codec

sealed trait TestType
object TestType {
  implicit val codec: Codec[TestType] = ???
}

case class Test(testType: TestType)
object Test {
  private def crossTalk: Unit = {
    shapeless.Lazy.mkLazy[Codec[TestType]]
    ()
  }
  implicit val codec: Codec[Test] = null
}

object Client {
  def crossTalk /*inferred*/ = {
    shapeless.Lazy.mkLazy[Codec[Test]]
    ()
  }
}

This expands to:

package <empty> {
  import scodec.Codec;
  sealed abstract trait TestType extends scala.AnyRef;
  object TestType extends scala.AnyRef {

    private[this] val codec: scodec.Codec[TestType] = scala.Predef.???;
    implicit <stable> <accessor> def codec: scodec.Codec[TestType] = TestType.this.codec
  };
  case class Test extends AnyRef with Product with Serializable {
  ...
  };
  object Test extends scala.AnyRef with Serializable {
 
    private def crossTalk: Unit = {
      {
        val inst$macro$4: scodec.Codec[TestType] = {
          final class anon$lazy$macro$3 extends AnyRef with Serializable {

            <stable> <accessor> lazy val inst$macro$2: scodec.Codec[Test] = Test.this.codec.asInstanceOf[scodec.Codec[Test]];
            <stable> <accessor> lazy val inst$macro$1: scodec.Codec[TestType] = TestType.codec.asInstanceOf[scodec.Codec[TestType]]
          };
          new anon$lazy$macro$3().inst$macro$1
        };
        shapeless.Lazy.apply[scodec.Codec[TestType]](inst$macro$4)
      };
      ()
    };
    private[this] val codec: scodec.Codec[Test] = null;
    implicit <stable> <accessor> def codec: scodec.Codec[Test] = Test.this.codec;
    case <synthetic> def apply(testType: TestType): Test = new Test(testType);
    case <synthetic> def unapply(x$0: Test): Option[TestType] = if (x$0.==(null))
      scala.None
    else
      Some.apply[TestType](x$0.testType);
    <synthetic> private def readResolve(): Object = Test
  };
  object Client extends scala.AnyRef {
    def crossTalk: Unit = {
      shapeless.Lazy.apply[scodec.Codec[Test]](inst$macro$2);
      ()
    }
  }
}

The ill-scoped reference to inst$macro$2 from Client.crossTalk results in a compiler crash in the backend when no such local variable is found.

I suspect this is a bug in the Shapeless mkLazy macro. Perhaps it is detecting recursive calls to the macro, but doesn't account for the that these might result from the out-of-order typechecking that can occur with methods with inferred types.

@milessabin
Copy link
Contributor

@retronym it looks like it's an interaction between Lazy and the SI-7046 fix in the presence of inferred method result types. Workaround is to annotate the result type. Investigating a fix in shapeless ...

@sbuzzard
Copy link
sbuzzard commented Jan 24, 2017

yep - we should be doing that anyway (at least in the cases where we've tripped over this). Adding the explicit result type works great to resolve the element not found error . There are still cases where I get the known direct subclass before subtype is registered with all obvious result types annotated. Thanks!

@milessabin
Copy link
Contributor
milessabin commented Jan 24, 2017

Actually, this change in the compiler seems to be the most straightforward fix,

diff --git src/compiler/scala/tools/nsc/typechecker/Namers.scala src/compiler/scala/tools/nsc/typechecker/Namers.scala
index 395bda234b..80730a83af 100644
--- src/compiler/scala/tools/nsc/typechecker/Namers.scala
+++ src/compiler/scala/tools/nsc/typechecker/Namers.scala
@@ -1856,6 +1856,7 @@ trait Namers extends MethodSynthesis {
     val tree: Tree
     override def forceDirectSuperclasses: Unit = {
       tree.foreach {
+        case DefDef(_, _, _, _, TypeTree(), _) =>
         case dt: DefTree => global.withPropagateCyclicReferences(Option(dt.symbol).map(_.maybeInitialize))
         case _ =>
       }

@@ -1194,7 +1194,8 @@ trait Contexts { self: Analyzer =>
}

final def lookupCompanionOf(original: Symbol): Symbol = {
Copy link
Contributor
@adriaanm adriaanm Jan 24, 2017

Choose a reason for hiding this comment

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

My head started spinning with indirection, so I figure I'll rubber-duck-review this by doing some inlining...

I tried to push all logic into lookupCompanionOf:

final def lookupCompanionOf(original: Symbol): Symbol =
  if (original == NoSymbol) NoSymbol else { // can we collapse the next few lines by using original.exists in the condition here?
    original.failIfStub()

    if (original.needsFlatClasses) original.info // use initialize() ?
    val flatOwnerInfo = original.owner.rawInfo

    // can we find it using the owner structure -- i.e., is it a member of some other symbol?
    val member =
      original match {
        case mcs: ModuleClassSymbol if mcs.module ne null => mcs.module
        case _ =>
          flatOwnerInfo.decl(original.name.companionName).suchThat(sym =>
            sym != original && (if (original.isClass) sym.isModule else sym.isClass) && sym.isCoDefinedWith(original)
          )
      }

    // can we decide which branch to take here directly rather than by trying the search?
    // i.e., is it a term-owned symbol??
    if (member == NoSymbol) 
      lookupScopeEntry(original) match {
        case null => NoSymbol
        case entry => entry.owner.lookupCompanion(original)
      }
    else member
  }

Copy link
Member Author
@retronym retronym Jan 24, 2017

Choose a reason for hiding this comment

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

can we decide which branch to take here directly rather than by trying the search? i.e., is it a term-owned symbol??

I think we also get into this code path for class-owned symbols where the class info is incomplete. So the check would need to be something like owner.isTerm || !owner.hasCompleteInfo

@SethTisue
8000 Copy link
Member

I would like to see this PR move forward, in order to fix the Scala 2.12 community build, in order to ensure the quality of the Scala 2.12.2 release, which is now probably only a few weeks off. @retronym, is there anything I can do to help?

@retronym
Copy link
Member Author
retronym commented Feb 9, 2017

@adriaanm I pushed a fix for the build errors in the last commit. Do you prefer it to the original commit?

@milessabin
Copy link
Contributor

@retronym does that fix your reduced version of @sbuzzard's example?

@adriaanm adriaanm merged commit 4ae3750 into scala:2.12.x Feb 15, 2017
@retronym
Copy link
Member Author
retronym commented Feb 15, 2017

I've moved the refactoring to #5700 so we can fast track the regression fix.

@milessabin
Copy link
Contributor

@retronym pinging you again on my earlier question: does the current state of the fix deal with your reduced version of @sbuzzard's example?

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.

7 participants
0