From 2ec3d68c70e88fe7bfc8c21ff47619a11d270d61 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Wed, 18 Sep 2024 12:14:32 +0200 Subject: [PATCH 01/20] A PoC for infering tracked with one working case --- .../src/dotty/tools/dotc/typer/Namer.scala | 62 +++++++++++++++++-- tests/pos/infer-tracked.scala | 22 +++++++ 2 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 tests/pos/infer-tracked.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 87dec93f8040..3c0c656e6e26 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1545,6 +1545,8 @@ class Namer { typer: Typer => case completer: Completer => completer.indexConstructor(constr, constrSym) case _ => + // constrSym.info = typeSig(constrSym) + tempInfo = denot.asClass.classInfo.integrateOpaqueMembers.asInstanceOf[TempClassInfo] denot.info = savedInfo } @@ -1646,6 +1648,7 @@ class Namer { typer: Typer => * as an attachment on the ClassDef tree. */ def enterParentRefinementSyms(refinements: List[(Name, Type)]) = + println(s"For class $cls, entering parent refinements: $refinements") val refinedSyms = mutable.ListBuffer[Symbol]() for (name, tp) <- refinements do if decls.lookupEntry(name) == null then @@ -1653,7 +1656,9 @@ class Namer { typer: Typer => case tp: MethodOrPoly => Method | Synthetic | Deferred | Tracked case _ if name.isTermName => Synthetic | Deferred | Tracked case _ => Synthetic | Deferred - refinedSyms += newSymbol(cls, name, flags, tp, coord = original.rhs.span.startPos).entered + val s = newSymbol(cls, name, flags, tp, coord = original.rhs.span.startPos).entered + refinedSyms += s + println(s" entered $s") if refinedSyms.nonEmpty then typr.println(i"parent refinement symbols: ${refinedSyms.toList}") original.pushAttachment(ParentRefinements, refinedSyms.toList) @@ -1695,6 +1700,7 @@ class Namer { typer: Typer => end addUsingTraits completeConstructor(denot) + val constrSym = symbolOfTree(constr) denot.info = tempInfo.nn val parentTypes = defn.adjustForTuple(cls, cls.typeParams, @@ -1928,7 +1934,7 @@ class Namer { typer: Typer => val mt = wrapMethType(effectiveResultType(sym, paramSymss)) if sym.isPrimaryConstructor then checkCaseClassParamDependencies(mt, sym.owner) mt - else if sym.isAllOf(Given | Method) && Feature.enabled(modularity) then + else if Feature.enabled(modularity) then // set every context bound evidence parameter of a given companion method // to be tracked, provided it has a type that has an abstract type member. // Add refinements for all tracked parameters to the result type. @@ -1986,14 +1992,60 @@ class Namer { typer: Typer => cls.srcPos) case _ => - /** Under x.modularity, we add `tracked` to context bound witnesses - * that have abstract type members + /** Try to infer if the parameter needs a `tracked` modifier */ def needsTracked(sym: Symbol, param: ValDef)(using Context) = !sym.is(Tracked) - && param.hasAttachment(ContextBoundParam) + && ( + isContextBoundWitnessWithAbstractMembers(sym, param) + || isReferencedInPublicSignatures(sym) + // || isPassedToTrackedParentParameter(sym, param) + ) + + /** Under x.modularity, we add `tracked` to context bound witnesses + * that have abstract type members + */ + def isContextBoundWitnessWithAbstractMembers(sym: Symbol, param: ValDef)(using Context): Boolean = + param.hasAttachment(ContextBoundParam) && sym.info.memberNames(abstractTypeNameFilter).nonEmpty + /** Under x.modularity, we add `tracked` to term parameters whose types are referenced + * in public signatures of the defining class + */ + def isReferencedInPublicSignatures(sym: Symbol)(using Context): Boolean = + val owner = sym.maybeOwner.maybeOwner + val accessorSyms = maybeParamAccessors(owner, sym) + def checkOwnerMemberSignatures(owner: Symbol): Boolean = + owner.infoOrCompleter match + case info: ClassInfo => + info.decls.filter(d => !d.isConstructor).exists(d => tpeContainsSymbolRef(d.info, accessorSyms)) + case _ => false + checkOwnerMemberSignatures(owner) + + def isPassedToTrackedParentParameter(sym: Symbol, param: ValDef)(using Context): Boolean = + val owner = sym.maybeOwner.maybeOwner + val accessorSyms = maybeParamAccessors(owner, sym) + owner.infoOrCompleter match + // case info: ClassInfo => + // info.parents.foreach(println) + // info.parents.exists(tpeContainsSymbolRef(_, accessorSyms)) + case _ => false + + private def namedTypeWithPrefixContainsSymbolRef(tpe: Type, syms: List[Symbol])(using Context): Boolean = tpe match + case tpe: NamedType => tpe.prefix.exists && tpeContainsSymbolRef(tpe.prefix, syms) + case _ => false + + private def tpeContainsSymbolRef(tpe: Type, syms: List[Symbol])(using Context): Boolean = + tpe.termSymbol.exists && syms.contains(tpe.termSymbol) + || tpe.argInfos.exists(tpeContainsSymbolRef(_, syms)) + || namedTypeWithPrefixContainsSymbolRef(tpe, syms) + + private def maybeParamAccessors(owner: Symbol, sym: Symbol)(using Context): List[Symbol] = + owner.infoOrCompleter match + case info: ClassInfo => + info.decls.lookupAll(sym.name).filter(d => d.is(ParamAccessor)).toList + case _ => List.empty + /** Under x.modularity, set every context bound evidence parameter of a class to be tracked, * provided it has a type that has an abstract type member. Reset private and local flags * so that the parameter becomes a `val`. diff --git a/tests/pos/infer-tracked.scala b/tests/pos/infer-tracked.scala new file mode 100644 index 000000000000..161c3b981a78 --- /dev/null +++ b/tests/pos/infer-tracked.scala @@ -0,0 +1,22 @@ +import scala.language.experimental.modularity +import scala.language.future + +abstract class C: + type T + def foo: T + +class F(val x: C): + val result: x.T = x.foo + +class G(override val x: C) extends F(x) + +def Test = + val c = new C: + type T = Int + def foo = 42 + + val f = new F(c) + val i: Int = f.result + + // val g = new G(c) + // val j: Int = g.result From 4115578f09b671f672e8b15b80f6c273c8ba54c2 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 23 Sep 2024 14:47:29 +0200 Subject: [PATCH 02/20] Check for parameter references in type bounds when infering tracked --- .../src/dotty/tools/dotc/typer/Namer.scala | 27 ++++-- tests/pos/infer-tracked-1.scala | 34 ++++++++ ...r-tracked-parsercombinators-expanded.scala | 65 +++++++++++++++ ...fer-tracked-parsercombinators-givens.scala | 55 +++++++++++++ tests/pos/infer-tracked-vector.scala | 82 +++++++++++++++++++ tests/pos/infer-tracked.scala | 28 ++++++- 6 files changed, 281 insertions(+), 10 deletions(-) create mode 100644 tests/pos/infer-tracked-1.scala create mode 100644 tests/pos/infer-tracked-parsercombinators-expanded.scala create mode 100644 tests/pos/infer-tracked-parsercombinators-givens.scala create mode 100644 tests/pos/infer-tracked-vector.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 3c0c656e6e26..4702ce7c14ce 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -30,6 +30,7 @@ import config.Feature.{sourceVersion, modularity} import config.SourceVersion.* import scala.compiletime.uninitialized +import dotty.tools.dotc.transform.init.Util.tree /** This class creates symbols from definitions and imports and gives them * lazy types. @@ -1648,7 +1649,6 @@ class Namer { typer: Typer => * as an attachment on the ClassDef tree. */ def enterParentRefinementSyms(refinements: List[(Name, Type)]) = - println(s"For class $cls, entering parent refinements: $refinements") val refinedSyms = mutable.ListBuffer[Symbol]() for (name, tp) <- refinements do if decls.lookupEntry(name) == null then @@ -1658,7 +1658,6 @@ class Namer { typer: Typer => case _ => Synthetic | Deferred val s = newSymbol(cls, name, flags, tp, coord = original.rhs.span.startPos).entered refinedSyms += s - println(s" entered $s") if refinedSyms.nonEmpty then typr.println(i"parent refinement symbols: ${refinedSyms.toList}") original.pushAttachment(ParentRefinements, refinedSyms.toList) @@ -1996,10 +1995,11 @@ class Namer { typer: Typer => */ def needsTracked(sym: Symbol, param: ValDef)(using Context) = !sym.is(Tracked) + && sym.maybeOwner.isConstructor && ( isContextBoundWitnessWithAbstractMembers(sym, param) || isReferencedInPublicSignatures(sym) - // || isPassedToTrackedParentParameter(sym, param) + || isPassedToTrackedParentParameter(sym, param) ) /** Under x.modularity, we add `tracked` to context bound witnesses @@ -2018,11 +2018,14 @@ class Namer { typer: Typer => def checkOwnerMemberSignatures(owner: Symbol): Boolean = owner.infoOrCompleter match case info: ClassInfo => - info.decls.filter(d => !d.isConstructor).exists(d => tpeContainsSymbolRef(d.info, accessorSyms)) + info.decls.filter(_.isTerm) + .filter(_ != sym.maybeOwner) + .exists(d => tpeContainsSymbolRef(d.info, accessorSyms)) case _ => false checkOwnerMemberSignatures(owner) def isPassedToTrackedParentParameter(sym: Symbol, param: ValDef)(using Context): Boolean = + // TODO(kπ) Add tracked if the param is passed as a tracked arg in parent. Can we touch the inheritance terms? val owner = sym.maybeOwner.maybeOwner val accessorSyms = maybeParamAccessors(owner, sym) owner.infoOrCompleter match @@ -2035,10 +2038,18 @@ class Namer { typer: Typer => case tpe: NamedType => tpe.prefix.exists && tpeContainsSymbolRef(tpe.prefix, syms) case _ => false - private def tpeContainsSymbolRef(tpe: Type, syms: List[Symbol])(using Context): Boolean = - tpe.termSymbol.exists && syms.contains(tpe.termSymbol) - || tpe.argInfos.exists(tpeContainsSymbolRef(_, syms)) - || namedTypeWithPrefixContainsSymbolRef(tpe, syms) + private def tpeContainsSymbolRef(tpe0: Type, syms: List[Symbol])(using Context): Boolean = + val tpe = tpe0.dropAlias.widenExpr.dealias + tpe match + case m : MethodOrPoly => + m.paramInfos.exists(tpeContainsSymbolRef(_, syms)) + || tpeContainsSymbolRef(m.resultType, syms) + case r @ RefinedType(parent, _, refinedInfo) => tpeContainsSymbolRef(parent, syms) || tpeContainsSymbolRef(refinedInfo, syms) + case TypeBounds(lo, hi) => tpeContainsSymbolRef(lo, syms) || tpeContainsSymbolRef(hi, syms) + case t: Type => + tpe.termSymbol.exists && syms.contains(tpe.termSymbol) + || tpe.argInfos.exists(tpeContainsSymbolRef(_, syms)) + || namedTypeWithPrefixContainsSymbolRef(tpe, syms) private def maybeParamAccessors(owner: Symbol, sym: Symbol)(using Context): List[Symbol] = owner.infoOrCompleter match diff --git a/tests/pos/infer-tracked-1.scala b/tests/pos/infer-tracked-1.scala new file mode 100644 index 000000000000..b4976a963074 --- /dev/null +++ b/tests/pos/infer-tracked-1.scala @@ -0,0 +1,34 @@ +import scala.language.experimental.modularity +import scala.language.future + +trait Ordering { + type T + def compare(t1:T, t2: T): Int +} + +class SetFunctor(val ord: Ordering) { + type Set = List[ord.T] + def empty: Set = Nil + + implicit class helper(s: Set) { + def add(x: ord.T): Set = x :: remove(x) + def remove(x: ord.T): Set = s.filter(e => ord.compare(x, e) != 0) + def member(x: ord.T): Boolean = s.exists(e => ord.compare(x, e) == 0) + } +} + +object Test { + val orderInt = new Ordering { + type T = Int + def compare(t1: T, t2: T): Int = t1 - t2 + } + + val IntSet = new SetFunctor(orderInt) + import IntSet.* + + def main(args: Array[String]) = { + val set = IntSet.empty.add(6).add(8).add(23) + assert(!set.member(7)) + assert(set.member(8)) + } +} diff --git a/tests/pos/infer-tracked-parsercombinators-expanded.scala b/tests/pos/infer-tracked-parsercombinators-expanded.scala new file mode 100644 index 000000000000..63c6aec9e84a --- /dev/null +++ b/tests/pos/infer-tracked-parsercombinators-expanded.scala @@ -0,0 +1,65 @@ +import scala.language.experimental.modularity +import scala.language.future + +import collection.mutable + +/// A parser combinator. +trait Combinator[T]: + + /// The context from which elements are being parsed, typically a stream of tokens. + type Context + /// The element being parsed. + type Element + + extension (self: T) + /// Parses and returns an element from `context`. + def parse(context: Context): Option[Element] +end Combinator + +final case class Apply[C, E](action: C => Option[E]) +final case class Combine[A, B](first: A, second: B) + +object test: + + class apply[C, E] extends Combinator[Apply[C, E]]: + type Context = C + type Element = E + extension(self: Apply[C, E]) + def parse(context: C): Option[E] = self.action(context) + + def apply[C, E]: apply[C, E] = new apply[C, E] + + class combine[A, B]( + val f: Combinator[A], + val s: Combinator[B] { type Context = f.Context} + ) extends Combinator[Combine[A, B]]: + type Context = f.Context + type Element = (f.Element, s.Element) + extension(self: Combine[A, B]) + def parse(context: Context): Option[Element] = ??? + + def combine[A, B]( + _f: Combinator[A], + _s: Combinator[B] { type Context = _f.Context} + ) = new combine[A, B](_f, _s) + // cast is needed since the type of new combine[A, B](_f, _s) + // drops the required refinement. + + extension [A] (buf: mutable.ListBuffer[A]) def popFirst() = + if buf.isEmpty then None + else try Some(buf.head) finally buf.remove(0) + + @main def hello: Unit = { + val source = (0 to 10).toList + val stream = source.to(mutable.ListBuffer) + + val n = Apply[mutable.ListBuffer[Int], Int](s => s.popFirst()) + val m = Combine(n, n) + + val c = combine( + apply[mutable.ListBuffer[Int], Int], + apply[mutable.ListBuffer[Int], Int] + ) + val r = c.parse(m)(stream) // was type mismatch, now OK + val rc: Option[(Int, Int)] = r + } diff --git a/tests/pos/infer-tracked-parsercombinators-givens.scala b/tests/pos/infer-tracked-parsercombinators-givens.scala new file mode 100644 index 000000000000..eee522ed7285 --- /dev/null +++ b/tests/pos/infer-tracked-parsercombinators-givens.scala @@ -0,0 +1,55 @@ +import scala.language.experimental.modularity +import scala.language.future + +import collection.mutable + +/// A parser combinator. +trait Combinator[T]: + + /// The context from which elements are being parsed, typically a stream of tokens. + type Context + /// The element being parsed. + type Element + + extension (self: T) + /// Parses and returns an element from `context`. + def parse(context: Context): Option[Element] +end Combinator + +final case class Apply[C, E](action: C => Option[E]) +final case class Combine[A, B](first: A, second: B) + +given apply[C, E]: Combinator[Apply[C, E]] with { + type Context = C + type Element = E + extension(self: Apply[C, E]) { + def parse(context: C): Option[E] = self.action(context) + } +} + +given combine[A, B](using + val f: Combinator[A], + val s: Combinator[B] { type Context = f.Context } +): Combinator[Combine[A, B]] with { + type Context = f.Context + type Element = (f.Element, s.Element) + extension(self: Combine[A, B]) { + def parse(context: Context): Option[Element] = ??? + } +} + +extension [A] (buf: mutable.ListBuffer[A]) def popFirst() = + if buf.isEmpty then None + else try Some(buf.head) finally buf.remove(0) + +@main def hello: Unit = { + val source = (0 to 10).toList + val stream = source.to(mutable.ListBuffer) + + val n = Apply[mutable.ListBuffer[Int], Int](s => s.popFirst()) + val m = Combine(n, n) + + val r = m.parse(stream) // error: type mismatch, found `mutable.ListBuffer[Int]`, required `?1.Context` + val rc: Option[(Int, Int)] = r + // it would be great if this worked +} diff --git a/tests/pos/infer-tracked-vector.scala b/tests/pos/infer-tracked-vector.scala new file mode 100644 index 000000000000..e748dc9cbe8e --- /dev/null +++ b/tests/pos/infer-tracked-vector.scala @@ -0,0 +1,82 @@ +import scala.language.experimental.modularity +import scala.language.future + +object typeparams: + sealed trait Nat + object Z extends Nat + final case class S[N <: Nat]() extends Nat + + type Zero = Z.type + type Succ[N <: Nat] = S[N] + + sealed trait Fin[N <: Nat] + case class FZero[N <: Nat]() extends Fin[Succ[N]] + case class FSucc[N <: Nat](pred: Fin[N]) extends Fin[Succ[N]] + + object Fin: + def zero[N <: Nat]: Fin[Succ[N]] = FZero() + def succ[N <: Nat](i: Fin[N]): Fin[Succ[N]] = FSucc(i) + + sealed trait Vec[A, N <: Nat] + case class VNil[A]() extends Vec[A, Zero] + case class VCons[A, N <: Nat](head: A, tail: Vec[A, N]) extends Vec[A, Succ[N]] + + object Vec: + def empty[A]: Vec[A, Zero] = VNil() + def cons[A, N <: Nat](head: A, tail: Vec[A, N]): Vec[A, Succ[N]] = VCons(head, tail) + + def get[A, N <: Nat](v: Vec[A, N], index: Fin[N]): A = (v, index) match + case (VCons(h, _), FZero()) => h + case (VCons(_, t), FSucc(pred)) => get(t, pred) + + def runVec(): Unit = + val v: Vec[Int, Succ[Succ[Succ[Zero]]]] = Vec.cons(1, Vec.cons(2, Vec.cons(3, Vec.empty))) + + println(s"Element at index 0: ${Vec.get(v, Fin.zero)}") + println(s"Element at index 1: ${Vec.get(v, Fin.succ(Fin.zero))}") + println(s"Element at index 2: ${Vec.get(v, Fin.succ(Fin.succ(Fin.zero)))}") + // println(s"Element at index 2: ${Vec.get(v, Fin.succ(Fin.succ(Fin.succ(Fin.zero))))}") // error + +// TODO(kπ) check if I can get it to work +// object typemembers: +// sealed trait Nat +// object Z extends Nat +// case class S() extends Nat: +// type N <: Nat + +// type Zero = Z.type +// type Succ[N1 <: Nat] = S { type N = N1 } + +// sealed trait Fin: +// type N <: Nat +// case class FZero[N1 <: Nat]() extends Fin: +// type N = Succ[N1] +// case class FSucc(tracked val pred: Fin) extends Fin: +// type N = Succ[pred.N] + +// object Fin: +// def zero[N1 <: Nat]: Fin { type N = Succ[N1] } = FZero[N1]() +// def succ[N1 <: Nat](i: Fin { type N = N1 }): Fin { type N = Succ[N1] } = FSucc(i) + +// sealed trait Vec[A]: +// type N <: Nat +// case class VNil[A]() extends Vec[A]: +// type N = Zero +// case class VCons[A](head: A, tracked val tail: Vec[A]) extends Vec[A]: +// type N = Succ[tail.N] + +// object Vec: +// def empty[A]: Vec[A] = VNil() +// def cons[A](head: A, tail: Vec[A]): Vec[A] = VCons(head, tail) + +// def get[A](v: Vec[A], index: Fin { type N = v.N }): A = (v, index) match +// case (VCons(h, _), FZero()) => h +// case (VCons(_, t), FSucc(pred)) => get(t, pred) + +// // def runVec(): Unit = +// val v: Vec[Int] = Vec.cons(1, Vec.cons(2, Vec.cons(3, Vec.empty))) + +// println(s"Element at index 0: ${Vec.get(v, Fin.zero)}") +// println(s"Element at index 1: ${Vec.get(v, Fin.succ(Fin.zero))}") +// println(s"Element at index 2: ${Vec.get(v, Fin.succ(Fin.succ(Fin.zero)))}") +// // println(s"Element at index 2: ${Vec.get(v, Fin.succ(Fin.succ(Fin.succ(Fin.zero))))}") diff --git a/tests/pos/infer-tracked.scala b/tests/pos/infer-tracked.scala index 161c3b981a78..496508ffdc6c 100644 --- a/tests/pos/infer-tracked.scala +++ b/tests/pos/infer-tracked.scala @@ -10,13 +10,37 @@ class F(val x: C): class G(override val x: C) extends F(x) +class H(val x: C): + type T1 = x.T + val result: T1 = x.foo + +class I(val c: C, val t: c.T) + +case class J(c: C): + val result: c.T = c.foo + +case class K(c: C): + def result[B >: c.T]: B = c.foo + def Test = val c = new C: type T = Int def foo = 42 val f = new F(c) - val i: Int = f.result + val _: Int = f.result // val g = new G(c) - // val j: Int = g.result + // val _: Int = g.result + + val h = new H(c) + val _: Int = h.result + + val i = new I(c, c.foo) + val _: Int = i.t + + val j = J(c) + val _: Int = j.result + + val k = K(c) + val _: Int = k.result From 9d2a24567150881027d7c4a5706f2c3dee2bc10e Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 23 Sep 2024 15:00:18 +0200 Subject: [PATCH 03/20] Try potential fixes for some of the cyclic referenc errors --- compiler/src/dotty/tools/dotc/config/Feature.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Namer.scala | 13 ++++++++----- .../infer-tracked-parsercombinators-givens.scala | 5 +++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 8b9a64924ace..776828de8f67 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -113,7 +113,7 @@ object Feature: * feature is defined. */ def enabled(feature: TermName)(using Context): Boolean = - enabledBySetting(feature) || enabledByImport(feature) + enabledBySetting(feature) || enabledByImport(feature) || feature == modularity /** Is auto-tupling enabled? */ def autoTuplingEnabled(using Context): Boolean = !enabled(nme.noAutoTupling) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 4702ce7c14ce..7d24563b6667 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -30,7 +30,6 @@ import config.Feature.{sourceVersion, modularity} import config.SourceVersion.* import scala.compiletime.uninitialized -import dotty.tools.dotc.transform.init.Util.tree /** This class creates symbols from definitions and imports and gives them * lazy types. @@ -1699,7 +1698,6 @@ class Namer { typer: Typer => end addUsingTraits completeConstructor(denot) - val constrSym = symbolOfTree(constr) denot.info = tempInfo.nn val parentTypes = defn.adjustForTuple(cls, cls.typeParams, @@ -1995,7 +1993,11 @@ class Namer { typer: Typer => */ def needsTracked(sym: Symbol, param: ValDef)(using Context) = !sym.is(Tracked) - && sym.maybeOwner.isConstructor + && sym.isTerm + && sym.maybeOwner.isPrimaryConstructor + // && !sym.flags.is(Synthetic) + // && !sym.maybeOwner.flags.is(Synthetic) + && !sym.maybeOwner.maybeOwner.flags.is(Synthetic) && ( isContextBoundWitnessWithAbstractMembers(sym, param) || isReferencedInPublicSignatures(sym) @@ -2018,7 +2020,7 @@ class Namer { typer: Typer => def checkOwnerMemberSignatures(owner: Symbol): Boolean = owner.infoOrCompleter match case info: ClassInfo => - info.decls.filter(_.isTerm) + info.decls.filter(_.isTerm).filter(_.isPublic) .filter(_ != sym.maybeOwner) .exists(d => tpeContainsSymbolRef(d.info, accessorSyms)) case _ => false @@ -2039,8 +2041,9 @@ class Namer { typer: Typer => case _ => false private def tpeContainsSymbolRef(tpe0: Type, syms: List[Symbol])(using Context): Boolean = - val tpe = tpe0.dropAlias.widenExpr.dealias + val tpe = tpe0.dropAlias.safeDealias tpe match + case ExprType(resType) => tpeContainsSymbolRef(resType, syms) case m : MethodOrPoly => m.paramInfos.exists(tpeContainsSymbolRef(_, syms)) || tpeContainsSymbolRef(m.resultType, syms) diff --git a/tests/pos/infer-tracked-parsercombinators-givens.scala b/tests/pos/infer-tracked-parsercombinators-givens.scala index eee522ed7285..8bb514c8a75a 100644 --- a/tests/pos/infer-tracked-parsercombinators-givens.scala +++ b/tests/pos/infer-tracked-parsercombinators-givens.scala @@ -27,9 +27,10 @@ given apply[C, E]: Combinator[Apply[C, E]] with { } } +// TODO(kπ) infer tracked correctly here given combine[A, B](using - val f: Combinator[A], - val s: Combinator[B] { type Context = f.Context } + tracked val f: Combinator[A], + tracked val s: Combinator[B] { type Context = f.Context } ): Combinator[Combine[A, B]] with { type Context = f.Context type Element = (f.Element, s.Element) From 50d8d88fea16eaba50a9d737f259c26104b256bd Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Wed, 23 Oct 2024 17:09:33 +0200 Subject: [PATCH 04/20] Add non-infering completers for infering tracked --- .../src/dotty/tools/dotc/config/Feature.scala | 2 +- .../tools/dotc/core/SymDenotations.scala | 3 ++ .../src/dotty/tools/dotc/typer/Namer.scala | 36 ++++++++++--------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 776828de8f67..8b9a64924ace 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -113,7 +113,7 @@ object Feature: * feature is defined. */ def enabled(feature: TermName)(using Context): Boolean = - enabledBySetting(feature) || enabledByImport(feature) || feature == modularity + enabledBySetting(feature) || enabledByImport(feature) /** Is auto-tupling enabled? */ def autoTuplingEnabled(using Context): Boolean = !enabled(nme.noAutoTupling) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index e14e5cf0a728..a645e5ab6d88 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2752,6 +2752,9 @@ object SymDenotations { /** Sets all missing fields of given denotation */ def complete(denot: SymDenotation)(using Context): Unit + /** Is this a completer for an explicit type tree */ + def isNonInfering: Boolean = false + def apply(sym: Symbol): LazyType = this def apply(module: TermSymbol, modcls: ClassSymbol): LazyType = this diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 7d24563b6667..fc897c04d2b3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -278,6 +278,9 @@ class Namer { typer: Typer => if rhs.isEmpty || flags.is(Opaque) then flags |= Deferred if flags.is(Param) then tree.rhs else analyzeRHS(tree.rhs) + def isNonInferingTree(tree: ValOrDefDef): Boolean = + !tree.tpt.isEmpty || tree.mods.isOneOf(TermParamOrAccessor) + // to complete a constructor, move one context further out -- this // is the context enclosing the class. Note that the context in which a // constructor is recorded and the context in which it is completed are @@ -291,6 +294,7 @@ class Namer { typer: Typer => val completer = tree match case tree: TypeDef => TypeDefCompleter(tree)(cctx) + case tree: ValOrDefDef if isNonInferingTree(tree) => NonInferingCompleter(tree)(cctx) case _ => Completer(tree)(cctx) val info = adjustIfModule(completer, tree) createOrRefine[Symbol](tree, name, flags, ctx.owner, _ => info, @@ -1736,6 +1740,10 @@ class Namer { typer: Typer => } } + class NonInferingCompleter(original: ValOrDefDef)(ictx: Context) extends Completer(original)(ictx) { + override def isNonInfering: Boolean = true + } + /** Possible actions to perform when deciding on a forwarder for a member */ private enum CanForward: case Yes @@ -1994,14 +2002,13 @@ class Namer { typer: Typer => def needsTracked(sym: Symbol, param: ValDef)(using Context) = !sym.is(Tracked) && sym.isTerm - && sym.maybeOwner.isPrimaryConstructor - // && !sym.flags.is(Synthetic) - // && !sym.maybeOwner.flags.is(Synthetic) - && !sym.maybeOwner.maybeOwner.flags.is(Synthetic) && ( isContextBoundWitnessWithAbstractMembers(sym, param) - || isReferencedInPublicSignatures(sym) - || isPassedToTrackedParentParameter(sym, param) + || sym.maybeOwner.isPrimaryConstructor + // && !sym.flags.is(Synthetic) + // && !sym.maybeOwner.flags.is(Synthetic) + // && !sym.maybeOwner.maybeOwner.flags.is(Synthetic) + && isReferencedInPublicSignatures(sym) ) /** Under x.modularity, we add `tracked` to context bound witnesses @@ -2011,6 +2018,11 @@ class Namer { typer: Typer => param.hasAttachment(ContextBoundParam) && sym.info.memberNames(abstractTypeNameFilter).nonEmpty + extension (sym: Symbol) + def infoWithForceNonInferingCompleter(using Context): Type = sym.infoOrCompleter match + case tpe: LazyType if tpe.isNonInfering => sym.info + case info => info + /** Under x.modularity, we add `tracked` to term parameters whose types are referenced * in public signatures of the defining class */ @@ -2022,20 +2034,10 @@ class Namer { typer: Typer => case info: ClassInfo => info.decls.filter(_.isTerm).filter(_.isPublic) .filter(_ != sym.maybeOwner) - .exists(d => tpeContainsSymbolRef(d.info, accessorSyms)) + .exists(d => tpeContainsSymbolRef(d.infoWithForceNonInferingCompleter, accessorSyms)) case _ => false checkOwnerMemberSignatures(owner) - def isPassedToTrackedParentParameter(sym: Symbol, param: ValDef)(using Context): Boolean = - // TODO(kπ) Add tracked if the param is passed as a tracked arg in parent. Can we touch the inheritance terms? - val owner = sym.maybeOwner.maybeOwner - val accessorSyms = maybeParamAccessors(owner, sym) - owner.infoOrCompleter match - // case info: ClassInfo => - // info.parents.foreach(println) - // info.parents.exists(tpeContainsSymbolRef(_, accessorSyms)) - case _ => false - private def namedTypeWithPrefixContainsSymbolRef(tpe: Type, syms: List[Symbol])(using Context): Boolean = tpe match case tpe: NamedType => tpe.prefix.exists && tpeContainsSymbolRef(tpe.prefix, syms) case _ => false From 07c62f26f529a44839ff54bf2946b73dbdc60e04 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Thu, 24 Oct 2024 17:31:53 +0200 Subject: [PATCH 05/20] Some condition reorder fixes related to infering tracked --- .../src/dotty/tools/dotc/typer/Namer.scala | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index fc897c04d2b3..ee59108a24a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -294,7 +294,8 @@ class Namer { typer: Typer => val completer = tree match case tree: TypeDef => TypeDefCompleter(tree)(cctx) - case tree: ValOrDefDef if isNonInferingTree(tree) => NonInferingCompleter(tree)(cctx) + case tree: ValOrDefDef if Feature.enabled(Feature.modularity) && isNonInferingTree(tree) => + NonInferingCompleter(tree)(cctx) case _ => Completer(tree)(cctx) val info = adjustIfModule(completer, tree) createOrRefine[Symbol](tree, name, flags, ctx.owner, _ => info, @@ -1549,8 +1550,6 @@ class Namer { typer: Typer => case completer: Completer => completer.indexConstructor(constr, constrSym) case _ => - // constrSym.info = typeSig(constrSym) - tempInfo = denot.asClass.classInfo.integrateOpaqueMembers.asInstanceOf[TempClassInfo] denot.info = savedInfo } @@ -1659,8 +1658,7 @@ class Namer { typer: Typer => case tp: MethodOrPoly => Method | Synthetic | Deferred | Tracked case _ if name.isTermName => Synthetic | Deferred | Tracked case _ => Synthetic | Deferred - val s = newSymbol(cls, name, flags, tp, coord = original.rhs.span.startPos).entered - refinedSyms += s + refinedSyms += newSymbol(cls, name, flags, tp, coord = original.rhs.span.startPos).entered if refinedSyms.nonEmpty then typr.println(i"parent refinement symbols: ${refinedSyms.toList}") original.pushAttachment(ParentRefinements, refinedSyms.toList) @@ -1945,7 +1943,7 @@ class Namer { typer: Typer => // Add refinements for all tracked parameters to the result type. for params <- ddef.termParamss; param <- params do val psym = symbolOfTree(param) - if needsTracked(psym, param) then psym.setFlag(Tracked) + if needsTracked(psym, param, sym) then psym.setFlag(Tracked) valOrDefDefSig(ddef, sym, paramSymss, wrapRefinedMethType) else valOrDefDefSig(ddef, sym, paramSymss, wrapMethType) @@ -1999,24 +1997,28 @@ class Namer { typer: Typer => /** Try to infer if the parameter needs a `tracked` modifier */ - def needsTracked(sym: Symbol, param: ValDef)(using Context) = - !sym.is(Tracked) - && sym.isTerm + def needsTracked(psym: Symbol, param: ValDef, owningSym: Symbol)(using Context) = + lazy val abstractContextBound = isContextBoundWitnessWithAbstractMembers(psym, param, owningSym) + lazy val isRefInSignatures = + psym.maybeOwner.isPrimaryConstructor + // && !psym.flags.is(Synthetic) + // && !psym.maybeOwner.flags.is(Synthetic) + // && !psym.maybeOwner.maybeOwner.flags.is(Synthetic) + && isReferencedInPublicSignatures(psym) + !psym.is(Tracked) + && psym.isTerm && ( - isContextBoundWitnessWithAbstractMembers(sym, param) - || sym.maybeOwner.isPrimaryConstructor - // && !sym.flags.is(Synthetic) - // && !sym.maybeOwner.flags.is(Synthetic) - // && !sym.maybeOwner.maybeOwner.flags.is(Synthetic) - && isReferencedInPublicSignatures(sym) + abstractContextBound + || isRefInSignatures ) /** Under x.modularity, we add `tracked` to context bound witnesses * that have abstract type members */ - def isContextBoundWitnessWithAbstractMembers(sym: Symbol, param: ValDef)(using Context): Boolean = - param.hasAttachment(ContextBoundParam) - && sym.info.memberNames(abstractTypeNameFilter).nonEmpty + def isContextBoundWitnessWithAbstractMembers(psym: Symbol, param: ValDef, owningSym: Symbol)(using Context): Boolean = + (owningSym.isClass || owningSym.isAllOf(Given | Method)) + && param.hasAttachment(ContextBoundParam) + && psym.info.memberNames(abstractTypeNameFilter).nonEmpty extension (sym: Symbol) def infoWithForceNonInferingCompleter(using Context): Type = sym.infoOrCompleter match @@ -2069,7 +2071,7 @@ class Namer { typer: Typer => def setTracked(param: ValDef)(using Context): Unit = val sym = symbolOfTree(param) sym.maybeOwner.maybeOwner.infoOrCompleter match - case info: ClassInfo if needsTracked(sym, param) => + case info: ClassInfo if needsTracked(sym, param, sym.maybeOwner.maybeOwner) => typr.println(i"set tracked $param, $sym: ${sym.info} containing ${sym.info.memberNames(abstractTypeNameFilter).toList}") for acc <- info.decls.lookupAll(sym.name) if acc.is(ParamAccessor) do acc.resetFlag(PrivateLocal) From 0499af670b7ca19d59e2520466459a6b2e3a3db3 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Fri, 25 Oct 2024 18:54:50 +0200 Subject: [PATCH 06/20] =?UTF-8?q?Fix=20some=20pickling=20errors=20?= =?UTF-8?q?=E2=80=A2=E1=B4=97=E2=80=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/dotty/tools/dotc/typer/Namer.scala | 29 ++++++++++++------- .../test/dotc/pos-test-pickling.blacklist | 1 + .../infer-tracked-parent-refinements.scala | 8 +++++ ...fer-tracked-parsercombinators-givens.scala | 3 +- 4 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 tests/pos/infer-tracked-parent-refinements.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index ee59108a24a5..39b9ed379e80 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1791,6 +1791,7 @@ class Namer { typer: Typer => sym.owner.typeParams.foreach(_.ensureCompleted()) completeTrailingParamss(constr, sym, indexingCtor = true) if Feature.enabled(modularity) then + // println(i"[indexConstructor] Checking if params of $constr need tracked") constr.termParamss.foreach(_.foreach(setTracked)) /** The signature of a module valdef. @@ -1931,22 +1932,26 @@ class Namer { typer: Typer => def wrapRefinedMethType(restpe: Type): Type = wrapMethType(addParamRefinements(restpe, paramSymss)) + def addTrackedIfNeeded(ddef: DefDef, owningSym: Symbol): Boolean = + var wasSet = false + for params <- ddef.termParamss; param <- params do + val psym = symbolOfTree(param) + if needsTracked(psym, param, owningSym) then + psym.setFlag(Tracked) + wasSet = true + wasSet + + if Feature.enabled(modularity) then addTrackedIfNeeded(ddef, sym.maybeOwner) + if isConstructor then // set result type tree to unit, but take the current class as result type of the symbol typedAheadType(ddef.tpt, defn.UnitType) val mt = wrapMethType(effectiveResultType(sym, paramSymss)) if sym.isPrimaryConstructor then checkCaseClassParamDependencies(mt, sym.owner) mt - else if Feature.enabled(modularity) then - // set every context bound evidence parameter of a given companion method - // to be tracked, provided it has a type that has an abstract type member. - // Add refinements for all tracked parameters to the result type. - for params <- ddef.termParamss; param <- params do - val psym = symbolOfTree(param) - if needsTracked(psym, param, sym) then psym.setFlag(Tracked) - valOrDefDefSig(ddef, sym, paramSymss, wrapRefinedMethType) else - valOrDefDefSig(ddef, sym, paramSymss, wrapMethType) + val paramFn = if Feature.enabled(Feature.modularity) && sym.isAllOf(Given | Method) then wrapRefinedMethType else wrapMethType + valOrDefDefSig(ddef, sym, paramSymss, paramFn) end defDefSig /** Complete the trailing parameters of a DefDef, @@ -1998,6 +2003,7 @@ class Namer { typer: Typer => /** Try to infer if the parameter needs a `tracked` modifier */ def needsTracked(psym: Symbol, param: ValDef, owningSym: Symbol)(using Context) = + // println(i"Checking if $psym needs tracked") lazy val abstractContextBound = isContextBoundWitnessWithAbstractMembers(psym, param, owningSym) lazy val isRefInSignatures = psym.maybeOwner.isPrimaryConstructor @@ -2071,8 +2077,9 @@ class Namer { typer: Typer => def setTracked(param: ValDef)(using Context): Unit = val sym = symbolOfTree(param) sym.maybeOwner.maybeOwner.infoOrCompleter match - case info: ClassInfo if needsTracked(sym, param, sym.maybeOwner.maybeOwner) => - typr.println(i"set tracked $param, $sym: ${sym.info} containing ${sym.info.memberNames(abstractTypeNameFilter).toList}") + case info: ClassInfo + if !sym.is(Tracked) && isContextBoundWitnessWithAbstractMembers(sym, param, sym.maybeOwner.maybeOwner) => + typr.println(i"set tracked $param, $sym: ${sym.info}") for acc <- info.decls.lookupAll(sym.name) if acc.is(ParamAccessor) do acc.resetFlag(PrivateLocal) acc.setFlag(Tracked) diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index ebdd414ea7f2..72431fb08e48 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -136,6 +136,7 @@ parsercombinators-new-syntax.scala hylolib-deferred-given hylolib-cb hylolib +infer-tracked-parsercombinators-givens.scala # typecheckErrors method unpickling i21415.scala diff --git a/tests/pos/infer-tracked-parent-refinements.scala b/tests/pos/infer-tracked-parent-refinements.scala new file mode 100644 index 000000000000..0d71d7cc2897 --- /dev/null +++ b/tests/pos/infer-tracked-parent-refinements.scala @@ -0,0 +1,8 @@ +import scala.language.experimental.modularity +import scala.language.future + +trait WithValue { type Value = Int } + +case class Year(value: Int) extends WithValue { + val x: Value = 2 +} diff --git a/tests/pos/infer-tracked-parsercombinators-givens.scala b/tests/pos/infer-tracked-parsercombinators-givens.scala index 8bb514c8a75a..209bbcc67493 100644 --- a/tests/pos/infer-tracked-parsercombinators-givens.scala +++ b/tests/pos/infer-tracked-parsercombinators-givens.scala @@ -1,6 +1,5 @@ import scala.language.experimental.modularity import scala.language.future - import collection.mutable /// A parser combinator. @@ -19,7 +18,7 @@ end Combinator final case class Apply[C, E](action: C => Option[E]) final case class Combine[A, B](first: A, second: B) -given apply[C, E]: Combinator[Apply[C, E]] with { +given apply: [C, E] => Combinator[Apply[C, E]] { type Context = C type Element = E extension(self: Apply[C, E]) { From 4e6a6f4866c890c6eb393c3a834d2f56586ab537 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 28 Oct 2024 09:49:17 +0100 Subject: [PATCH 07/20] Enable modularity by default to test infering tracked --- compiler/src/dotty/tools/dotc/config/Feature.scala | 2 +- scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 8b9a64924ace..776828de8f67 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -113,7 +113,7 @@ object Feature: * feature is defined. */ def enabled(feature: TermName)(using Context): Boolean = - enabledBySetting(feature) || enabledByImport(feature) + enabledBySetting(feature) || enabledByImport(feature) || feature == modularity /** Is auto-tupling enabled? */ def autoTuplingEnabled(using Context): Boolean = !enabled(nme.noAutoTupling) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala index 1a8337e0c6b7..ab7fd918ebb7 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala @@ -127,6 +127,7 @@ case class ScaladocTastyInspector()(using ctx: DocContext) extends Inspector: topLevels ++= intrinsicTypeDefs val scalaPckg = defn.ScalaPackage given parser.qctx.type = parser.qctx + import parser.dri topLevels += "scala" -> Member(scalaPckg.fullName, "", scalaPckg.dri, Kind.Package) topLevels += mergeAnyRefAliasAndObject(parser) From df1048f1623bbc4b4a9c1c43ce58788a5b6f4f5b Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Tue, 10 Dec 2024 16:03:54 +0100 Subject: [PATCH 08/20] Separate features related to tracked into a separate sub-feature --- .github/workflows/ci.yaml | 8 ++++---- compiler/src/dotty/tools/dotc/config/Feature.scala | 6 +++++- .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 4 ++-- .../src/dotty/tools/dotc/transform/PostTyper.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Namer.scala | 11 +++++------ compiler/src/dotty/tools/dotc/typer/Typer.scala | 4 ++-- .../src/scala/runtime/stdLibPatches/language.scala | 7 +++++++ .../src/dotty/tools/scaladoc/tasty/TastyParser.scala | 1 - tests/pos/infer-tracked-1.scala | 2 +- tests/pos/infer-tracked-parent-refinements.scala | 2 +- .../infer-tracked-parsercombinators-expanded.scala | 2 +- .../pos/infer-tracked-parsercombinators-givens.scala | 2 +- tests/pos/infer-tracked-vector.scala | 2 +- tests/pos/infer-tracked.scala | 2 +- 16 files changed, 34 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a2006e16c7e8..b69ce997a131 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,10 +26,10 @@ on: - cron: '0 3 * * *' # Every day at 3 AM workflow_dispatch: -# Cancels any in-progress runs within the same group identified by workflow name and GH reference (branch or tag) +# Cancels any in-progress runs within the same group identified by workflow name and GH reference (branch or tag) # For example it would: # - terminate previous PR CI execution after pushing more changes to the same PR branch -# - terminate previous on-push CI run after merging new PR to main +# - terminate previous on-push CI run after merging new PR to main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} @@ -831,7 +831,7 @@ jobs: path: . - name: Prepare MSI package shell: bash - run: | + run: | msiInstaller="scala3-${{ env.RELEASE_TAG }}.msi" mv scala.msi "${msiInstaller}" sha256sum "${msiInstaller}" > "${msiInstaller}.sha256" @@ -870,7 +870,7 @@ jobs: # ${upload("tar.gz archive", s"$filename.tar.gz", "application/gzip", distribution)} # ${upload("tar.gz archive SHA", s"$filename.tar.gz.sha256", "text/plain", distribution)} # """ - # def uploadMSI() = + # def uploadMSI() = # val distribution = "Windows x86_64 MSI" # s""" # # $distribution diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 776828de8f67..c40ab518a800 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -36,6 +36,7 @@ object Feature: val into = experimental("into") val namedTuples = experimental("namedTuples") val modularity = experimental("modularity") + val tracked = experimental("tracked") val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors") val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions") val betterFors = experimental("betterFors") @@ -68,6 +69,7 @@ object Feature: (into, "Allow into modifier on parameter types"), (namedTuples, "Allow named tuples"), (modularity, "Enable experimental modularity features"), + (tracked, "Enable tracked modifier"), (betterMatchTypeExtractors, "Enable better match type extractors"), (betterFors, "Enable improvements in `for` comprehensions") ) @@ -113,7 +115,7 @@ object Feature: * feature is defined. */ def enabled(feature: TermName)(using Context): Boolean = - enabledBySetting(feature) || enabledByImport(feature) || feature == modularity + enabledBySetting(feature) || enabledByImport(feature) /** Is auto-tupling enabled? */ def autoTuplingEnabled(using Context): Boolean = !enabled(nme.noAutoTupling) @@ -129,6 +131,8 @@ object Feature: def betterForsEnabled(using Context) = enabled(betterFors) + def trackedEnabled(using Context) = enabled(tracked) || enabled(modularity) + def genericNumberLiteralsEnabled(using Context) = enabled(genericNumberLiterals) def scala2ExperimentalMacroEnabled(using Context) = enabled(scala2macros) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index de99ce0105ea..3fd80db693d9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -924,7 +924,7 @@ class TreeUnpickler(reader: TastyReader, val resType = if name == nme.CONSTRUCTOR then effectiveResultType(sym, paramss) - else if sym.isAllOf(Given | Method) && Feature.enabled(Feature.modularity) then + else if sym.isAllOf(Given | Method) && Feature.trackedEnabled then addParamRefinements(tpt.tpe, paramss) else tpt.tpe diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 89f88faebc17..fdbe5c3af04a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3557,7 +3557,7 @@ object Parsers { if isErasedKw then mods = addModifier(mods) if paramOwner.isClass then - if isIdent(nme.tracked) && in.featureEnabled(Feature.modularity) && !in.lookahead.isColon then + if isIdent(nme.tracked) && (in.featureEnabled(Feature.tracked) || in.featureEnabled(Feature.modularity)) && !in.lookahead.isColon then mods = addModifier(mods) mods = addFlag(modifiers(start = mods), ParamAccessor) mods = @@ -3633,7 +3633,7 @@ object Parsers { || isIdent && (in.name == nme.inline // inline starts a name binding || in.name == nme.tracked // tracked starts a name binding under x.modularity - && in.featureEnabled(Feature.modularity) + && (in.featureEnabled(Feature.tracked) || in.featureEnabled(Feature.modularity)) || in.lookahead.isColon) // a following `:` starts a name binding (mods, paramsAreNamed) val params = diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 0feee53ca50f..e62ecfadaaaa 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -378,7 +378,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => def checkClassType(tpe: Type, stablePrefixReq: Boolean) = ctx.typer.checkClassType(tpe, tree.srcPos, traitReq = false, stablePrefixReq = stablePrefixReq, - refinementOK = Feature.enabled(Feature.modularity)) + refinementOK = Feature.trackedEnabled) checkClassType(tree.tpe, true) if !nu.tpe.isLambdaSub then // Check the constructor type as well; it could be an illegal singleton type diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 1cd531046753..566306a177f1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -197,7 +197,7 @@ object Checking { * and that the instance conforms to the self type of the created class. */ def checkInstantiable(tp: Type, srcTp: Type, pos: SrcPos)(using Context): Unit = - tp.underlyingClassRef(refinementOK = Feature.enabled(modularity)) match + tp.underlyingClassRef(refinementOK = Feature.trackedEnabled) match case tref: TypeRef => val cls = tref.symbol if (cls.isOneOf(AbstractOrTrait)) { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 39b9ed379e80..a2548baaf25d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -294,7 +294,7 @@ class Namer { typer: Typer => val completer = tree match case tree: TypeDef => TypeDefCompleter(tree)(cctx) - case tree: ValOrDefDef if Feature.enabled(Feature.modularity) && isNonInferingTree(tree) => + case tree: ValOrDefDef if Feature.trackedEnabled && isNonInferingTree(tree) => NonInferingCompleter(tree)(cctx) case _ => Completer(tree)(cctx) val info = adjustIfModule(completer, tree) @@ -1614,7 +1614,7 @@ class Namer { typer: Typer => if (cls.isRefinementClass) ptype else { val pt = checkClassType( - if Feature.enabled(modularity) + if Feature.trackedEnabled then ptype.separateRefinements(cls, parentRefinements) else ptype, parent.srcPos, @@ -1790,8 +1790,7 @@ class Namer { typer: Typer => index(constr.leadingTypeParams) sym.owner.typeParams.foreach(_.ensureCompleted()) completeTrailingParamss(constr, sym, indexingCtor = true) - if Feature.enabled(modularity) then - // println(i"[indexConstructor] Checking if params of $constr need tracked") + if Feature.trackedEnabled then constr.termParamss.foreach(_.foreach(setTracked)) /** The signature of a module valdef. @@ -1941,7 +1940,7 @@ class Namer { typer: Typer => wasSet = true wasSet - if Feature.enabled(modularity) then addTrackedIfNeeded(ddef, sym.maybeOwner) + if Feature.trackedEnabled then addTrackedIfNeeded(ddef, sym.maybeOwner) if isConstructor then // set result type tree to unit, but take the current class as result type of the symbol @@ -1950,7 +1949,7 @@ class Namer { typer: Typer => if sym.isPrimaryConstructor then checkCaseClassParamDependencies(mt, sym.owner) mt else - val paramFn = if Feature.enabled(Feature.modularity) && sym.isAllOf(Given | Method) then wrapRefinedMethType else wrapMethType + val paramFn = if Feature.trackedEnabled && sym.isAllOf(Given | Method) then wrapRefinedMethType else wrapMethType valOrDefDefSig(ddef, sym, paramSymss, paramFn) end defDefSig diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6bb5d1ee70ff..56703cf050c6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1148,7 +1148,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if templ1.parents.isEmpty && isFullyDefined(pt, ForceDegree.flipBottom) && isSkolemFree(pt) - && isEligible(pt.underlyingClassRef(refinementOK = Feature.enabled(modularity))) + && isEligible(pt.underlyingClassRef(refinementOK = Feature.trackedEnabled)) then templ1 = cpy.Template(templ)(parents = untpd.TypeTree(pt) :: Nil) for case parent: RefTree <- templ1.parents do @@ -4719,7 +4719,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer cpy.Ident(qual)(qual.symbol.name.sourceModuleName.toTypeName) case _ => errorTree(tree, em"cannot convert from $tree to an instance creation expression") - val tycon = ctorResultType.underlyingClassRef(refinementOK = Feature.enabled(modularity)) + val tycon = ctorResultType.underlyingClassRef(refinementOK = Feature.trackedEnabled) typed( untpd.Select( untpd.New(untpd.TypedSplice(tpt.withType(tycon))), diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 547710d55293..10ec3280be8f 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -111,6 +111,13 @@ object language: @compileTimeOnly("`modularity` can only be used at compile time in import statements") object modularity + /** Experimental support for tracked modifier + * + * `tracked` is a subset of `modularity` + */ + @compileTimeOnly("`tracked` can only be used at compile time in import statements") + object tracked + /** Was needed to add support for relaxed imports of extension methods. * The language import is no longer needed as this is now a standard feature since SIP was accepted. * @see [[http://dotty.epfl.ch/docs/reference/contextual/extension-methods]] diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala index ab7fd918ebb7..1a8337e0c6b7 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala @@ -127,7 +127,6 @@ case class ScaladocTastyInspector()(using ctx: DocContext) extends Inspector: topLevels ++= intrinsicTypeDefs val scalaPckg = defn.ScalaPackage given parser.qctx.type = parser.qctx - import parser.dri topLevels += "scala" -> Member(scalaPckg.fullName, "", scalaPckg.dri, Kind.Package) topLevels += mergeAnyRefAliasAndObject(parser) diff --git a/tests/pos/infer-tracked-1.scala b/tests/pos/infer-tracked-1.scala index b4976a963074..e75fb88b7521 100644 --- a/tests/pos/infer-tracked-1.scala +++ b/tests/pos/infer-tracked-1.scala @@ -1,4 +1,4 @@ -import scala.language.experimental.modularity +import scala.language.experimental.tracked import scala.language.future trait Ordering { diff --git a/tests/pos/infer-tracked-parent-refinements.scala b/tests/pos/infer-tracked-parent-refinements.scala index 0d71d7cc2897..b50c6e88383d 100644 --- a/tests/pos/infer-tracked-parent-refinements.scala +++ b/tests/pos/infer-tracked-parent-refinements.scala @@ -1,4 +1,4 @@ -import scala.language.experimental.modularity +import scala.language.experimental.tracked import scala.language.future trait WithValue { type Value = Int } diff --git a/tests/pos/infer-tracked-parsercombinators-expanded.scala b/tests/pos/infer-tracked-parsercombinators-expanded.scala index 63c6aec9e84a..30828b1341bb 100644 --- a/tests/pos/infer-tracked-parsercombinators-expanded.scala +++ b/tests/pos/infer-tracked-parsercombinators-expanded.scala @@ -1,4 +1,4 @@ -import scala.language.experimental.modularity +import scala.language.experimental.tracked import scala.language.future import collection.mutable diff --git a/tests/pos/infer-tracked-parsercombinators-givens.scala b/tests/pos/infer-tracked-parsercombinators-givens.scala index 209bbcc67493..3a66ea717892 100644 --- a/tests/pos/infer-tracked-parsercombinators-givens.scala +++ b/tests/pos/infer-tracked-parsercombinators-givens.scala @@ -1,4 +1,4 @@ -import scala.language.experimental.modularity +import scala.language.experimental.tracked import scala.language.future import collection.mutable diff --git a/tests/pos/infer-tracked-vector.scala b/tests/pos/infer-tracked-vector.scala index e748dc9cbe8e..c952c37bf1e0 100644 --- a/tests/pos/infer-tracked-vector.scala +++ b/tests/pos/infer-tracked-vector.scala @@ -1,4 +1,4 @@ -import scala.language.experimental.modularity +import scala.language.experimental.tracked import scala.language.future object typeparams: diff --git a/tests/pos/infer-tracked.scala b/tests/pos/infer-tracked.scala index 496508ffdc6c..80626ab74267 100644 --- a/tests/pos/infer-tracked.scala +++ b/tests/pos/infer-tracked.scala @@ -1,4 +1,4 @@ -import scala.language.experimental.modularity +import scala.language.experimental.tracked import scala.language.future abstract class C: From 2e68fb9d1b6fbe148a21724e04e20a771080d816 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 28 Oct 2024 16:18:37 +0100 Subject: [PATCH 09/20] Enable tracked by default to test infering tracked --- compiler/src/dotty/tools/dotc/config/Feature.scala | 2 +- scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index c40ab518a800..60905523efaa 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -115,7 +115,7 @@ object Feature: * feature is defined. */ def enabled(feature: TermName)(using Context): Boolean = - enabledBySetting(feature) || enabledByImport(feature) + enabledBySetting(feature) || enabledByImport(feature) || feature == tracked /** Is auto-tupling enabled? */ def autoTuplingEnabled(using Context): Boolean = !enabled(nme.noAutoTupling) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala index 1a8337e0c6b7..ab7fd918ebb7 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala @@ -127,6 +127,7 @@ case class ScaladocTastyInspector()(using ctx: DocContext) extends Inspector: topLevels ++= intrinsicTypeDefs val scalaPckg = defn.ScalaPackage given parser.qctx.type = parser.qctx + import parser.dri topLevels += "scala" -> Member(scalaPckg.fullName, "", scalaPckg.dri, Kind.Package) topLevels += mergeAnyRefAliasAndObject(parser) From fca3a657c9cf835021faa163ec6d7ba6a21f2da7 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Tue, 29 Oct 2024 11:48:39 +0100 Subject: [PATCH 10/20] Disable tracked by default, mark accessors as tracked, when infering tracked --- .../src/dotty/tools/dotc/config/Feature.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 8 +- ...fer-tracked-parsercombinators-givens.scala | 1 - tests/pos/infer-tracked-vector.scala | 82 ------------------- 4 files changed, 5 insertions(+), 88 deletions(-) delete mode 100644 tests/pos/infer-tracked-vector.scala diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 60905523efaa..c40ab518a800 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -115,7 +115,7 @@ object Feature: * feature is defined. */ def enabled(feature: TermName)(using Context): Boolean = - enabledBySetting(feature) || enabledByImport(feature) || feature == tracked + enabledBySetting(feature) || enabledByImport(feature) /** Is auto-tupling enabled? */ def autoTuplingEnabled(using Context): Boolean = !enabled(nme.noAutoTupling) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index a2548baaf25d..41df449f47a3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1931,14 +1931,14 @@ class Namer { typer: Typer => def wrapRefinedMethType(restpe: Type): Type = wrapMethType(addParamRefinements(restpe, paramSymss)) - def addTrackedIfNeeded(ddef: DefDef, owningSym: Symbol): Boolean = - var wasSet = false + def addTrackedIfNeeded(ddef: DefDef, owningSym: Symbol): Unit = for params <- ddef.termParamss; param <- params do val psym = symbolOfTree(param) if needsTracked(psym, param, owningSym) then psym.setFlag(Tracked) - wasSet = true - wasSet + for acc <- sym.maybeOwner.infoOrCompleter.decls.lookupAll(psym.name) if acc.is(ParamAccessor) do + acc.resetFlag(PrivateLocal) + acc.setFlag(Tracked) if Feature.trackedEnabled then addTrackedIfNeeded(ddef, sym.maybeOwner) diff --git a/tests/pos/infer-tracked-parsercombinators-givens.scala b/tests/pos/infer-tracked-parsercombinators-givens.scala index 3a66ea717892..f8ed1a768912 100644 --- a/tests/pos/infer-tracked-parsercombinators-givens.scala +++ b/tests/pos/infer-tracked-parsercombinators-givens.scala @@ -26,7 +26,6 @@ given apply: [C, E] => Combinator[Apply[C, E]] { } } -// TODO(kπ) infer tracked correctly here given combine[A, B](using tracked val f: Combinator[A], tracked val s: Combinator[B] { type Context = f.Context } diff --git a/tests/pos/infer-tracked-vector.scala b/tests/pos/infer-tracked-vector.scala deleted file mode 100644 index c952c37bf1e0..000000000000 --- a/tests/pos/infer-tracked-vector.scala +++ /dev/null @@ -1,82 +0,0 @@ -import scala.language.experimental.tracked -import scala.language.future - -object typeparams: - sealed trait Nat - object Z extends Nat - final case class S[N <: Nat]() extends Nat - - type Zero = Z.type - type Succ[N <: Nat] = S[N] - - sealed trait Fin[N <: Nat] - case class FZero[N <: Nat]() extends Fin[Succ[N]] - case class FSucc[N <: Nat](pred: Fin[N]) extends Fin[Succ[N]] - - object Fin: - def zero[N <: Nat]: Fin[Succ[N]] = FZero() - def succ[N <: Nat](i: Fin[N]): Fin[Succ[N]] = FSucc(i) - - sealed trait Vec[A, N <: Nat] - case class VNil[A]() extends Vec[A, Zero] - case class VCons[A, N <: Nat](head: A, tail: Vec[A, N]) extends Vec[A, Succ[N]] - - object Vec: - def empty[A]: Vec[A, Zero] = VNil() - def cons[A, N <: Nat](head: A, tail: Vec[A, N]): Vec[A, Succ[N]] = VCons(head, tail) - - def get[A, N <: Nat](v: Vec[A, N], index: Fin[N]): A = (v, index) match - case (VCons(h, _), FZero()) => h - case (VCons(_, t), FSucc(pred)) => get(t, pred) - - def runVec(): Unit = - val v: Vec[Int, Succ[Succ[Succ[Zero]]]] = Vec.cons(1, Vec.cons(2, Vec.cons(3, Vec.empty))) - - println(s"Element at index 0: ${Vec.get(v, Fin.zero)}") - println(s"Element at index 1: ${Vec.get(v, Fin.succ(Fin.zero))}") - println(s"Element at index 2: ${Vec.get(v, Fin.succ(Fin.succ(Fin.zero)))}") - // println(s"Element at index 2: ${Vec.get(v, Fin.succ(Fin.succ(Fin.succ(Fin.zero))))}") // error - -// TODO(kπ) check if I can get it to work -// object typemembers: -// sealed trait Nat -// object Z extends Nat -// case class S() extends Nat: -// type N <: Nat - -// type Zero = Z.type -// type Succ[N1 <: Nat] = S { type N = N1 } - -// sealed trait Fin: -// type N <: Nat -// case class FZero[N1 <: Nat]() extends Fin: -// type N = Succ[N1] -// case class FSucc(tracked val pred: Fin) extends Fin: -// type N = Succ[pred.N] - -// object Fin: -// def zero[N1 <: Nat]: Fin { type N = Succ[N1] } = FZero[N1]() -// def succ[N1 <: Nat](i: Fin { type N = N1 }): Fin { type N = Succ[N1] } = FSucc(i) - -// sealed trait Vec[A]: -// type N <: Nat -// case class VNil[A]() extends Vec[A]: -// type N = Zero -// case class VCons[A](head: A, tracked val tail: Vec[A]) extends Vec[A]: -// type N = Succ[tail.N] - -// object Vec: -// def empty[A]: Vec[A] = VNil() -// def cons[A](head: A, tail: Vec[A]): Vec[A] = VCons(head, tail) - -// def get[A](v: Vec[A], index: Fin { type N = v.N }): A = (v, index) match -// case (VCons(h, _), FZero()) => h -// case (VCons(_, t), FSucc(pred)) => get(t, pred) - -// // def runVec(): Unit = -// val v: Vec[Int] = Vec.cons(1, Vec.cons(2, Vec.cons(3, Vec.empty))) - -// println(s"Element at index 0: ${Vec.get(v, Fin.zero)}") -// println(s"Element at index 1: ${Vec.get(v, Fin.succ(Fin.zero))}") -// println(s"Element at index 2: ${Vec.get(v, Fin.succ(Fin.succ(Fin.zero)))}") -// // println(s"Element at index 2: ${Vec.get(v, Fin.succ(Fin.succ(Fin.succ(Fin.zero))))}") From abc41f645d8c6b6bffed653681e45657c3e36e11 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Fri, 1 Nov 2024 11:18:13 +0100 Subject: [PATCH 11/20] Revert "Separate features related to tracked into a separate sub-feature" This reverts commit 1e2c226cb0113b9d8dfb2ff0204693451bea2fdc. --- compiler/src/dotty/tools/dotc/config/Feature.scala | 6 +----- .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 4 ++-- .../src/dotty/tools/dotc/transform/PostTyper.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Namer.scala | 11 ++++++----- compiler/src/dotty/tools/dotc/typer/Typer.scala | 4 ++-- .../src/scala/runtime/stdLibPatches/language.scala | 7 ------- tests/pos/infer-tracked-1.scala | 2 +- tests/pos/infer-tracked-parent-refinements.scala | 2 +- .../infer-tracked-parsercombinators-expanded.scala | 2 +- .../pos/infer-tracked-parsercombinators-givens.scala | 2 +- tests/pos/infer-tracked.scala | 2 +- 13 files changed, 19 insertions(+), 29 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index c40ab518a800..776828de8f67 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -36,7 +36,6 @@ object Feature: val into = experimental("into") val namedTuples = experimental("namedTuples") val modularity = experimental("modularity") - val tracked = experimental("tracked") val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors") val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions") val betterFors = experimental("betterFors") @@ -69,7 +68,6 @@ object Feature: (into, "Allow into modifier on parameter types"), (namedTuples, "Allow named tuples"), (modularity, "Enable experimental modularity features"), - (tracked, "Enable tracked modifier"), (betterMatchTypeExtractors, "Enable better match type extractors"), (betterFors, "Enable improvements in `for` comprehensions") ) @@ -115,7 +113,7 @@ object Feature: * feature is defined. */ def enabled(feature: TermName)(using Context): Boolean = - enabledBySetting(feature) || enabledByImport(feature) + enabledBySetting(feature) || enabledByImport(feature) || feature == modularity /** Is auto-tupling enabled? */ def autoTuplingEnabled(using Context): Boolean = !enabled(nme.noAutoTupling) @@ -131,8 +129,6 @@ object Feature: def betterForsEnabled(using Context) = enabled(betterFors) - def trackedEnabled(using Context) = enabled(tracked) || enabled(modularity) - def genericNumberLiteralsEnabled(using Context) = enabled(genericNumberLiterals) def scala2ExperimentalMacroEnabled(using Context) = enabled(scala2macros) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 3fd80db693d9..de99ce0105ea 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -924,7 +924,7 @@ class TreeUnpickler(reader: TastyReader, val resType = if name == nme.CONSTRUCTOR then effectiveResultType(sym, paramss) - else if sym.isAllOf(Given | Method) && Feature.trackedEnabled then + else if sym.isAllOf(Given | Method) && Feature.enabled(Feature.modularity) then addParamRefinements(tpt.tpe, paramss) else tpt.tpe diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index fdbe5c3af04a..89f88faebc17 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3557,7 +3557,7 @@ object Parsers { if isErasedKw then mods = addModifier(mods) if paramOwner.isClass then - if isIdent(nme.tracked) && (in.featureEnabled(Feature.tracked) || in.featureEnabled(Feature.modularity)) && !in.lookahead.isColon then + if isIdent(nme.tracked) && in.featureEnabled(Feature.modularity) && !in.lookahead.isColon then mods = addModifier(mods) mods = addFlag(modifiers(start = mods), ParamAccessor) mods = @@ -3633,7 +3633,7 @@ object Parsers { || isIdent && (in.name == nme.inline // inline starts a name binding || in.name == nme.tracked // tracked starts a name binding under x.modularity - && (in.featureEnabled(Feature.tracked) || in.featureEnabled(Feature.modularity)) + && in.featureEnabled(Feature.modularity) || in.lookahead.isColon) // a following `:` starts a name binding (mods, paramsAreNamed) val params = diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index e62ecfadaaaa..0feee53ca50f 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -378,7 +378,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => def checkClassType(tpe: Type, stablePrefixReq: Boolean) = ctx.typer.checkClassType(tpe, tree.srcPos, traitReq = false, stablePrefixReq = stablePrefixReq, - refinementOK = Feature.trackedEnabled) + refinementOK = Feature.enabled(Feature.modularity)) checkClassType(tree.tpe, true) if !nu.tpe.isLambdaSub then // Check the constructor type as well; it could be an illegal singleton type diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 566306a177f1..1cd531046753 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -197,7 +197,7 @@ object Checking { * and that the instance conforms to the self type of the created class. */ def checkInstantiable(tp: Type, srcTp: Type, pos: SrcPos)(using Context): Unit = - tp.underlyingClassRef(refinementOK = Feature.trackedEnabled) match + tp.underlyingClassRef(refinementOK = Feature.enabled(modularity)) match case tref: TypeRef => val cls = tref.symbol if (cls.isOneOf(AbstractOrTrait)) { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 41df449f47a3..bd9a1d5f3289 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -294,7 +294,7 @@ class Namer { typer: Typer => val completer = tree match case tree: TypeDef => TypeDefCompleter(tree)(cctx) - case tree: ValOrDefDef if Feature.trackedEnabled && isNonInferingTree(tree) => + case tree: ValOrDefDef if Feature.enabled(Feature.modularity) && isNonInferingTree(tree) => NonInferingCompleter(tree)(cctx) case _ => Completer(tree)(cctx) val info = adjustIfModule(completer, tree) @@ -1614,7 +1614,7 @@ class Namer { typer: Typer => if (cls.isRefinementClass) ptype else { val pt = checkClassType( - if Feature.trackedEnabled + if Feature.enabled(modularity) then ptype.separateRefinements(cls, parentRefinements) else ptype, parent.srcPos, @@ -1790,7 +1790,8 @@ class Namer { typer: Typer => index(constr.leadingTypeParams) sym.owner.typeParams.foreach(_.ensureCompleted()) completeTrailingParamss(constr, sym, indexingCtor = true) - if Feature.trackedEnabled then + if Feature.enabled(modularity) then + // println(i"[indexConstructor] Checking if params of $constr need tracked") constr.termParamss.foreach(_.foreach(setTracked)) /** The signature of a module valdef. @@ -1940,7 +1941,7 @@ class Namer { typer: Typer => acc.resetFlag(PrivateLocal) acc.setFlag(Tracked) - if Feature.trackedEnabled then addTrackedIfNeeded(ddef, sym.maybeOwner) + if Feature.enabled(modularity) then addTrackedIfNeeded(ddef, sym.maybeOwner) if isConstructor then // set result type tree to unit, but take the current class as result type of the symbol @@ -1949,7 +1950,7 @@ class Namer { typer: Typer => if sym.isPrimaryConstructor then checkCaseClassParamDependencies(mt, sym.owner) mt else - val paramFn = if Feature.trackedEnabled && sym.isAllOf(Given | Method) then wrapRefinedMethType else wrapMethType + val paramFn = if Feature.enabled(Feature.modularity) && sym.isAllOf(Given | Method) then wrapRefinedMethType else wrapMethType valOrDefDefSig(ddef, sym, paramSymss, paramFn) end defDefSig diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 56703cf050c6..6bb5d1ee70ff 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1148,7 +1148,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if templ1.parents.isEmpty && isFullyDefined(pt, ForceDegree.flipBottom) && isSkolemFree(pt) - && isEligible(pt.underlyingClassRef(refinementOK = Feature.trackedEnabled)) + && isEligible(pt.underlyingClassRef(refinementOK = Feature.enabled(modularity))) then templ1 = cpy.Template(templ)(parents = untpd.TypeTree(pt) :: Nil) for case parent: RefTree <- templ1.parents do @@ -4719,7 +4719,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer cpy.Ident(qual)(qual.symbol.name.sourceModuleName.toTypeName) case _ => errorTree(tree, em"cannot convert from $tree to an instance creation expression") - val tycon = ctorResultType.underlyingClassRef(refinementOK = Feature.trackedEnabled) + val tycon = ctorResultType.underlyingClassRef(refinementOK = Feature.enabled(modularity)) typed( untpd.Select( untpd.New(untpd.TypedSplice(tpt.withType(tycon))), diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 10ec3280be8f..547710d55293 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -111,13 +111,6 @@ object language: @compileTimeOnly("`modularity` can only be used at compile time in import statements") object modularity - /** Experimental support for tracked modifier - * - * `tracked` is a subset of `modularity` - */ - @compileTimeOnly("`tracked` can only be used at compile time in import statements") - object tracked - /** Was needed to add support for relaxed imports of extension methods. * The language import is no longer needed as this is now a standard feature since SIP was accepted. * @see [[http://dotty.epfl.ch/docs/reference/contextual/extension-methods]] diff --git a/tests/pos/infer-tracked-1.scala b/tests/pos/infer-tracked-1.scala index e75fb88b7521..b4976a963074 100644 --- a/tests/pos/infer-tracked-1.scala +++ b/tests/pos/infer-tracked-1.scala @@ -1,4 +1,4 @@ -import scala.language.experimental.tracked +import scala.language.experimental.modularity import scala.language.future trait Ordering { diff --git a/tests/pos/infer-tracked-parent-refinements.scala b/tests/pos/infer-tracked-parent-refinements.scala index b50c6e88383d..0d71d7cc2897 100644 --- a/tests/pos/infer-tracked-parent-refinements.scala +++ b/tests/pos/infer-tracked-parent-refinements.scala @@ -1,4 +1,4 @@ -import scala.language.experimental.tracked +import scala.language.experimental.modularity import scala.language.future trait WithValue { type Value = Int } diff --git a/tests/pos/infer-tracked-parsercombinators-expanded.scala b/tests/pos/infer-tracked-parsercombinators-expanded.scala index 30828b1341bb..63c6aec9e84a 100644 --- a/tests/pos/infer-tracked-parsercombinators-expanded.scala +++ b/tests/pos/infer-tracked-parsercombinators-expanded.scala @@ -1,4 +1,4 @@ -import scala.language.experimental.tracked +import scala.language.experimental.modularity import scala.language.future import collection.mutable diff --git a/tests/pos/infer-tracked-parsercombinators-givens.scala b/tests/pos/infer-tracked-parsercombinators-givens.scala index f8ed1a768912..c183eb13a9ea 100644 --- a/tests/pos/infer-tracked-parsercombinators-givens.scala +++ b/tests/pos/infer-tracked-parsercombinators-givens.scala @@ -1,4 +1,4 @@ -import scala.language.experimental.tracked +import scala.language.experimental.modularity import scala.language.future import collection.mutable diff --git a/tests/pos/infer-tracked.scala b/tests/pos/infer-tracked.scala index 80626ab74267..496508ffdc6c 100644 --- a/tests/pos/infer-tracked.scala +++ b/tests/pos/infer-tracked.scala @@ -1,4 +1,4 @@ -import scala.language.experimental.tracked +import scala.language.experimental.modularity import scala.language.future abstract class C: From 5c08c5660fcd7db95b9fe936300b5dda73908e69 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Fri, 1 Nov 2024 12:31:06 +0100 Subject: [PATCH 12/20] Refactor checking for symbol references in signatures, when infering tracked --- .../src/dotty/tools/dotc/config/Feature.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 42 +++++++------------ 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 776828de8f67..8b9a64924ace 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -113,7 +113,7 @@ object Feature: * feature is defined. */ def enabled(feature: TermName)(using Context): Boolean = - enabledBySetting(feature) || enabledByImport(feature) || feature == modularity + enabledBySetting(feature) || enabledByImport(feature) /** Is auto-tupling enabled? */ def autoTuplingEnabled(using Context): Boolean = !enabled(nme.noAutoTupling) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index bd9a1d5f3289..714738be1fde 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -2003,13 +2003,9 @@ class Namer { typer: Typer => /** Try to infer if the parameter needs a `tracked` modifier */ def needsTracked(psym: Symbol, param: ValDef, owningSym: Symbol)(using Context) = - // println(i"Checking if $psym needs tracked") lazy val abstractContextBound = isContextBoundWitnessWithAbstractMembers(psym, param, owningSym) lazy val isRefInSignatures = psym.maybeOwner.isPrimaryConstructor - // && !psym.flags.is(Synthetic) - // && !psym.maybeOwner.flags.is(Synthetic) - // && !psym.maybeOwner.maybeOwner.flags.is(Synthetic) && isReferencedInPublicSignatures(psym) !psym.is(Tracked) && psym.isTerm @@ -2046,29 +2042,21 @@ class Namer { typer: Typer => case _ => false checkOwnerMemberSignatures(owner) - private def namedTypeWithPrefixContainsSymbolRef(tpe: Type, syms: List[Symbol])(using Context): Boolean = tpe match - case tpe: NamedType => tpe.prefix.exists && tpeContainsSymbolRef(tpe.prefix, syms) - case _ => false - - private def tpeContainsSymbolRef(tpe0: Type, syms: List[Symbol])(using Context): Boolean = - val tpe = tpe0.dropAlias.safeDealias - tpe match - case ExprType(resType) => tpeContainsSymbolRef(resType, syms) - case m : MethodOrPoly => - m.paramInfos.exists(tpeContainsSymbolRef(_, syms)) - || tpeContainsSymbolRef(m.resultType, syms) - case r @ RefinedType(parent, _, refinedInfo) => tpeContainsSymbolRef(parent, syms) || tpeContainsSymbolRef(refinedInfo, syms) - case TypeBounds(lo, hi) => tpeContainsSymbolRef(lo, syms) || tpeContainsSymbolRef(hi, syms) - case t: Type => - tpe.termSymbol.exists && syms.contains(tpe.termSymbol) - || tpe.argInfos.exists(tpeContainsSymbolRef(_, syms)) - || namedTypeWithPrefixContainsSymbolRef(tpe, syms) - - private def maybeParamAccessors(owner: Symbol, sym: Symbol)(using Context): List[Symbol] = - owner.infoOrCompleter match - case info: ClassInfo => - info.decls.lookupAll(sym.name).filter(d => d.is(ParamAccessor)).toList - case _ => List.empty + /** Check if any of syms are referenced in tpe */ + private def tpeContainsSymbolRef(tpe: Type, syms: List[Symbol])(using Context): Boolean = + val acc = new ExistsAccumulator( + { tpe => tpe.termSymbol.exists && syms.contains(tpe.termSymbol) }, + StopAt.Static, + forceLazy = false + ) { + override def apply(acc: Boolean, tpe: Type): Boolean = super.apply(acc, tpe.safeDealias) + } + acc(false, tpe) + + private def maybeParamAccessors(owner: Symbol, sym: Symbol)(using Context): List[Symbol] = owner.infoOrCompleter match + case info: ClassInfo => + info.decls.lookupAll(sym.name).filter(d => d.is(ParamAccessor)).toList + case _ => List.empty /** Under x.modularity, set every context bound evidence parameter of a class to be tracked, * provided it has a type that has an abstract type member. Reset private and local flags From 161f697ab55fb02e71782e75de2f31527692a614 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Fri, 15 Nov 2024 11:20:44 +0100 Subject: [PATCH 13/20] Also check type members, when infering tracked --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 13 ++++++++----- tests/pos/infer-tracked.scala | 6 ++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 714738be1fde..6eecba4c1802 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1791,7 +1791,6 @@ class Namer { typer: Typer => sym.owner.typeParams.foreach(_.ensureCompleted()) completeTrailingParamss(constr, sym, indexingCtor = true) if Feature.enabled(modularity) then - // println(i"[indexConstructor] Checking if params of $constr need tracked") constr.termParamss.foreach(_.foreach(setTracked)) /** The signature of a module valdef. @@ -2000,7 +1999,8 @@ class Namer { typer: Typer => cls.srcPos) case _ => - /** Try to infer if the parameter needs a `tracked` modifier + /** `psym` needs tracked if it is referenced in any of the public signatures of the defining class + * or when `psym` is a context bound witness with an abstract type member */ def needsTracked(psym: Symbol, param: ValDef, owningSym: Symbol)(using Context) = lazy val abstractContextBound = isContextBoundWitnessWithAbstractMembers(psym, param, owningSym) @@ -2025,6 +2025,7 @@ class Namer { typer: Typer => extension (sym: Symbol) def infoWithForceNonInferingCompleter(using Context): Type = sym.infoOrCompleter match case tpe: LazyType if tpe.isNonInfering => sym.info + case tpe if sym.isType => sym.info case info => info /** Under x.modularity, we add `tracked` to term parameters whose types are referenced @@ -2036,9 +2037,11 @@ class Namer { typer: Typer => def checkOwnerMemberSignatures(owner: Symbol): Boolean = owner.infoOrCompleter match case info: ClassInfo => - info.decls.filter(_.isTerm).filter(_.isPublic) + info.decls.filter(_.isPublic) .filter(_ != sym.maybeOwner) - .exists(d => tpeContainsSymbolRef(d.infoWithForceNonInferingCompleter, accessorSyms)) + .exists { decl => + tpeContainsSymbolRef(decl.infoWithForceNonInferingCompleter, accessorSyms) + } case _ => false checkOwnerMemberSignatures(owner) @@ -2056,7 +2059,7 @@ class Namer { typer: Typer => private def maybeParamAccessors(owner: Symbol, sym: Symbol)(using Context): List[Symbol] = owner.infoOrCompleter match case info: ClassInfo => info.decls.lookupAll(sym.name).filter(d => d.is(ParamAccessor)).toList - case _ => List.empty + case _ => List(sym) /** Under x.modularity, set every context bound evidence parameter of a class to be tracked, * provided it has a type that has an abstract type member. Reset private and local flags diff --git a/tests/pos/infer-tracked.scala b/tests/pos/infer-tracked.scala index 496508ffdc6c..c29ccfbb7708 100644 --- a/tests/pos/infer-tracked.scala +++ b/tests/pos/infer-tracked.scala @@ -22,6 +22,9 @@ case class J(c: C): case class K(c: C): def result[B >: c.T]: B = c.foo +case class L(c: C): + type T = c.T + def Test = val c = new C: type T = Int @@ -44,3 +47,6 @@ def Test = val k = K(c) val _: Int = k.result + + val l = L(c) + summon[l.T =:= Int] From 5e295b3f89f6ee5f385b3aac6e5092009893641a Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Fri, 15 Nov 2024 11:24:10 +0100 Subject: [PATCH 14/20] Cleanup infer-tracked --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala | 1 - tests/pos/infer-tracked.scala | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 6eecba4c1802..6683b86fc6f9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -2070,7 +2070,7 @@ class Namer { typer: Typer => sym.maybeOwner.maybeOwner.infoOrCompleter match case info: ClassInfo if !sym.is(Tracked) && isContextBoundWitnessWithAbstractMembers(sym, param, sym.maybeOwner.maybeOwner) => - typr.println(i"set tracked $param, $sym: ${sym.info}") + typr.println(i"set tracked $param, $sym: ${sym.info} containing ${sym.info.memberNames(abstractTypeNameFilter).toList}") for acc <- info.decls.lookupAll(sym.name) if acc.is(ParamAccessor) do acc.resetFlag(PrivateLocal) acc.setFlag(Tracked) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala index ab7fd918ebb7..1a8337e0c6b7 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala @@ -127,7 +127,6 @@ case class ScaladocTastyInspector()(using ctx: DocContext) extends Inspector: topLevels ++= intrinsicTypeDefs val scalaPckg = defn.ScalaPackage given parser.qctx.type = parser.qctx - import parser.dri topLevels += "scala" -> Member(scalaPckg.fullName, "", scalaPckg.dri, Kind.Package) topLevels += mergeAnyRefAliasAndObject(parser) diff --git a/tests/pos/infer-tracked.scala b/tests/pos/infer-tracked.scala index c29ccfbb7708..8c8d7b1de126 100644 --- a/tests/pos/infer-tracked.scala +++ b/tests/pos/infer-tracked.scala @@ -33,6 +33,7 @@ def Test = val f = new F(c) val _: Int = f.result + // Not really possible to work with inference in Namer, should emit a lint // val g = new G(c) // val _: Int = g.result From 0e453a181737021edad7e248165bffeba478a9b9 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 18 Nov 2024 11:45:58 +0100 Subject: [PATCH 15/20] Add a section about tracked inference to the modularity doc --- .../reference/experimental/modularity.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/_docs/reference/experimental/modularity.md b/docs/_docs/reference/experimental/modularity.md index a989b71770af..717fda26db33 100644 --- a/docs/_docs/reference/experimental/modularity.md +++ b/docs/_docs/reference/experimental/modularity.md @@ -116,6 +116,37 @@ ClsParam ::= {Annotation} [{Modifier | ‘tracked’} (‘val’ | ‘var’)] The (soft) `tracked` modifier is only allowed for `val` parameters of classes. +**Tracked inference** + +In some cases `tracked` can be infered and doesn't have to be written +explicitly. A common such case is when a class parameter is referenced in the +signatures of the public members of the class. e.g. +```scala 3 +class OrdSet(val ord: Ordering) { + type Set = List[ord.T] + def empty: Set = Nil + + implicit class helper(s: Set) { + def add(x: ord.T): Set = x :: remove(x) + def remove(x: ord.T): Set = s.filter(e => ord.compare(x, e) != 0) + def member(x: ord.T): Boolean = s.exists(e => ord.compare(x, e) == 0) + } +} +``` +In the example above, `ord` is referenced in the signatures of the public +members of `OrdSet`, so a `tracked` modifier will be inserted automatically. + +Another common case is when a context bound has an associated type (i.e. an abstract type member) e.g. +```scala 3 +trait TC: + type Self + type T + +class Klass[A: {TC as tc}] +``` + +Here, `tc` is a context bound with an associated type `T`, so `tracked` will be inferred for `tc`. + **Discussion** Since `tracked` is so useful, why not assume it by default? First, `tracked` makes sense only for `val` parameters. If a class parameter is not also a field declared using `val` then there's nothing to refine in the constructor result type. One could think of at least making all `val` parameters tracked by default, but that would be a backwards incompatible change. For instance, the following code would break: From c02798726b8d52f9b591516e627c42d5df6cb6e3 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 18 Nov 2024 17:57:19 +0100 Subject: [PATCH 16/20] Add some test cases with current limitations --- .../src/dotty/tools/dotc/ast/Desugar.scala | 4 +- .../src/dotty/tools/dotc/typer/Namer.scala | 12 ++--- .../test/dotc/pos-test-pickling.blacklist | 1 - ...fer-tracked-parsercombinators-givens.scala | 54 ------------------- tests/pos/infer-tracked.scala | 11 +++- 5 files changed, 17 insertions(+), 65 deletions(-) delete mode 100644 tests/pos/infer-tracked-parsercombinators-givens.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 56c153498f87..94290076b92a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1052,8 +1052,8 @@ object desugar { // we can reuse the constructor parameters; no derived params are needed. DefDef( className.toTermName, joinParams(constrTparams, defParamss), classTypeRef, creatorExpr - ) .withMods(companionMods | mods.flags.toTermFlags & (GivenOrImplicit | Inline) | finalFlag) - .withSpan(cdef.span) :: Nil + ).withMods(companionMods | mods.flags.toTermFlags & (GivenOrImplicit | Inline) | finalFlag) + .withSpan(cdef.span) :: Nil } val self1 = { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 6683b86fc6f9..1b27abe9e888 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -2078,12 +2078,12 @@ class Namer { typer: Typer => case _ => def inferredResultType( - mdef: ValOrDefDef, - sym: Symbol, - paramss: List[List[Symbol]], - paramFn: Type => Type, - fallbackProto: Type - )(using Context): Type = + mdef: ValOrDefDef, + sym: Symbol, + paramss: List[List[Symbol]], + paramFn: Type => Type, + fallbackProto: Type + )(using Context): Type = /** A type for this definition that might be inherited from elsewhere: * If this is a setter parameter, the corresponding getter type. diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 72431fb08e48..ebdd414ea7f2 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -136,7 +136,6 @@ parsercombinators-new-syntax.scala hylolib-deferred-given hylolib-cb hylolib -infer-tracked-parsercombinators-givens.scala # typecheckErrors method unpickling i21415.scala diff --git a/tests/pos/infer-tracked-parsercombinators-givens.scala b/tests/pos/infer-tracked-parsercombinators-givens.scala deleted file mode 100644 index c183eb13a9ea..000000000000 --- a/tests/pos/infer-tracked-parsercombinators-givens.scala +++ /dev/null @@ -1,54 +0,0 @@ -import scala.language.experimental.modularity -import scala.language.future -import collection.mutable - -/// A parser combinator. -trait Combinator[T]: - - /// The context from which elements are being parsed, typically a stream of tokens. - type Context - /// The element being parsed. - type Element - - extension (self: T) - /// Parses and returns an element from `context`. - def parse(context: Context): Option[Element] -end Combinator - -final case class Apply[C, E](action: C => Option[E]) -final case class Combine[A, B](first: A, second: B) - -given apply: [C, E] => Combinator[Apply[C, E]] { - type Context = C - type Element = E - extension(self: Apply[C, E]) { - def parse(context: C): Option[E] = self.action(context) - } -} - -given combine[A, B](using - tracked val f: Combinator[A], - tracked val s: Combinator[B] { type Context = f.Context } -): Combinator[Combine[A, B]] with { - type Context = f.Context - type Element = (f.Element, s.Element) - extension(self: Combine[A, B]) { - def parse(context: Context): Option[Element] = ??? - } -} - -extension [A] (buf: mutable.ListBuffer[A]) def popFirst() = - if buf.isEmpty then None - else try Some(buf.head) finally buf.remove(0) - -@main def hello: Unit = { - val source = (0 to 10).toList - val stream = source.to(mutable.ListBuffer) - - val n = Apply[mutable.ListBuffer[Int], Int](s => s.popFirst()) - val m = Combine(n, n) - - val r = m.parse(stream) // error: type mismatch, found `mutable.ListBuffer[Int]`, required `?1.Context` - val rc: Option[(Int, Int)] = r - // it would be great if this worked -} diff --git a/tests/pos/infer-tracked.scala b/tests/pos/infer-tracked.scala index 8c8d7b1de126..08caac1c46c1 100644 --- a/tests/pos/infer-tracked.scala +++ b/tests/pos/infer-tracked.scala @@ -25,6 +25,11 @@ case class K(c: C): case class L(c: C): type T = c.T +class M + +given mInst: (c: C) => M: + def foo: c.T = c.foo + def Test = val c = new C: type T = Int @@ -33,8 +38,7 @@ def Test = val f = new F(c) val _: Int = f.result - // Not really possible to work with inference in Namer, should emit a lint - // val g = new G(c) + // val g = new G(c) // current limitation of infering in Namer, should emit a lint // val _: Int = g.result val h = new H(c) @@ -51,3 +55,6 @@ def Test = val l = L(c) summon[l.T =:= Int] + + // val m = mInst(using c) // current limitation, we infer tracked after this desugaring + // val _: Int = m.foo From 9231b699b622b614f8d5094186f94bfc3a16be5e Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Fri, 29 Nov 2024 12:56:28 +0100 Subject: [PATCH 17/20] Infer tracked for explicit type class witnesses --- .../src/dotty/tools/dotc/typer/Namer.scala | 4 ++-- tests/pos/infer-tracked-explicit-witness.scala | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 tests/pos/infer-tracked-explicit-witness.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 1b27abe9e888..3b5d2eddc2a1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1935,9 +1935,9 @@ class Namer { typer: Typer => for params <- ddef.termParamss; param <- params do val psym = symbolOfTree(param) if needsTracked(psym, param, owningSym) then - psym.setFlag(Tracked) for acc <- sym.maybeOwner.infoOrCompleter.decls.lookupAll(psym.name) if acc.is(ParamAccessor) do acc.resetFlag(PrivateLocal) + psym.setFlag(Tracked) acc.setFlag(Tracked) if Feature.enabled(modularity) then addTrackedIfNeeded(ddef, sym.maybeOwner) @@ -2019,7 +2019,7 @@ class Namer { typer: Typer => */ def isContextBoundWitnessWithAbstractMembers(psym: Symbol, param: ValDef, owningSym: Symbol)(using Context): Boolean = (owningSym.isClass || owningSym.isAllOf(Given | Method)) - && param.hasAttachment(ContextBoundParam) + && (param.hasAttachment(ContextBoundParam) || psym.isOneOf(GivenOrImplicit)) && psym.info.memberNames(abstractTypeNameFilter).nonEmpty extension (sym: Symbol) diff --git a/tests/pos/infer-tracked-explicit-witness.scala b/tests/pos/infer-tracked-explicit-witness.scala new file mode 100644 index 000000000000..7326919b0d6a --- /dev/null +++ b/tests/pos/infer-tracked-explicit-witness.scala @@ -0,0 +1,18 @@ +import scala.language.experimental.modularity + +trait T: + type Self + type X + def foo: Self + +class D[C](using val wd: C is T) +class E(using val we: Int is T) + +def Test = + given w: Int is T: + def foo: Int = 42 + type X = Long + val d = D(using w) + summon[d.wd.X =:= Long] + val e = E(using w) + summon[e.we.X =:= Long] From 7442d5a5bcd95f60cd66b63c589ac0050893c1d1 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Fri, 6 Dec 2024 16:58:47 +0100 Subject: [PATCH 18/20] Don't add tracked to PrivateLocal witnesses --- .../src/dotty/tools/dotc/typer/Namer.scala | 50 ++++++++++--------- .../neg/infer-tracked-explicit-witness.scala | 18 +++++++ 2 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 tests/neg/infer-tracked-explicit-witness.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 3b5d2eddc2a1..e1cbd600d39c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1791,7 +1791,7 @@ class Namer { typer: Typer => sym.owner.typeParams.foreach(_.ensureCompleted()) completeTrailingParamss(constr, sym, indexingCtor = true) if Feature.enabled(modularity) then - constr.termParamss.foreach(_.foreach(setTracked)) + constr.termParamss.foreach(_.foreach(setTrackedConstrParam)) /** The signature of a module valdef. * This will compute the corresponding module class TypeRef immediately @@ -1935,10 +1935,8 @@ class Namer { typer: Typer => for params <- ddef.termParamss; param <- params do val psym = symbolOfTree(param) if needsTracked(psym, param, owningSym) then - for acc <- sym.maybeOwner.infoOrCompleter.decls.lookupAll(psym.name) if acc.is(ParamAccessor) do - acc.resetFlag(PrivateLocal) - psym.setFlag(Tracked) - acc.setFlag(Tracked) + psym.setFlag(Tracked) + setParamTrackedWithAccessors(psym, sym.maybeOwner.infoOrCompleter) if Feature.enabled(modularity) then addTrackedIfNeeded(ddef, sym.maybeOwner) @@ -1999,8 +1997,15 @@ class Namer { typer: Typer => cls.srcPos) case _ => - /** `psym` needs tracked if it is referenced in any of the public signatures of the defining class - * or when `psym` is a context bound witness with an abstract type member + private def setParamTrackedWithAccessors(psym: Symbol, ownerTpe: Type)(using Context): Unit = + for acc <- ownerTpe.decls.lookupAll(psym.name) if acc.is(ParamAccessor) do + acc.resetFlag(PrivateLocal) + psym.setFlag(Tracked) + acc.setFlag(Tracked) + + /** `psym` needs tracked if it is referenced in any of the public signatures + * of the defining class or when `psym` is a context bound witness with an + * abstract type member */ def needsTracked(psym: Symbol, param: ValDef, owningSym: Symbol)(using Context) = lazy val abstractContextBound = isContextBoundWitnessWithAbstractMembers(psym, param, owningSym) @@ -2014,24 +2019,25 @@ class Namer { typer: Typer => || isRefInSignatures ) - /** Under x.modularity, we add `tracked` to context bound witnesses - * that have abstract type members + /** Under x.modularity, we add `tracked` to context bound witnesses and + * explicit evidence parameters that have abstract type members */ - def isContextBoundWitnessWithAbstractMembers(psym: Symbol, param: ValDef, owningSym: Symbol)(using Context): Boolean = + private def isContextBoundWitnessWithAbstractMembers(psym: Symbol, param: ValDef, owningSym: Symbol)(using Context): Boolean = + val accessorSyms = maybeParamAccessors(owningSym, psym) (owningSym.isClass || owningSym.isAllOf(Given | Method)) - && (param.hasAttachment(ContextBoundParam) || psym.isOneOf(GivenOrImplicit)) + && (param.hasAttachment(ContextBoundParam) || (psym.isOneOf(GivenOrImplicit) && !accessorSyms.forall(_.isOneOf(PrivateLocal)))) && psym.info.memberNames(abstractTypeNameFilter).nonEmpty extension (sym: Symbol) - def infoWithForceNonInferingCompleter(using Context): Type = sym.infoOrCompleter match + private def infoWithForceNonInferingCompleter(using Context): Type = sym.infoOrCompleter match case tpe: LazyType if tpe.isNonInfering => sym.info case tpe if sym.isType => sym.info case info => info - /** Under x.modularity, we add `tracked` to term parameters whose types are referenced - * in public signatures of the defining class + /** Under x.modularity, we add `tracked` to term parameters whose types are + * referenced in public signatures of the defining class */ - def isReferencedInPublicSignatures(sym: Symbol)(using Context): Boolean = + private def isReferencedInPublicSignatures(sym: Symbol)(using Context): Boolean = val owner = sym.maybeOwner.maybeOwner val accessorSyms = maybeParamAccessors(owner, sym) def checkOwnerMemberSignatures(owner: Symbol): Boolean = @@ -2061,20 +2067,18 @@ class Namer { typer: Typer => info.decls.lookupAll(sym.name).filter(d => d.is(ParamAccessor)).toList case _ => List(sym) - /** Under x.modularity, set every context bound evidence parameter of a class to be tracked, - * provided it has a type that has an abstract type member. Reset private and local flags - * so that the parameter becomes a `val`. + /** Under x.modularity, set every context bound evidence parameter or public + * using parameter of a class to be tracked, provided it has a type that has + * an abstract type member. Reset private and local flags so that the + * parameter becomes a `val`. */ - def setTracked(param: ValDef)(using Context): Unit = + def setTrackedConstrParam(param: ValDef)(using Context): Unit = val sym = symbolOfTree(param) sym.maybeOwner.maybeOwner.infoOrCompleter match case info: ClassInfo if !sym.is(Tracked) && isContextBoundWitnessWithAbstractMembers(sym, param, sym.maybeOwner.maybeOwner) => typr.println(i"set tracked $param, $sym: ${sym.info} containing ${sym.info.memberNames(abstractTypeNameFilter).toList}") - for acc <- info.decls.lookupAll(sym.name) if acc.is(ParamAccessor) do - acc.resetFlag(PrivateLocal) - acc.setFlag(Tracked) - sym.setFlag(Tracked) + setParamTrackedWithAccessors(sym, info) case _ => def inferredResultType( diff --git a/tests/neg/infer-tracked-explicit-witness.scala b/tests/neg/infer-tracked-explicit-witness.scala new file mode 100644 index 000000000000..853cec748b03 --- /dev/null +++ b/tests/neg/infer-tracked-explicit-witness.scala @@ -0,0 +1,18 @@ +import scala.language.experimental.modularity + +trait T: + type Self + type X + def foo: Self + +class D[C](using wd: C is T) +class E(using we: Int is T) + +def Test = + given w: Int is T: + def foo: Int = 42 + type X = Long + val d = D(using w) + summon[d.wd.X =:= Long] // error + val e = E(using w) + summon[e.we.X =:= Long] // error From 90d60cf04224aef6bc0e157829ad3ef6f175a8d9 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Fri, 10 Jan 2025 13:22:06 +0100 Subject: [PATCH 19/20] tracked inference review changes --- .github/workflows/ci.yaml | 8 ++++---- .../src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Namer.scala | 14 +++++--------- docs/_docs/reference/experimental/modularity.md | 4 ++-- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b69ce997a131..03a18b23ddd8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,10 +26,10 @@ on: - cron: '0 3 * * *' # Every day at 3 AM workflow_dispatch: -# Cancels any in-progress runs within the same group identified by workflow name and GH reference (branch or tag) +# Cancels any in-progress runs within the same group identified by workflow name and GH reference (branch or tag) # For example it would: # - terminate previous PR CI execution after pushing more changes to the same PR branch -# - terminate previous on-push CI run after merging new PR to main +# - terminate previous on-push CI run after merging new PR to main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} @@ -831,7 +831,7 @@ jobs: path: . - name: Prepare MSI package shell: bash - run: | + run: | msiInstaller="scala3-${{ env.RELEASE_TAG }}.msi" mv scala.msi "${msiInstaller}" sha256sum "${msiInstaller}" > "${msiInstaller}.sha256" @@ -870,7 +870,7 @@ jobs: # ${upload("tar.gz archive", s"$filename.tar.gz", "application/gzip", distribution)} # ${upload("tar.gz archive SHA", s"$filename.tar.gz.sha256", "text/plain", distribution)} # """ - # def uploadMSI() = + # def uploadMSI() = # val distribution = "Windows x86_64 MSI" # s""" # # $distribution diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index a645e5ab6d88..fd6cef33f603 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2753,7 +2753,7 @@ object SymDenotations { def complete(denot: SymDenotation)(using Context): Unit /** Is this a completer for an explicit type tree */ - def isNonInfering: Boolean = false + def isExplicit: Boolean = false def apply(sym: Symbol): LazyType = this def apply(module: TermSymbol, modcls: ClassSymbol): LazyType = this diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e1cbd600d39c..4dbe86d70a53 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -278,7 +278,7 @@ class Namer { typer: Typer => if rhs.isEmpty || flags.is(Opaque) then flags |= Deferred if flags.is(Param) then tree.rhs else analyzeRHS(tree.rhs) - def isNonInferingTree(tree: ValOrDefDef): Boolean = + def hasExplicitType(tree: ValOrDefDef): Boolean = !tree.tpt.isEmpty || tree.mods.isOneOf(TermParamOrAccessor) // to complete a constructor, move one context further out -- this @@ -294,8 +294,8 @@ class Namer { typer: Typer => val completer = tree match case tree: TypeDef => TypeDefCompleter(tree)(cctx) - case tree: ValOrDefDef if Feature.enabled(Feature.modularity) && isNonInferingTree(tree) => - NonInferingCompleter(tree)(cctx) + case tree: ValOrDefDef if Feature.enabled(Feature.modularity) && hasExplicitType(tree) => + new Completer(tree, isExplicit = true)(cctx) case _ => Completer(tree)(cctx) val info = adjustIfModule(completer, tree) createOrRefine[Symbol](tree, name, flags, ctx.owner, _ => info, @@ -805,7 +805,7 @@ class Namer { typer: Typer => } /** The completer of a symbol defined by a member def or import (except ClassSymbols) */ - class Completer(val original: Tree)(ictx: Context) extends LazyType with SymbolLoaders.SecondCompleter { + class Completer(val original: Tree, override val isExplicit: Boolean = false)(ictx: Context) extends LazyType with SymbolLoaders.SecondCompleter { protected def localContext(owner: Symbol): FreshContext = ctx.fresh.setOwner(owner).setTree(original) @@ -1738,10 +1738,6 @@ class Namer { typer: Typer => } } - class NonInferingCompleter(original: ValOrDefDef)(ictx: Context) extends Completer(original)(ictx) { - override def isNonInfering: Boolean = true - } - /** Possible actions to perform when deciding on a forwarder for a member */ private enum CanForward: case Yes @@ -2030,7 +2026,7 @@ class Namer { typer: Typer => extension (sym: Symbol) private def infoWithForceNonInferingCompleter(using Context): Type = sym.infoOrCompleter match - case tpe: LazyType if tpe.isNonInfering => sym.info + case tpe: LazyType if tpe.isExplicit => sym.info case tpe if sym.isType => sym.info case info => info diff --git a/docs/_docs/reference/experimental/modularity.md b/docs/_docs/reference/experimental/modularity.md index 717fda26db33..ce6f654a5bad 100644 --- a/docs/_docs/reference/experimental/modularity.md +++ b/docs/_docs/reference/experimental/modularity.md @@ -116,7 +116,7 @@ ClsParam ::= {Annotation} [{Modifier | ‘tracked’} (‘val’ | ‘var’)] The (soft) `tracked` modifier is only allowed for `val` parameters of classes. -**Tracked inference** +### Tracked inference In some cases `tracked` can be infered and doesn't have to be written explicitly. A common such case is when a class parameter is referenced in the @@ -147,7 +147,7 @@ class Klass[A: {TC as tc}] Here, `tc` is a context bound with an associated type `T`, so `tracked` will be inferred for `tc`. -**Discussion** +### Discussion Since `tracked` is so useful, why not assume it by default? First, `tracked` makes sense only for `val` parameters. If a class parameter is not also a field declared using `val` then there's nothing to refine in the constructor result type. One could think of at least making all `val` parameters tracked by default, but that would be a backwards incompatible change. For instance, the following code would break: From a96c9c5db5df97e0570100504614092b23e49554 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Fri, 10 Jan 2025 13:29:59 +0100 Subject: [PATCH 20/20] tracked inference review changes cd. --- .github/workflows/ci.yaml | 2 +- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 22673c9c58a6..cc1eb5d40d97 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -29,7 +29,7 @@ on: # Cancels any in-progress runs within the same group identified by workflow name and GH reference (branch or tag) # For example it would: # - terminate previous PR CI execution after pushing more changes to the same PR branch -# - terminate previous on-push CI run after merging new PR to main +# - terminate previous on-push CI run after merging new PR to main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 342af4f6d585..67e1885b511f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1057,8 +1057,8 @@ object desugar { // we can reuse the constructor parameters; no derived params are needed. DefDef( className.toTermName, joinParams(constrTparams, defParamss), classTypeRef, creatorExpr - ).withMods(companionMods | mods.flags.toTermFlags & (GivenOrImplicit | Inline) | finalFlag) - .withSpan(cdef.span) :: Nil + ) .withMods(companionMods | mods.flags.toTermFlags & (GivenOrImplicit | Inline) | finalFlag) + .withSpan(cdef.span) :: Nil } val self1 = {