A class imported from a companion not usable as the constructor parameter default value
Asked Answered
C

1

7

Consider following code:

object Main extends App {
  object Project {
    case class Config(rules: Seq[String] = Seq.empty)
  }

  import Project._

  //case class Project(root: String, config: Config) // compiles fine

  //case class Project(root: String, config: Project.Config = Project.Config()) // compiles fine

  case class Project(root: String, config: Config = Config()) // error: not found: type Config

}

Why does the last version not compile (same with Config = Config.apply())?

Comeuppance answered 8/4, 2017 at 11:58 Comment(4)
It looks like a bug to me.Akeyla
It probably has to do with the fact that case classes add some code in their companion object, so they have trouble accessing it before they are properly defined. However, you can access (implicitly defined) Project.apply in Project constructor, so it really looks weird to me.Dorsman
When I place companion object after the case class, it works.Comeuppance
Note: There was an issue with local case classes, where you need to place companion before the case class - see Scala: order of definition for companion object vs case classComeuppance
R
2

It is not clear to me if this is a bug or not, but here is why it produces an error:

Consider this, which works:

import Project._

object Project {
  case class Config()
}

case class Project(config: Config = Config())

When you add a default argument the compiler generates a method to calculate the value. When that value is a constructor default, that method is added to the companion object of the class. So the compiler will generate this method:

def <init>$default$1: Project.Config = Config() 

Which will get added to your Project object.

The Scala type checker generates an object tree of Contexts. Each context has a reference to the context of it's outer scope. So the generated method gets a context and that generated method's outer scope is the Project companion object.

When the type checker attempts to resolve Config() it traverses all the enclosing contexts and cannot find Config (I am not sure why, and this may be a bug).

Once it has exhausted the contexts it resolves the imports which has the import Project._! The type checker is happy because it can now traverse the imports and find the apply method.

Now when you move the import below Project:

object Project {
  case class Config()
}

import Project._

case class Project(config: Config = Config())

In this case the imports available to the generated method does not have the Project._ import (this may also be a bug), I'm assuming because it's below the object definition which is where the generated method lives. The type checker then throws an error because it can't find Config.

What appears to be happening is when the type checker is resolving Config() it needs the import above the Project companion object as it needs to process the import to be able to resolve it and unless the import is above Project that import is not in scope.

For those who wish to debug further take a look at Contexts.lookupSymbol which is where the lookup is happening

Reidreidar answered 8/4, 2017 at 17:25 Comment(1)
Placing import before the companion provides a nice (even if a bit strange) workaround.Comeuppance

© 2022 - 2024 — McMap. All rights reserved.