8

Given

case class Fruit(name: String, colour: String)

I'd like to deprecate the case class field colour.

I tried to achieve the former by both employing Scala @deprecated

case class Fruit(name: String, @deprecated("message", "since") colour: String)

and Java @Deprecated annotation

case class Fruit(
  name: String, 
  /* @Deprecated with some message here */
  @(Deprecated @field) 
  colour: String
)

Unfortunately I wasn't able to make colour deprecated in any case and I'm not able to find any resource on that.

Indeed, I can achieve the same task using other approaches, e.g. by relaxing my case class to a normal class and provide a getter for colour and make the latter deprecated, but I would like to know if deprecating case class fields is actually not possible and -- eventually -- the reason why.

Thank you.


UPDATE

As Mike rightly pointed out, colour is a required field for a case class, so the sense of setting this attribute to deprecated is arguable. I'd like to deprecate colour , since I'm now providing colour information from another construct, I want to keep the user knowing that colour is not the right instance by where fetch that information, and I will remove it from Fruit in next releases.

By now, I'd just like warn users to not fetch colour information from the Fruit attribute and, temporarily, I don't really care if they can create instances of fruit with colour information.


UPDATE 2

As Alexey said, I do have the deprecated warning at compile time. But why can't I observe the deprecation in my IDE code as well as I'd deprecating a method, then? I'd expect something like the following

val fruit = Fruit("peer", "green")
val colour = fruit.colour

I'm using IntelliJ.

spi-x-i
  • 287
  • 1
  • 12
  • 1
    Thanks for the clarification! However, I'm still a little confused. Can you post a representation of how you're providing colour information using the other construct? If we knew what you want the class to look like in the future, it would help us craft better answers. – Mike Allen Mar 20 '18 at 15:26
  • For the update: it just seems to be an IntelliJ bug and should be reported there. – Alexey Romanov Mar 20 '18 at 17:52
  • 1
    And in general, _don't_ trust IntelliJ to report errors and warnings correctly for Scala without compiling to check. – Alexey Romanov Mar 20 '18 at 18:01
  • OK @AlexeyRomanov; can you kindly edit your answer with this latter update? I'll accept your answer since you demonstrated that deprecating case class fields is actually possible. I opened an issue on YouTrack [here](https://youtrack.jetbrains.com/issue/SCL-13533). – spi-x-i Mar 20 '18 at 18:47
  • @spi-x-i Updated. – Alexey Romanov Mar 21 '18 at 06:09

3 Answers3

6

This is an odd use case. If a required field is deprecated, then the class itself must be deprecated too, surely? That is, assuming what you're trying to do is possible, it's impossible to use the class at all without getting a deprecation warning. (This probably explains why it's not possible.)

That said, as you indicate, there are ways to achieve what you're trying to do. Here's one of them:

case class Fruit(name: String) {

  @deprecated("message", "since")
  val colour: String = "some default value"
}

object Fruit {

  // Deprecated factory method.
  @deprecated("message", "since")
  def apply(name: String,  colour: String): Fruit = Fruit(name)
}

Clearly, this assumes that colour is no longer required as an attribute of Fruit. (If it is required, it should not be deprecated.) It works by deprecating the factory method that creates a Fruit using a colour value.

If this doesn't work, can you explain why the field needs to be deprecated?

Mike Allen
  • 8,139
  • 2
  • 24
  • 46
  • Hi Mike. Thank you for the answer. I perfectly got your point. I updated my question to clarify my use case. – spi-x-i Mar 20 '18 at 15:17
5

Your first version seems to work fine: if you paste

case class Fruit(name: String, @deprecated("message", "since") colour: String)

println(Fruit("", "").colour)

at https://scastie.scala-lang.org/, you'll see the deprecation warning. Of course, you need to access the field, whether directly or through an extractor:

Fruit("", "") match { case Fruit(x, y) => y }

will also warn.

That IDEA doesn't show colour as deprecated is I believe just a bug. In general it's quite unreliable in showing errors and warnings in Scala code inline and you should always check by actually building the project.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • 1
    Problem is, when you create an instance, you don't get a deprecation warning. – Mike Allen Mar 20 '18 at 14:26
  • 3
    Why would that be a problem? Creating an instance doesn't access the deprecated field. If you want that warning, deprecate the class. – Alexey Romanov Mar 20 '18 at 14:27
  • True. I guess it's for the OP to clarify. However, I would have expected that a _deprecated_ case class field should report the deprecation warning when it is initialized as well as accessed. – Mike Allen Mar 20 '18 at 14:29
  • I can only reply that I wouldn't. – Alexey Romanov Mar 20 '18 at 14:30
  • 1
    I could _maybe_ see making the `unapply` deprecated because it provides access to the field, as mentioned in the answer. – Alexey Romanov Mar 20 '18 at 14:33
  • Oops, I didn't check the last part of the answer: it _is_ deprecated. – Alexey Romanov Mar 20 '18 at 14:35
  • 1
    The whole point of marking something as _deprecated_ is to tell people that it is no longer recommended, and that (ideally) there is a better or preferred alternative. In this case, there is technically no way to use the case class _at all_ without needing to specify a value for a deprecated field. If you can create instances of that field without a warning, then what's the point? – Mike Allen Mar 20 '18 at 14:36
  • Hi @AlexeyRomanov. Thank you. Yeah, you are right. I can observe the compile warning. But why so can't I see the deprecation along my code as well? I updated my question. – spi-x-i Mar 20 '18 at 15:34
  • @MikeAllen 1. If you have a deprecated field in Java, and set this field in the constructor, the constructor isn't automatically deprecated. This is exactly the same situation. – Alexey Romanov Mar 20 '18 at 17:54
  • @MikeAllen 2. A specific example why you may want to deprecate a field like this: you've discovered that in practice this field always must have the same value, so it should never be accessed. You still need to keep it in the constructor and the extractor for binary compatibility, but can provide a default value for the constructor so it _doesn't_ need to be specified when creating an instance. – Alexey Romanov Mar 20 '18 at 17:57
  • But in _Java_ you can't have a field that's also a constructor argument. I take your point, but a case class constructor argument is a publicly visible, required field. – Mike Allen Mar 20 '18 at 17:57
0

This is tricky and it also depends on level of compatibility you want.

You might remove the field (and thus remove it from primary constructor), but also create a deprecated constructor with the old signature. Plus you'll need a similar apply method on the companion object. The problem is, it is going to break pattern matching for the case class (unless you override unapply). Maybe you find it reasonable, maybe not. And, if you don't want to break readers, you'll also have to implement a deprecated getter stub. (It is however questionable what value to return.)

A more conservative approach: Deprecate the primary constructor, create a secondary (non-deprecated) constructor without the value. Also, deprecate the field. This seems to work OK and it does not break unapply method. It seems to raise deprecated warnings as you would want, except the definition also raises a deprecation warning. See this snippet:

case class Foo @deprecated() (x: String, @deprecated y: Int){
  def this(x: String) = this(x, 0)
}

object Foo {
  def apply(x: String) = new Foo(x)
}

println((new Foo("x")).x)
println((new Foo("x", 66)).x)
println((Foo("x")).x)
println((Foo("x", 66)).x)

println((new Foo("x")).y)
println((new Foo("x", 66)).y)
println((Foo("x")).y)
println((Foo("x", 66)).y)

Unfortunately, there is no @SuppressWarning.

Of course, adjust parameters for @deprecated as you need.

v6ak
  • 1,636
  • 2
  • 12
  • 27