Skip to content

Fix secondary constructor capture checking#25841

Open
odersky wants to merge 2 commits intoscala:mainfrom
dotty-staging:fix-secondary-constrs
Open

Fix secondary constructor capture checking#25841
odersky wants to merge 2 commits intoscala:mainfrom
dotty-staging:fix-secondary-constrs

Conversation

@odersky
Copy link
Copy Markdown
Contributor

@odersky odersky commented Apr 17, 2026

Secondary constructors lost parameter captures. This is fixed now. As a result, scala.util.Random is now safe to use from safe mode.

Solution sketch: We keep track of which parameters in secondary constructors flow into a parameter of the primary constructor. For arguments of secondary constructors corresponding to such parameters we can use their actual capture sets. For primary constructor parameters that are not instantiated with an argument of a secondary constructors we assume that they keep their declared formal parameter capture set, which is a conservative appoximation.

@odersky odersky requested a review from a team as a code owner April 17, 2026 11:22
@odersky odersky requested a review from natsukagami April 17, 2026 14:33
odersky added 2 commits May 5, 2026 15:05
Secondary constructors lost parameter captures. This is fixed now.
As a result, scala.util.Random is now safe to use from safe mode.
The previous treatment was wrong if fields have LocalCaps with different
classifiers that have a common ancestor classifier class. In that case
we created an unclassified LocalCap instead of a LocalCap with the
common ancestor classifier.
@odersky odersky force-pushed the fix-secondary-constrs branch from b190d56 to 7c223de Compare May 5, 2026 13:06
@odersky
Copy link
Copy Markdown
Contributor Author

odersky commented May 5, 2026

@natsukagami ping for review

Copy link
Copy Markdown
Contributor

@natsukagami natsukagami left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just one note

Comment on lines +1016 to +1022
for (param, argType) <- sym.paramSymss.flatten.filter(_.isTerm).lazyZip(argTypes) do
for
ann <- param.getAnnotation(defn.ParamAliasAnnot)
name <- ann.argumentConstantString(0)
do
aliasMap = aliasMap.updated(name.toTermName, argType)
val aliasedArgs = instCls.paramGetters.map: param =>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to work if the parameter has multiple alias annotations, like in this case:

import language.experimental.captureChecking

class T

class A(val x: T^, val y: T^):
  def this(v: T^) = this(v, v)

def f(x: T^) =
  val a = A(x)
  val b: T^{x} = a.x
  val c: T^{x} = a.y

This gives the following -Vprint:cc output:

package <empty> {
  import language.experimental.captureChecking
  @SourceFile("a.scala") class T() extends Object() {}
  @SourceFile("a.scala") class A(x: T^, y: T^) extends Object() {
    val x: T^
    val y: T^
    def <init>(@paramAlias("y") @paramAlias("x") v: T^): Unit =
      {
        this(v, v)
        ()
      }
  }
  final lazy module val a$package: a$package^{} = new a$package()
  @SourceFile("a.scala") final module class a$package() extends Object() {
    private[this] type $this = a$package.type
    private def writeReplace(): AnyRef =
      new scala.runtime.ModuleSerializationProxy(classOf[a$package.type])
    def f(x: T^): Unit =
      {
        val a: A{val x: T^; val y: T^{x}}^{any, x} = new A(x)
        val b: T^{x} = a.x
        val c: T^{x} = a.y
        ()
      }
  }
}

(and then b gets rejected)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants