question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Warn for code that depend on subtle semantics of class parameters in constructors

See original GitHub issue

Minimized example

class B(val y: Int):
  println(y)                   // A warning is due here
  foo()
  def foo() = println(y)

class C(override val y: Int) extends B(10)

@main
def Test = new C(20)

Output

10
20

Expectation

The code above depends on subtle semantics of class parameters in constructors, for which the programmer should receive a warning.

As discussed in https://github.com/lampepfl/dotty/issues/15723#issuecomment-1193181067, one possibility to issue the warning is:

Rule A: Issue a warning for the usage of an unqualified class parameter x in the constructor (including usage in super constructor calls) if this.x is overridden in a subclass.

This rule, however, is too coarse-grained, as programmers will get an annoying warning for the following code:

class B(val y: Int):
  println(y)                   // should not issue a warning here
  foo()
  def foo() = println(y)

class C(override val y: Int) extends B(y)

Here is a better rule to avoid the spurious warnings in the above:

Rule B: Issue a warning for the usage of an unqualified class parameter x in the constructor (including usage in super constructor calls) if its semantics deviate from that of this.x.

The plan is to implement the rule in the initialization checker. Implementation of Rule B would require extending the abstract domain with a symbolic value in the spirit of symbolic execution.

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:10 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
liufengyuncommented, Sep 21, 2022

Unlike the core of initialization checking I don’t see a convincing guarantee here. So what is the guarantee we provide here?

The guarantee is the following:

  • A class parameter may only be overridden by another class parameter.
  • If a parameter is overridden, at the end of class constructors, the overridden and overriding parameters have the same value.

We have conducted an empirical study both for Dotty and community projects, we find no valid use which violates the rules/guarantees above.

For a new language design, it seems good to simply make class parameters final – which we cannot do in Scala because of legacy code. Therefore, we propose the check as an alternative.

Do we want to spend a considerable amount of our complexity budget in warning people on this

The new check is actually very simple. We only reason about the symbolic value for the overridding parameter is passed to the overridden parameter.

For that purpose, the checker only checks super constructor calls, x and this.a, all other expressions are ignored. It’s guaranteed to be fast and always terminates.

1reaction
liufengyuncommented, Aug 5, 2022

Found more instances of overridding class parameters in #15815 .

The following two are in Scaladoc:

enum TemplateName(val name: String):
  case YamlDefined(override val name: String) extends TemplateName(name)
  case SidebarDefined(override val name: String) extends TemplateName(name)
  case FilenameDefined(override val name: String) extends TemplateName(name)
enum Resource(val path: String):
  case Text(override val path: String, content: String) extends Resource(path)
  case Classpath(override val path: String, name: String) extends Resource(path)
  case File(override val path: String, file: Path) extends Resource(path)

The following one comes from munit:

class FailException(
    val message: String,
    val cause: Throwable,
    val isStackTracesEnabled: Boolean,
    val location: Location
) extends AssertionError(message, cause)

class FailSuiteException(
    override val message: String,
    override val location: Location
) extends FailException(message, location)

Fastparse:

community-build/community-projects/fastparse/cssparse/src/cssparse/Ast.scala:37:47 
[error] 37 |  sealed case class BracketsBlock(override val values: Seq[ComponentValue]) extends Block("(", ")", values)
[error]    |                                               ^
[error]    |error overriding value values in class Block of type Seq[cssparse.Ast.ComponentValue];
[error]    |  value values of type Seq[cssparse.Ast.ComponentValue] cannot override final member value values in class Block
[error] -- [E164] Declaration Error: /__w/dotty/dotty/community-build/community-projects/fastparse/cssparse/src/cssparse/Ast.scala:38:52 
[error] 38 |  sealed case class CurlyBracketsBlock(override val values: Seq[ComponentValue]) extends Block("{", "}", values)
[error]    |                                                    ^
[error]    |error overriding value values in class Block of type Seq[cssparse.Ast.ComponentValue];
[error]    |  value values of type Seq[cssparse.Ast.ComponentValue] cannot override final member value values in class Block
[error] -- [E164] Declaration Error: /__w/dotty/dotty/community-build/community-projects/fastparse/cssparse/src/cssparse/Ast.scala:39:53 
[error] 39 |  sealed case class SquareBracketsBlock(override val values: Seq[ComponentValue]) extends Block("[", "]", values)
[error]    |                                                     ^
[error]    |error overriding value values in class Block of type Seq[cssparse.Ast.ComponentValue];
[error]    |  value values of type Seq[cssparse.Ast.ComponentValue] cannot override final member value values in class Block

