2

I have a case class Disconnect(nodeId: PublicKey) that has 1 parameter in it, however in some other part of the code it happened to be used without the parameter i.e: Disconnect and the compiler did not catch the error, note that I've also tried to run the compiler with -Xlint option and it still can't catch the error.

  • scala version: 2.11.12
  • target jvm version: 1.8
  • code compiled with options: -deprecation -feature -language:postfixOps -language:implicitConversions -Xfatal-warnings -unchecked -Xmax-classfile-name 140 -nobootcp

[history] It used to be case object Disconnect but at some point it has been changed to case class and a parameter was added, in the code it was still instantiated parameterless and the compiler couldn't notice it. I tried adding -Xlint option to the compiler but it didn't help.

In Peer.scala

  object Peer { 
    // other code
    case class Disconnect(nodeId: PublicKey)
    // more code
  }  

In Channel.scala

  // inside a function
  revocationTimeout.peer ! Peer.Disconnect
  //

I expected the compiler to catch the misuse of the case class and fail to compile.

Edit: thanks all for the replies, indeed the compiler is doing his job just fine and Disconnect is being used as a type instead of case class instance, this is possible because it's used in a function that accepts Any as parameter.

3 Answers3

2

Since you declare Disconnect to be a case class, the compiler automatically generates a companion object Disconnect, which holds all the neat apply and unapply methods. Therefore, Peer.Disconnect is a perfectly valid expression of the singleton type Peer.Disconnect.type. One might argue that it wouldn't have happened if you used Akka Typed right from the beginning, but in your code, the ! method accepts anything, so in order to force the compiler to emit some meaningful error messages, you need something else. Here is one simple approach:

  1. Revert to the state where Disconnect was a singleton object, without an associated case class.
  2. Remove the Disconnect definition altogether. Add case class NewDisconnect instead. Now every occurrence of Peer.Disconnect will become a proper error.
  3. Replace all Peer.Disconnect by Peer.NewDisconnect(foo)
  4. Rename NewDisconnect to Disconnect.
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
1

The problem is not in the compiler but in the method ! which accepts Any as argument:

def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit

so we could change the argument to anything and it would still compile, for example,

revocationTimeout.peer ! "woohoo"  // compiles OK!

On the other hand if we look at the corresponding Akka Typed ! method

implicit final class ActorRefOps[-T](val ref: ActorRef[T]) extends AnyVal {
    def !(msg: T): Unit = ref.tell(msg)
}

then we see it is parameterised with type parameter T and the compiler would catch it.

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
1

I assume that ! is the tell operator from akka actors.

It's signature is

def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit

So you can send to it literally anything, in this case you are sending the type Disconnect. This is one of the greatest disadvantages from using akka actors, that's why there is a new module akka typed where you define a type-safe behavior for your actors.

You may wonder why this doesn't blow up in runtime if you are sending an object that you don't expect. The reason is that actor's receive is a PartialFunction[Any, Unit] that "discards" the messages that are not defined for the PF.

gabrielgiussi
  • 9,245
  • 7
  • 41
  • 71