Scala Programming Language
  1. Scala Programming Language
  2. SI-6490

Wrong error: Companions Foo and Foo must be defined in the same source file

    Details

      Description

      I spent some time debugging the spurious error we started to get recently in the presentation compiler. It is about 'Companions X and X must be defined in the same file'. You can see one user complaining here: https://groups.google.com/d/msg/scala-internals/aPGxqZSyvYg/W5yo9TKKMl4J

      In my tests, I can reproduce it with any class in the Empty package (but not only there).

      • my source is just 'class Foo {}'
      • I don't define a companion myself, nor should there be one. Am I right?
      • the source files that are listed in the error message are the same

      I traced it back to commit 816cecf9a95989dfc570f2acad87d4156b09e2ff. Reverting this made the problem go away (I'm not suggesting that, just a data point).
      Debugging lead to the following event:

      • the immediate cause is that 'isCoDefinedWith' returns false for the two symbols
      • it returns false because the first test fails (this.rawInfo ne NoType)

      NOTE: This looks asymmetrical. Is that intended? If I call 'that isCoDefinedWith this' I might get a different result..

      • when the class is entered, it goes through updatePosFlags (https://github.com/scala/scala/blob/master/src/compiler/scala/tools/nsc/typechecker/Namers.scala#L135)
        The code looks like this:
                companionSymbolOf(sym, context) andAlso { companion =>
                  val assignNoType = companion.rawInfo match {
                    case _: SymLoader => true
                    case tp           => tp.isComplete && (runId(sym.validTo) != currentRunId)
                  }
                  // pre-set linked symbol to NoType, in case it is not loaded together with this symbol.
                  if (assignNoType)
                    companion setInfo NoType
                }
        

      This code makes companions that were companions (the call to `companionSymbolOf` just returned my companion) not be companions anymore. This is what I'm witnessing in the IDE.

        Activity

        Hide
        Paul Phillips added a comment -

        That logic is at least 7 years old: https://github.com/scala/scala/commit/322068fb8a1e

        +      if (sym.owner.isPackageClass &&
        +          (sym.linkedSym.rawInfo.isInstanceOf[loaders.SymbolLoader] ||
        +           sym.linkedSym.rawInfo.isComplete && sym.validForRun != currentRun))
                 // pre-set linked symbol to NoType, in case it is not loaded together with this symbol.
                 sym.linkedSym.setInfo(NoType);
        
        Show
        Paul Phillips added a comment - That logic is at least 7 years old: https://github.com/scala/scala/commit/322068fb8a1e + if (sym.owner.isPackageClass && + (sym.linkedSym.rawInfo.isInstanceOf[loaders.SymbolLoader] || + sym.linkedSym.rawInfo.isComplete && sym.validForRun != currentRun)) // pre-set linked symbol to NoType, in case it is not loaded together with this symbol. sym.linkedSym.setInfo(NoType);
        Hide
        Iulian Dragos added a comment -

        For future generations of compiler engineers:

        • companions for existing classes are created by the symbol loader, and they may in fact not exist. When the class is loaded, if there is no companion its companion symbol type is set to `NoType` (the symbol can't be wiped out anymore)
        • The logic to set it to `NoType` is therefore correct
        • The fix was in the validation check: before calling `isCoDefinedWith`, check that both symbols exist
        • isCoDefinedWith remains asymmetrical. My attempts to "fix" it hit too many errors/crashes in the compiler. It's caller's responsibility to make sure the symbols exist before calling `isCoDefinedWith`.
        Show
        Iulian Dragos added a comment - For future generations of compiler engineers: companions for existing classes are created by the symbol loader, and they may in fact not exist. When the class is loaded, if there is no companion its companion symbol type is set to `NoType` (the symbol can't be wiped out anymore) The logic to set it to `NoType` is therefore correct The fix was in the validation check: before calling `isCoDefinedWith`, check that both symbols exist isCoDefinedWith remains asymmetrical. My attempts to "fix" it hit too many errors/crashes in the compiler. It's caller's responsibility to make sure the symbols exist before calling `isCoDefinedWith`.
        Show
        Josh Suereth added a comment - https://github.com/scala/scala/pull/1481
        Hide
        Josh Suereth added a comment -

        I believe the patch fixed the issue, so... yay.

        Show
        Josh Suereth added a comment - I believe the patch fixed the issue, so... yay.

          People

          • Assignee:
            Martin Odersky
            Reporter:
            Iulian Dragos
          • Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development