Effpi:

private class ActorPipeImpl[A](
  override val mbox: Mailbox[A],
  override val ref: ActorRef[A]) extends ActorPipe[A](mbox, ref)

Akka-actor (there are many such code):

final class BackoffSupervisor @deprecated("Use `BackoffSupervisor.props` method instead", since = "2.5.22")(
    override val childProps: Props,
    override val childName: String,
    minBackoff: FiniteDuration,
    maxBackoff: FiniteDuration,
    override val reset: BackoffReset,
    randomFactor: Double,
    strategy: SupervisorStrategy,
    val replyWhileStopped: Option[Any],
    val finalStopMessage: Option[Any => Boolean])
    extends BackoffOnStopSupervisor(
      childProps,
      childName,
      minBackoff,
      maxBackoff,
      reset,
      randomFactor,
      strategy,
      replyWhileStopped.map(msg => ReplyWith(msg)).getOrElse(ForwardDeathLetters),
      finalStopMessage)

endpoints4s:

  class JsonSchema[A](
      val ujsonSchema: ujsonSchemas.JsonSchema[A],
      val docs: DocumentedJsonSchema
  )

  class Record[A](
      override val ujsonSchema: ujsonSchemas.Record[A],
      override val docs: DocumentedRecord
  ) extends JsonSchema[A](ujsonSchema, docs)

  class Tagged[A](
      override val ujsonSchema: ujsonSchemas.Tagged[A],
      override val docs: DocumentedCoProd
  ) extends JsonSchema[A](ujsonSchema, docs)

  class Enum[A](
      override val ujsonSchema: ujsonSchemas.Enum[A],
      override val docs: DocumentedEnum
  ) extends JsonSchema[A](ujsonSchema, docs)

specs2:

community-build/community-projects/specs2/common/shared/src/main/scala/org/specs2/execute/Result.scala:452:17 
[error] 452 |    override val expectationsNb = n
[error]     |                 ^
[error]     |error overriding value expectationsNb in class Result of type Int;

stdlib:

