Skip to content

Unexpectedly retained constructor argument #22979

@rcano

Description

@rcano

Compiler version

3.6.4 and 3.7.0-RC1

Minimized code

import scala.util.boundary
import scala.annotation.constructorOnly

class Leak()(using @constructorOnly l: boundary.Label[String]) {
  Seq("a", "b").foreach(_ => boundary.break("stop"))
}

Output

Compilation error

l is marked `@constructorOnly` but it is retained as a field in class Leak

Expectation

It should compile successfully, as the constructor argument shouldn't be captured.

Note

The compiler currently does capture the variable the moment it sees it passed to a lambda. Running CFR on the compiled code (when the annotation is not present), reveals:

import java.io.Serializable;
import scala.Function1;
import scala.collection.SeqOps;
import scala.collection.immutable.$colon$colon;
import scala.collection.immutable.List;
import scala.collection.immutable.Nil$;
import scala.runtime.Nothing$;
import scala.util.boundary;
import scala.util.boundary$;

public class Leak {
    private final boundary.Label<String> l;

    public Leak(boundary.Label<String> l) {
        this.l = l;
        ((SeqOps)new $colon$colon<Nothing$>((Nothing$)((Object)"a"), (List<Nothing$>)new $colon$colon<Nothing$>((Nothing$)((Object)"b"), Nil$.MODULE$))).foreach((Function1<String, Object> & Serializable)_$1 -> {
            throw boundary$.MODULE$.break("stop", l);
        });
    }
}

where one can see that this.l is indeed never used, the Lambda simply closes over the constructor argument as expected.

Activity

added and removed
stat:needs triageEvery issue needs to have an "area" and "itype" label
on Apr 14, 2025
rcano

rcano commented on May 27, 2025

@rcano
Author

I'd like to clarify, the bug is not about the annotation misfiring, but the compiler violating expectation and retaining the field.

som-snytt

som-snytt commented on May 29, 2025

@som-snytt
Contributor

In Scala 2, the lambda uses the instance field, so perhaps this is residual behavior.

self-assigned this
on May 29, 2025
som-snytt

som-snytt commented on May 29, 2025

@som-snytt
Contributor

The member starts out captured. The field is dropped if it is only used from the constructor. For the lambda case, lambda construction must happen before the constructor finishes.

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

    Development

    Participants

    @som-snytt@rcano@Gedochao

    Issue actions

      Unexpectedly retained constructor argument · Issue #22979 · scala/scala3