community-build/community-projects/stdLib213/src/library/scala/collection/convert/JavaCollectionWrappers.scala:384:48 
[error] 384 |  class ConcurrentMapWrapper[K, V](override val underlying: concurrent.Map[K, V]) extends MutableMapWrapper[K, V](underlying) with juc.ConcurrentMap[K, V] {
[error]     |                                                ^
[error]     |error overriding value underlying in class MutableMapWrapper of type scala.collection.mutable.Map[K, V];
[error]     |  value underlying of type scala.collection.concurrent.Map[K, V] cannot override final member value underlying in class MutableMapWrapper

jackson-module-scala:

community-projects/jackson-module-scala/src/test/scala/com/fasterxml/jackson/module/scala/deser/CreatorTest.scala:42:38 
[error] 42 |  case class DerivedCase(override val timestamp: Long, name: String) extends AbstractBase(timestamp)
[error]    |                                      ^
[error]    |error overriding value timestamp in class AbstractBase of type Long;
[error]    |  value timestamp of type Long cannot override final member value timestamp in class AbstractBase
[error] -- [E164] Declaration Error: /__w/dotty/dotty/community-build/community-projects/jackson-module-scala/src/test/scala/com/fasterxml/jackson/module/scala/ser/OverrideValSerializerTest.scala:19:30 
[error] 19 |  case class Sub(override val id: UUID, something: String) extends Base(id)
[error]    |                              ^
[error]    |error overriding value id in class Base of type java.util.UUID;
[error]    |  value id of type java.util.UUID cannot override final member value id in class Base

scala-parser-combinator:

community-build/community-projects/scala-parser-combinators/)
[error] -- [E164] Declaration Error: /__w/dotty/dotty/community-build/community-projects/scala-parser-combinators/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala:194:34 
[error] 194 |  case class Failure(override val msg: String, override val next: Input) extends NoSuccess(msg, next) {
[error]     |                                  ^
[error]     |error overriding value msg in class NoSuccess of type String;
[error]     |  value msg of type String cannot override final member value msg in class NoSuccess
[error] -- [E164] Declaration Error: /__w/dotty/dotty/community-build/community-projects/scala-parser-combinators/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala:194:60 
[error] 194 |  case class Failure(override val msg: String, override val next: Input) extends NoSuccess(msg, next) {
[error]     |                                                            ^
[error]     |error overriding value next in class NoSuccess of type Parsers.this.Input;
[error]     |  value next of type Parsers.this.Input cannot override final member value next in class NoSuccess
[error] -- [E164] Declaration Error: /__w/dotty/dotty/community-build/community-projects/scala-parser-combinators/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala:217:32 
[error] 217 |  case class Error(override val msg: String, override val next: Input) extends NoSuccess(msg, next) {
[error]     |                                ^
[error]     |error overriding value msg in class NoSuccess of type String;
[error]     |  value msg of type String cannot override final member value msg in class NoSuccess
[error] -- [E164] Declaration Error: /__w/dotty/dotty/community-build/community-projects/scala-parser-combinators/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala:217:58 
[error] 217 |  case class Error(override val msg: String, override val next: Input) extends NoSuccess(msg, next) {
[error]     |                                                          ^
[error]     |error overriding value next in class NoSuccess of type Parsers.this.Input;
[error]     |  value next of type Parsers.this.Input cannot override final member value next in class NoSuccess

sconfig:

community-build/community-projects/sconfig/)
[error] -- [E164] Declaration Error: /__w/dotty/dotty/community-build/community-projects/sconfig/sconfig/shared/src/test/scala/org/ekrich/config/impl/TokenizerTest.scala:360:37 
[error] 360 |    case class LongTest(override val s: String, override val result: Token)
[error]     |                                     ^
[error]     |error overriding value s in class NumberTest of type String;
[error]     |  value s of type String cannot override final member value s in class NumberTest
[error] -- [E164] Declaration Error: /__w/dotty/dotty/community-build/community-projects/sconfig/sconfig/shared/src/test/scala/org/ekrich/config/impl/TokenizerTest.scala:360:61 
[error] 360 |    case class LongTest(override val s: String, override val result: Token)
[error]     |                                                             ^
[error]     |error overriding value result in class NumberTest of type org.ekrich.config.impl.Token;
[error]     |  value result of type org.ekrich.config.impl.Token cannot override final member value result in class NumberTest
[error] -- [E164] Declaration Error: /__w/dotty/dotty/community-build/community-projects/sconfig/sconfig/shared/src/test/scala/org/ekrich/config/impl/TokenizerTest.scala:362:39 
[error] 362 |    case class DoubleTest(override val s: String, override val result: Token)
[error]     |                                       ^
[error]     |error overriding value s in class NumberTest of type String;
[error]     |  value s of type String cannot override final member value s in class NumberTest
[error] -- [E164] Declaration Error: /__w/dotty/dotty/community-build/community-projects/sconfig/sconfig/shared/src/test/scala/org/ekrich/config/impl/TokenizerTest.scala:362:63 
[error] 362 |    case class DoubleTest(override val s: String, override val result: Token)
[error]     |                                                               ^
[error]     |error overriding value result in class NumberTest of type org.ekrich.config.impl.Token;
[error]     |  value result of type org.ekrich.config.impl.Token cannot override final member value result in class NumberTest
Read more comments on GitHub >

github_iconTop Results From Across the Web

Constructors, C++ FAQ
No. A “default constructor” is a constructor that can be called with no arguments. One example of this is a constructor that takes...
Read more >
Constructors (C++) | Microsoft Learn
A constructor has the same name as the class and no return value. You can define as many overloaded constructors as needed to...
Read more >
Chapter 8. Classes - Oracle Help Center
A record class (§8.10) is a class declared with abbreviated syntax that defines a simple aggregate of values. This chapter discusses the common...
Read more >
Classes in ECMAScript 6 (final semantics) - 2ality
Recently, TC39 decided on the final semantics of classes in ECMAScript 6. This blog post explains how their final incarnation works.
Read more >
Programming in C++, Rules and Recommendations - Classes
This problem is especially common with constructors and destructors. A constructor always invokes the constructors of its base classes and member data before ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found