From 0036ecb648feba68c1fa163a88820a739e2b3be8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 17 May 2021 14:12:18 +0200 Subject: [PATCH 01/87] First version of RefineTypes --- compiler/src/dotty/tools/dotc/Compiler.scala | 3 +- .../dotty/tools/dotc/config/Printers.scala | 1 + .../src/dotty/tools/dotc/core/Contexts.scala | 2 + .../src/dotty/tools/dotc/core/Phases.scala | 6 +- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../dotty/tools/dotc/typer/Inferencing.scala | 4 +- .../dotty/tools/dotc/typer/RefineTypes.scala | 144 ++++++++++++++++++ .../dotty/tools/dotc/typer/Synthesizer.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 4 +- 9 files changed, 161 insertions(+), 7 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/typer/RefineTypes.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index e213e1bee01f..b7094f0e9e6f 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -3,7 +3,7 @@ package dotc import core._ import Contexts._ -import typer.{FrontEnd, RefChecks} +import typer.{FrontEnd, RefChecks, RefineTypes} import Phases.Phase import transform._ import dotty.tools.backend.jvm.{CollectSuperCalls, GenBCode} @@ -37,6 +37,7 @@ class Compiler { /** Phases dealing with the frontend up to trees ready for TASTY pickling */ protected def frontendPhases: List[List[Phase]] = List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer + List(new RefineTypes) :: List(new YCheckPositions) :: // YCheck positions List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 8e13e50e59b7..64a71259e3d8 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -39,6 +39,7 @@ object Printers { val quotePickling = noPrinter val plugins = noPrinter val refcheck = noPrinter + val refiner = noPrinter val simplify = noPrinter val staging = noPrinter val subtyping = noPrinter diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 6ef1856f5cfa..3c0c685ce65d 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -391,6 +391,8 @@ object Contexts { /** Is current phase after FrontEnd? */ final def isAfterTyper = base.isAfterTyper(phase) + final def isAfterRefiner = base.isAfterRefiner(phase) + /** Is this a context for the members of a class definition? */ def isClassDefContext: Boolean = owner.isClass && (owner ne outer.owner) diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 4646751192b4..524991347050 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -13,7 +13,7 @@ import scala.collection.mutable.ListBuffer import dotty.tools.dotc.transform.MegaPhase._ import dotty.tools.dotc.transform._ import Periods._ -import typer.{FrontEnd, RefChecks} +import typer.{FrontEnd, RefineTypes, RefChecks} import typer.ImportInfo.withRootImports import ast.tpd import scala.annotation.internal.sharable @@ -195,6 +195,7 @@ object Phases { } private var myTyperPhase: Phase = _ + private var myRefinerPhase: Phase = _ private var myPostTyperPhase: Phase = _ private var mySbtExtractDependenciesPhase: Phase = _ private var myPicklerPhase: Phase = _ @@ -216,6 +217,7 @@ object Phases { private var myGenBCodePhase: Phase = _ final def typerPhase: Phase = myTyperPhase + final def refinerPhase: Phase = myRefinerPhase final def postTyperPhase: Phase = myPostTyperPhase final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase final def picklerPhase: Phase = myPicklerPhase @@ -240,6 +242,7 @@ object Phases { def phaseOfClass(pclass: Class[?]) = phases.find(pclass.isInstance).getOrElse(NoPhase) myTyperPhase = phaseOfClass(classOf[FrontEnd]) + myRefinerPhase = phases.find(_.isInstanceOf[RefineTypes]).getOrElse(myTyperPhase) myPostTyperPhase = phaseOfClass(classOf[PostTyper]) mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies]) myPicklerPhase = phaseOfClass(classOf[Pickler]) @@ -262,6 +265,7 @@ object Phases { } final def isAfterTyper(phase: Phase): Boolean = phase.id > typerPhase.id + final def isAfterRefiner(phase: Phase): Boolean = phase.id > refinerPhase.id } abstract class Phase { diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e0c1c35e850a..4fb080f0d591 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4428,7 +4428,7 @@ object Types { owningState1.ownedVars -= this owningState = null // no longer needed; null out to avoid a memory leak - private[core] def resetInst(ts: TyperState): Unit = + private[dotc] def resetInst(ts: TyperState): Unit = myInst = NoType owningState = new WeakReference(ts) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 97f14e2fe5a4..68fae95ac754 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -533,6 +533,8 @@ trait Inferencing { this: Typer => import Inferencing._ import tpd._ + def qualifyForInterpolation(tvars: TypeVars)(using Context): TypeVars = tvars + /** Interpolate undetermined type variables in the widened type of this tree. * @param tree the tree whose type is interpolated * @param pt the expected result type @@ -563,7 +565,7 @@ trait Inferencing { this: Typer => val ownedVars = state.ownedVars if ((ownedVars ne locked) && !ownedVars.isEmpty) { - val qualifying = ownedVars -- locked + val qualifying = qualifyForInterpolation(ownedVars -- locked) if (!qualifying.isEmpty) { typr.println(i"interpolate $tree: ${tree.tpe.widen} in $state, owned vars = ${state.ownedVars.toList}%, %, qualifying = ${qualifying.toList}%, %, previous = ${locked.toList}%, % / ${state.constraint}") val resultAlreadyConstrained = diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala new file mode 100644 index 000000000000..af2069c0e098 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -0,0 +1,144 @@ +package dotty.tools +package dotc +package typer + +import core._ +import Phases.*, DenotTransformers.*, SymDenotations.* +import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.* +import Types._ +import Symbols._ +import StdNames._ +import Decorators._ +import typer.ProtoTypes._ +import config.Printers.refiner +import ast.{tpd, untpd, Trees} +import core.NameKinds.{DocArtifactName, OuterSelectName} +import Trees._ +import scala.util.control.NonFatal +import typer.ErrorReporting._ +import util.Spans.Span +import Nullables._ +import transform.* +import scala.collection.mutable +import util.Chars.* +import reporting._ +import ProtoTypes._ +import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions + + +class RefineTypes extends Phase, IdentityDenotTransformer: + import RefineTypes.* + import ast.tpd.* + + def phaseName: String = RefineTypes.name + + override def isTyper: Boolean = true + + def run(using Context): Unit = + println(i"refine types of ${ctx.compilationUnit}") + val refiner = newRefiner() + val refineCtx = ctx + .fresh + .setMode(Mode.ImplicitsEnabled) + .setNewTyperState() + .setTyper(refiner) + ctx.typerState.constraint = OrderingConstraint.empty + refiner.typedExpr(ctx.compilationUnit.tpdTree)(using refineCtx) + + def newRefiner(): TypeRefiner = TypeRefiner() + + class TypeRefiner extends ReTyper: + import ast.tpd.* + + // don't check value classes after typer, as the constraint about constructors doesn't hold after transform + override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(using Context): Unit = () + + /** Exclude all typevars that are referred to in a parameter of an enclosing closure */ + override def qualifyForInterpolation(tvars: TypeVars)(using Context): TypeVars = + var qualifying = tvars + val anonFuns = ctx.owner.ownersIterator.filter(_.isAnonymousFunction).toList + if anonFuns.nonEmpty && false then + val anonFunParamTypes = anonFuns.flatMap(_.rawParamss.flatten).map(_.info) + qualifying.foreach (tvar => + if anonFunParamTypes.exists(formal => + formal.existsPart(tvar eq, stopAtStatic = true, forceLazy = false)) + then qualifying -= tvar + ) + qualifying + + /** Exclude from double definition checks any erased symbols that were + * made `private` in phase `UnlinkErasedDecls`. These symbols will be removed + * completely in phase `Erasure` if they are defined in a currently compiled unit. + */ + override def excludeFromDoubleDeclCheck(sym: Symbol)(using Context): Boolean = + sym.isEffectivelyErased && sym.is(Private) && !sym.initial.is(Private) + + override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = + trace(i"typed $tree, $pt", refiner, show = true) { + tree match + case _: untpd.TypedSplice | _: untpd.Thicket | _: EmptyValDef[?] => + super.typedUnadapted(tree, pt, locked) + case _ if tree.isType => + promote(tree) + case _ => + super.typedUnadapted(tree, pt, locked) + } + + override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = + val Select(qual, name) = tree + val qual1 = withoutMode(Mode.Pattern)(typed(qual, AnySelectionProto)) + val qualType = qual1.tpe.widenIfUnstable + val pre = maybeSkolemizePrefix(qualType, name) + val mbr = qualType.findMember(name, pre).suchThat(tree.symbol ==) + val ownType = qualType.select(name, mbr) + untpd.cpy.Select(tree)(qual1, name).withType(ownType) + + override def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree = + if tree.symbol == defn.StdLibPatchesPackage then + promote(tree) // don't check stdlib patches, since their symbols were highjacked by stdlib classes + else + super.typedPackageDef(tree) + + override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = + val isInferred = tree.args.forall { + case arg: TypeVarBinder[?] => arg.typeOpt.isInstanceOf[TypeVar] + case _ => false + } + if isInferred then + var origin: Type = null + for arg <- tree.args do + assert(arg.isInstanceOf[untpd.TypeTree], arg) + arg.typeOpt match + case tvar: TypeVar => + if origin == null then origin = tvar.origin.binder + else assert(tvar.origin.binder eq origin) + if isInferred then + val args1 = tree.args.asInstanceOf[List[tpd.Tree]] + val tvars = args1.tpes.asInstanceOf[List[TypeVar]] + for tvar <- tvars do + tvar.resetInst(ctx.typerState) + ctx.typerState.ownedVars += tvar + val binder = tvars.head.origin.binder + val added = ctx.typerState.constraint.ensureFresh(binder) + if added ne binder then + for i <- tvars.indices do + tvars(i).setOrigin(added.paramRefs(i)) + val fun1 = typedExpr(tree.fun, PolyProto(args1, pt)) + //println(i"adding $added, $tree to ${ctx.typerState.constraint}") + TypeComparer.addToConstraint(added, tvars) + assignType(tree, fun1, args1) + else + super.typedTypeApply(tree, pt) + + //override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(using Context): Tree = + // tree + + //override def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean)(using Context): Tree = + // tree + + //override def simplify(tree: Tree, pt: Type, locked: TypeVars)(using Context): tree.type = tree + end TypeRefiner + +object RefineTypes: + val name = "refineTypes" +end RefineTypes diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index e848a19e147e..6537b67b957c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -179,7 +179,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): * and mark it with given attachment so that it is made into a mirror at PostTyper. */ private def anonymousMirror(monoType: Type, attachment: Property.StickyKey[Unit], span: Span)(using Context) = - if ctx.isAfterTyper then ctx.compilationUnit.needsMirrorSupport = true + if ctx.isAfterRefiner then ctx.compilationUnit.needsMirrorSupport = true val monoTypeDef = untpd.TypeDef(tpnme.MirroredMonoType, untpd.TypeTree(monoType)) val newImpl = untpd.Template( constr = untpd.emptyConstructor, diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 536a80626380..662866551c7c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -858,7 +858,7 @@ class Typer extends Namer def typedTpt = checkSimpleKinded(typedType(tree.tpt)) def handlePattern: Tree = { val tpt1 = typedTpt - if !ctx.isAfterTyper && pt != defn.ImplicitScrutineeTypeRef then + if !ctx.isAfterRefiner && pt != defn.ImplicitScrutineeTypeRef then withMode(Mode.GadtConstraintInference) { TypeComparer.constrainPatternType(tpt1.tpe, pt) } @@ -1584,7 +1584,7 @@ class Typer extends Namer assert(sym.name != tpnme.WILDCARD) if ctx.scope.lookup(b.name) == NoSymbol then ctx.enter(sym) else report.error(new DuplicateBind(b, cdef), b.srcPos) - if (!ctx.isAfterTyper) { + if (!ctx.isAfterRefiner) { val bounds = ctx.gadt.fullBounds(sym) if (bounds != null) sym.info = bounds } From 4a3eb524af0af3438555e7eed673aec88374c5b5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 19 May 2021 11:59:40 +0200 Subject: [PATCH 02/87] Create fresh TypeVars when refining --- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../dotty/tools/dotc/typer/RefineTypes.scala | 106 ++++++++++-------- 2 files changed, 62 insertions(+), 46 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 4fb080f0d591..405f7d98c4f2 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4420,7 +4420,7 @@ object Types { private var myInst: Type = NoType private[core] def inst: Type = myInst - private[core] def setInst(tp: Type): Unit = + private[dotc] def setInst(tp: Type): Unit = myInst = tp if tp.exists && owningState != null then val owningState1 = owningState.get diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index af2069c0e098..8f3659685921 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -17,6 +17,8 @@ import Trees._ import scala.util.control.NonFatal import typer.ErrorReporting._ import util.Spans.Span +import util.SimpleIdentitySet +import util.Chars.* import Nullables._ import transform.* import scala.collection.mutable @@ -40,9 +42,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: val refineCtx = ctx .fresh .setMode(Mode.ImplicitsEnabled) - .setNewTyperState() .setTyper(refiner) - ctx.typerState.constraint = OrderingConstraint.empty refiner.typedExpr(ctx.compilationUnit.tpdTree)(using refineCtx) def newRefiner(): TypeRefiner = TypeRefiner() @@ -53,19 +53,6 @@ class RefineTypes extends Phase, IdentityDenotTransformer: // don't check value classes after typer, as the constraint about constructors doesn't hold after transform override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(using Context): Unit = () - /** Exclude all typevars that are referred to in a parameter of an enclosing closure */ - override def qualifyForInterpolation(tvars: TypeVars)(using Context): TypeVars = - var qualifying = tvars - val anonFuns = ctx.owner.ownersIterator.filter(_.isAnonymousFunction).toList - if anonFuns.nonEmpty && false then - val anonFunParamTypes = anonFuns.flatMap(_.rawParamss.flatten).map(_.info) - qualifying.foreach (tvar => - if anonFunParamTypes.exists(formal => - formal.existsPart(tvar eq, stopAtStatic = true, forceLazy = false)) - then qualifying -= tvar - ) - qualifying - /** Exclude from double definition checks any erased symbols that were * made `private` in phase `UnlinkErasedDecls`. These symbols will be removed * completely in phase `Erasure` if they are defined in a currently compiled unit. @@ -93,42 +80,71 @@ class RefineTypes extends Phase, IdentityDenotTransformer: val ownType = qualType.select(name, mbr) untpd.cpy.Select(tree)(qual1, name).withType(ownType) + private def resetTypeVars(tree: Tree)(using Context): (Tree, List[TypeVar]) = tree match + case tree: TypeApply => + val isInferred = tree.args.forall { + case arg: TypeVarBinder[?] => + arg.tpe match + case tvar: TypeVar => + tvar.isInstantiated // test makes sure we do not reset typevars again in eta expanded closures + case _ => false + case _ => false + } + if isInferred then + val args = tree.args + val args1 = constrained(tree.fun.tpe.widen.asInstanceOf[TypeLambda], tree)._2 + for i <- args.indices do + args(i).tpe.asInstanceOf[TypeVar].setInst(args1(i).tpe) + (cpy.TypeApply(tree)(tree.fun, args1), args1.tpes.asInstanceOf[List[TypeVar]]) + else + (tree, Nil) + case Block(stats, closure: Closure) => + var tvars: List[TypeVar] = Nil + val stats1 = stats.mapConserve { + case stat: DefDef if stat.symbol == closure.meth.symbol => + val (rhs1, tvars1) = resetTypeVars(stat.rhs) + tvars = tvars1 + cpy.DefDef(stat)(rhs = rhs1) + case stat => stat + } + (cpy.Block(tree)(stats1, closure), tvars) + case Block(Nil, expr) => + val (rhs1, tvars1) = resetTypeVars(expr) + (cpy.Block(tree)(Nil, rhs1), tvars1) + case _ => + (tree, Nil) + end resetTypeVars + + override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = + val tree1 = resetTypeVars(tree.asInstanceOf[TypeApply])._1.asInstanceOf[TypeApply] + super.typedTypeApply(tree1, pt) + + override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = + if sym.isAnonymousFunction then + val ddef0 = ddef.asInstanceOf[tpd.DefDef] + val (rhs2, newTvars) = resetTypeVars(ddef0.rhs) + val ddef1 = cpy.DefDef(ddef0)(rhs = rhs2) + val bindsNestedTypeVar = + newTvars.nonEmpty + && sym.rawParamss.nestedExists(param => + param.info.existsPart({ + case tvar1: TypeVar => newTvars.contains(tvar1.instanceOpt) + case _ => false + }, stopAtStatic = true, forceLazy = false)) + if bindsNestedTypeVar then + val nestedCtx = ctx.fresh.setNewTyperState() + try inContext(nestedCtx) { super.typedDefDef(ddef1, sym) } + finally nestedCtx.typerState.commit() + else + super.typedDefDef(ddef1, sym) + else super.typedDefDef(ddef, sym) + override def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree = if tree.symbol == defn.StdLibPatchesPackage then promote(tree) // don't check stdlib patches, since their symbols were highjacked by stdlib classes else super.typedPackageDef(tree) - override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = - val isInferred = tree.args.forall { - case arg: TypeVarBinder[?] => arg.typeOpt.isInstanceOf[TypeVar] - case _ => false - } - if isInferred then - var origin: Type = null - for arg <- tree.args do - assert(arg.isInstanceOf[untpd.TypeTree], arg) - arg.typeOpt match - case tvar: TypeVar => - if origin == null then origin = tvar.origin.binder - else assert(tvar.origin.binder eq origin) - if isInferred then - val args1 = tree.args.asInstanceOf[List[tpd.Tree]] - val tvars = args1.tpes.asInstanceOf[List[TypeVar]] - for tvar <- tvars do - tvar.resetInst(ctx.typerState) - ctx.typerState.ownedVars += tvar - val binder = tvars.head.origin.binder - val added = ctx.typerState.constraint.ensureFresh(binder) - if added ne binder then - for i <- tvars.indices do - tvars(i).setOrigin(added.paramRefs(i)) - val fun1 = typedExpr(tree.fun, PolyProto(args1, pt)) - //println(i"adding $added, $tree to ${ctx.typerState.constraint}") - TypeComparer.addToConstraint(added, tvars) - assignType(tree, fun1, args1) - else - super.typedTypeApply(tree, pt) //override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(using Context): Tree = // tree From 247f4e0138ff2bce349a89c31fd29de4b768e137 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 20 May 2021 10:51:46 +0200 Subject: [PATCH 03/87] Fix typedSelect for avoiding private members --- compiler/src/dotty/tools/dotc/typer/RefineTypes.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 8f3659685921..98cccffd5997 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -22,7 +22,6 @@ import util.Chars.* import Nullables._ import transform.* import scala.collection.mutable -import util.Chars.* import reporting._ import ProtoTypes._ import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions @@ -76,7 +75,9 @@ class RefineTypes extends Phase, IdentityDenotTransformer: val qual1 = withoutMode(Mode.Pattern)(typed(qual, AnySelectionProto)) val qualType = qual1.tpe.widenIfUnstable val pre = maybeSkolemizePrefix(qualType, name) - val mbr = qualType.findMember(name, pre).suchThat(tree.symbol ==) + val mbr = qualType.findMember(name, pre, + excluded = if tree.symbol.is(Private) then EmptyFlags else Private) + .suchThat(tree.symbol ==) val ownType = qualType.select(name, mbr) untpd.cpy.Select(tree)(qual1, name).withType(ownType) From febed32c10b96560be74222707a3c5265327f7e1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 20 May 2021 10:52:31 +0200 Subject: [PATCH 04/87] Remember instantiation direction of type variables Use the same direction when re-typing during refinement --- .../src/dotty/tools/dotc/core/Types.scala | 18 ++++- .../dotty/tools/dotc/typer/RefineTypes.scala | 67 ++++++++++++++++++- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 405f7d98c4f2..b88843bbe96b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4391,6 +4391,10 @@ object Types { // ------------ Type variables ---------------------------------------- + /** The direction with which a variable was or should be instantiated */ + enum InstDirection: + case FromBelow, FromAbove, Other + /** In a TypeApply tree, a TypeVar is created for each argument type to be inferred. * Every type variable is referred to by exactly one inferred type parameter of some * TypeApply tree. @@ -4407,6 +4411,8 @@ object Types { */ final class TypeVar private(initOrigin: TypeParamRef, creatorState: TyperState, nestingLevel: Int) extends CachedProxyType with ValueType { + private var instDirection: InstDirection = InstDirection.Other + private var currentOrigin = initOrigin def origin: TypeParamRef = currentOrigin @@ -4420,7 +4426,7 @@ object Types { private var myInst: Type = NoType private[core] def inst: Type = myInst - private[dotc] def setInst(tp: Type): Unit = + private[core] def setInst(tp: Type): Unit = myInst = tp if tp.exists && owningState != null then val owningState1 = owningState.get @@ -4432,6 +4438,10 @@ object Types { myInst = NoType owningState = new WeakReference(ts) + private[dotc] def link(previous: TypeVar): Unit = + previous.setInst(this) + instDirection = previous.instDirection + /** The state owning the variable. This is at first `creatorState`, but it can * be changed to an enclosing state on a commit. */ @@ -4495,7 +4505,11 @@ object Types { * is also a singleton type. */ def instantiate(fromBelow: Boolean)(using Context): Type = - instantiateWith(avoidCaptures(TypeComparer.instanceType(origin, fromBelow))) + if instDirection == InstDirection.Other then + instDirection = if fromBelow then InstDirection.FromBelow else InstDirection.FromAbove + instantiateWith( + avoidCaptures( + TypeComparer.instanceType(origin, instDirection == InstDirection.FromBelow))) /** For uninstantiated type variables: the entry in the constraint (either bounds or * provisional instance value) diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 98cccffd5997..eaf2fa4afddc 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -38,11 +38,15 @@ class RefineTypes extends Phase, IdentityDenotTransformer: def run(using Context): Unit = println(i"refine types of ${ctx.compilationUnit}") val refiner = newRefiner() + ctx.typerState.constraint = OrderingConstraint.empty + ctx.typerState.ownedVars = SimpleIdentitySet.empty val refineCtx = ctx .fresh .setMode(Mode.ImplicitsEnabled) +// .setNewTyperState() .setTyper(refiner) refiner.typedExpr(ctx.compilationUnit.tpdTree)(using refineCtx) +// refineCtx.typerState.commit() def newRefiner(): TypeRefiner = TypeRefiner() @@ -52,6 +56,23 @@ class RefineTypes extends Phase, IdentityDenotTransformer: // don't check value classes after typer, as the constraint about constructors doesn't hold after transform override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(using Context): Unit = () + /* Exclude all typevars that are referred to in a parameter of an enclosing closure + override def qualifyForInterpolation(tvars: TypeVars)(using Context): TypeVars = + var qualifying = tvars + val anonFuns = ctx.owner.ownersIterator.filter(_.isAnonymousFunction).toList + if anonFuns.nonEmpty then + val anonFunParamTypes = anonFuns.flatMap(_.rawParamss.flatten).map(_.info) + qualifying.foreach { tvar => + if anonFunParamTypes.exists(formal => + formal.existsPart({ + case tvar1: TypeVar => tvar1.instanceOpt eq tvar + case _ => false + }, stopAtStatic = true, forceLazy = false)) + then qualifying -= tvar + } + qualifying + */ + /** Exclude from double definition checks any erased symbols that were * made `private` in phase `UnlinkErasedDecls`. These symbols will be removed * completely in phase `Erasure` if they are defined in a currently compiled unit. @@ -95,7 +116,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: val args = tree.args val args1 = constrained(tree.fun.tpe.widen.asInstanceOf[TypeLambda], tree)._2 for i <- args.indices do - args(i).tpe.asInstanceOf[TypeVar].setInst(args1(i).tpe) + args1(i).tpe.asInstanceOf[TypeVar].link(args(i).tpe.asInstanceOf[TypeVar]) (cpy.TypeApply(tree)(tree.fun, args1), args1.tpes.asInstanceOf[List[TypeVar]]) else (tree, Nil) @@ -117,8 +138,16 @@ class RefineTypes extends Phase, IdentityDenotTransformer: end resetTypeVars override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = - val tree1 = resetTypeVars(tree.asInstanceOf[TypeApply])._1.asInstanceOf[TypeApply] - super.typedTypeApply(tree1, pt) + if tree.symbol == defn.ClassTagModule_apply && false then + // ClassTag types behave weirdly in many situations. They are usually taken over + // a typevariable ClassTag[T] and inferred implicitly. In that case the type variable + // is fully defined before doing the search. But when they are given explicitly + // the type variable is interpolated. This can make a difference in direction. + // Fully defined would avoid Nothing, but interpolation does not. Test case is pos/i6127.scala. + promote(tree) + else + val tree1 = resetTypeVars(tree.asInstanceOf[TypeApply])._1.asInstanceOf[TypeApply] + super.typedTypeApply(tree1, pt) override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = if sym.isAnonymousFunction then @@ -140,6 +169,38 @@ class RefineTypes extends Phase, IdentityDenotTransformer: super.typedDefDef(ddef1, sym) else super.typedDefDef(ddef, sym) +/* + val isInferred = tree.args.forall { + case arg: TypeVarBinder[?] => arg.typeOpt.isInstanceOf[TypeVar] + case _ => false + } + if isInferred then + var origin: Type = null + for arg <- tree.args do + assert(arg.isInstanceOf[untpd.TypeTree], arg) + arg.typeOpt match + case tvar: TypeVar => + if origin == null then origin = tvar.origin.binder + else assert(tvar.origin.binder eq origin) + if isInferred then + val args1 = tree.args.asInstanceOf[List[tpd.Tree]] + val tvars = args1.tpes.asInstanceOf[List[TypeVar]] + for tvar <- tvars do + tvar.resetInst(ctx.typerState) + ctx.typerState.ownedVars += tvar + val binder = tvars.head.origin.binder + val added = ctx.typerState.constraint.ensureFresh(binder) + if added ne binder then + for i <- tvars.indices do + tvars(i).setOrigin(added.paramRefs(i)) + val fun1 = typedExpr(tree.fun, PolyProto(args1, pt)) + //println(i"adding $added, $tree to ${ctx.typerState.constraint}") + TypeComparer.addToConstraint(added, tvars) + assignType(tree, fun1, args1) + else + super.typedTypeApply(tree, pt) +*/ + override def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree = if tree.symbol == defn.StdLibPatchesPackage then promote(tree) // don't check stdlib patches, since their symbols were highjacked by stdlib classes From 7cfc1cfcff25c19a11710faa33b5d2274a6590ef Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 20 May 2021 16:50:07 +0200 Subject: [PATCH 05/87] Don't use inst for class linking We are throwing away the refined trees, so we should keep the old type variables unchanged. --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 2 +- compiler/src/dotty/tools/dotc/core/Types.scala | 10 +++++++--- compiler/src/dotty/tools/dotc/typer/RefineTypes.scala | 9 ++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 5a95b422b284..c536fae0cec8 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -427,7 +427,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling } case tp1: SkolemType => tp2 match { - case tp2: SkolemType if !ctx.phase.isTyper && recur(tp1.info, tp2.info) => true + case tp2: SkolemType if ctx.isAfterTyper && recur(tp1.info, tp2.info) => true case _ => thirdTry } case tp1: TypeVar => diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b88843bbe96b..7b7ceb751cf3 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4411,8 +4411,6 @@ object Types { */ final class TypeVar private(initOrigin: TypeParamRef, creatorState: TyperState, nestingLevel: Int) extends CachedProxyType with ValueType { - private var instDirection: InstDirection = InstDirection.Other - private var currentOrigin = initOrigin def origin: TypeParamRef = currentOrigin @@ -4438,10 +4436,16 @@ object Types { myInst = NoType owningState = new WeakReference(ts) + private var instDirection: InstDirection = InstDirection.Other + private var linkedVar: TypeVar = null + private[dotc] def link(previous: TypeVar): Unit = - previous.setInst(this) + linkedVar = previous instDirection = previous.instDirection + private[dotc] def isLinked(previous: TypeVar): Boolean = + previous eq linkedVar + /** The state owning the variable. This is at first `creatorState`, but it can * be changed to an enclosing state on a commit. */ diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index eaf2fa4afddc..1e4b0c6ea2b2 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -138,12 +138,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: end resetTypeVars override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = - if tree.symbol == defn.ClassTagModule_apply && false then - // ClassTag types behave weirdly in many situations. They are usually taken over - // a typevariable ClassTag[T] and inferred implicitly. In that case the type variable - // is fully defined before doing the search. But when they are given explicitly - // the type variable is interpolated. This can make a difference in direction. - // Fully defined would avoid Nothing, but interpolation does not. Test case is pos/i6127.scala. + if tree.symbol == defn.Predef_classOf then promote(tree) else val tree1 = resetTypeVars(tree.asInstanceOf[TypeApply])._1.asInstanceOf[TypeApply] @@ -158,7 +153,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: newTvars.nonEmpty && sym.rawParamss.nestedExists(param => param.info.existsPart({ - case tvar1: TypeVar => newTvars.contains(tvar1.instanceOpt) + case tvar1: TypeVar => newTvars.exists(_.isLinked(tvar1)) case _ => false }, stopAtStatic = true, forceLazy = false)) if bindsNestedTypeVar then From 451059a50e395f0eedfbadd8b1b244cd81c656bb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 21 May 2021 17:29:50 +0200 Subject: [PATCH 06/87] Leave types as they are for outer references Outer references cannot be re-typed with the usual typedSelect algorithm. Their types have to be preserved. Test case is pos/i8892.scala. --- .../dotty/tools/dotc/typer/RefineTypes.scala | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 1e4b0c6ea2b2..c313ab7f3ef4 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -93,14 +93,16 @@ class RefineTypes extends Phase, IdentityDenotTransformer: override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = val Select(qual, name) = tree - val qual1 = withoutMode(Mode.Pattern)(typed(qual, AnySelectionProto)) - val qualType = qual1.tpe.widenIfUnstable - val pre = maybeSkolemizePrefix(qualType, name) - val mbr = qualType.findMember(name, pre, - excluded = if tree.symbol.is(Private) then EmptyFlags else Private) - .suchThat(tree.symbol ==) - val ownType = qualType.select(name, mbr) - untpd.cpy.Select(tree)(qual1, name).withType(ownType) + if name.is(OuterSelectName) then promote(tree) + else + val qual1 = withoutMode(Mode.Pattern)(typed(qual, AnySelectionProto)) + val qualType = qual1.tpe.widenIfUnstable + val pre = maybeSkolemizePrefix(qualType, name) + val mbr = qualType.findMember(name, pre, + excluded = if tree.symbol.is(Private) then EmptyFlags else Private) + .suchThat(tree.symbol ==) + val ownType = qualType.select(name, mbr) + untpd.cpy.Select(tree)(qual1, name).withType(ownType) private def resetTypeVars(tree: Tree)(using Context): (Tree, List[TypeVar]) = tree match case tree: TypeApply => From 1aca42837a5cb4d71b0f8108aa82b7ba78e1d3e1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 21 May 2021 17:40:52 +0200 Subject: [PATCH 07/87] Prefer old instance over extremal bound when refining Prefer old instance over extremal bound Nothing or Any when refining --- .../src/dotty/tools/dotc/core/Types.scala | 46 +++++++++++++++++-- tests/run/i7960.scala | 19 ++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 7b7ceb751cf3..15c1e21da214 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -35,7 +35,7 @@ import config.Feature import annotation.{tailrec, constructorOnly} import language.implicitConversions import scala.util.hashing.{ MurmurHash3 => hashing } -import config.Printers.{core, typr, matchTypes} +import config.Printers.{core, typr, refiner, matchTypes} import reporting.{trace, Message} import java.lang.ref.WeakReference @@ -4511,9 +4511,47 @@ object Types { def instantiate(fromBelow: Boolean)(using Context): Type = if instDirection == InstDirection.Other then instDirection = if fromBelow then InstDirection.FromBelow else InstDirection.FromAbove - instantiateWith( - avoidCaptures( - TypeComparer.instanceType(origin, instDirection == InstDirection.FromBelow))) + if instDirection == InstDirection.FromBelow + && linkedVar != null && linkedVar.inst.isSingleton + then + // Force new instantiation to be also a singleton. + // This is neceesary to make several macro tests pass. An example is pos-macros/i9812.scala. + (this <:< defn.SingletonType) + .showing(i"add upper singleton bound to $this, success = $result", refiner) + var inst = TypeComparer.instanceType(origin, instDirection == InstDirection.FromBelow) + if linkedVar != null then + refiner.println(i"instantiate $this to $inst, was ${linkedVar.inst}, fromBelow = ${instDirection == InstDirection.FromBelow}") + // Instead of instantiating to an extremal type Nothing or Any, pick the previous + // instantiation as long as it is compatible with the current constraints. + // This is needed because of a particular interaction of type variable instantiation + // and implicit search. Before doing an implicit search, some type variables are + // instantiated via `instantiatSelected`. During retyping, the implicit argument + // is passed explicitly, but if it has type parameters, those parameters become + // fresh type variables. Instantiating these type variables is now done in a constraint + // that is weaker than the original typing constraint, since the `instantiateSelected` + // step is missing. An example with more explanations is run/i7960.scala. + val needsOldInstance = + if instDirection == InstDirection.FromBelow then + inst.isExactlyNothing + && !linkedVar.inst.isExactlyNothing + && linkedVar.inst <:< this + else + inst.isExactlyAny + && !linkedVar.inst.isExactlyAny + && this <:< linkedVar.inst + if needsOldInstance then + inst = linkedVar.inst + .showing(i"avoid extremal instance for $this be instantiating with old $inst", refiner) + + instantiateWith(avoidCaptures(inst)) + end instantiate + + private def linkForward(using Context): TypeMap = new TypeMap: + def apply(tp: Type): Type = tp match + case tvar: TypeVar if tvar.linkedVar != null && tvar.inst.exists => + tvar.linkedVar + case _ => + mapOver(tp) /** For uninstantiated type variables: the entry in the constraint (either bounds or * provisional instance value) diff --git a/tests/run/i7960.scala b/tests/run/i7960.scala index 423b6111d8b1..759cf82627ff 100644 --- a/tests/run/i7960.scala +++ b/tests/run/i7960.scala @@ -27,5 +27,24 @@ object Test { Future { A.a }, Future { B.a }, )), 1.seconds) + /* + On the other hand, this fails: + + Await.result(Future.sequence(Seq( + Future { A.a }, + Future { B.a }, + ))( + scala.collection.BuildFrom.buildFromIterableOps/*[Seq, Future[A], A]*/, global + ) + , 1.seconds) + + the problem here is that there is not enough info to instantiate the type parameters of + scala.collection.BuildFrom.buildFromIterableOps. If the BuildFrom is searched as an implicit, + enough type variables are instantiated to correctly determine the parameters. But if the + `buildFromIterableOps` is given explicitly, the problem is underconstrained and the type + parameters are instantiated to `Nothing`. + + */ + } } From 66ea7346da82bde9da05bfe0794542e1d25cae55 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 22 May 2021 10:39:54 +0200 Subject: [PATCH 08/87] Rename TypeVarBinder --> InferredTypeTree --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 8 +++++--- compiler/src/dotty/tools/dotc/typer/Inferencing.scala | 2 +- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 2 +- compiler/src/dotty/tools/dotc/typer/RefineTypes.scala | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 25b67921fc44..df913a0f4405 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -699,10 +699,12 @@ object Trees { s"TypeTree${if (hasType) s"[$typeOpt]" else ""}" } - /** A type tree that defines a new type variable. Its type is always a TypeVar. - * Every TypeVar is created as the type of one TypeVarBinder. + /** A type tree whose type is inferred. These trees appear in two contexts + * - as an argument of a TypeApply. In that case its type is always a TypeVar + * - as a (result-)type of an inferred ValDef or DefDef. + * Every TypeVar is created as the type of one InferredTypeTree. */ - class TypeVarBinder[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends TypeTree[T] + class InferredTypeTree[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends TypeTree[T] /** ref.type */ case class SingletonTypeTree[-T >: Untyped] private[ast] (ref: Tree[T])(implicit @constructorOnly src: SourceFile) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 68fae95ac754..7e19e48426a4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -333,7 +333,7 @@ object Inferencing { @tailrec def boundVars(tree: Tree, acc: List[TypeVar]): List[TypeVar] = tree match { case Apply(fn, _) => boundVars(fn, acc) case TypeApply(fn, targs) => - val tvars = targs.filter(_.isInstanceOf[TypeVarBinder[?]]).tpes.collect { + val tvars = targs.filter(_.isInstanceOf[InferredTypeTree[?]]).tpes.collect { case tvar: TypeVar if !tvar.isInstantiated && ctx.typerState.ownedVars.contains(tvar) && diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 7e7132307b0e..8af6c00e190c 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -647,7 +647,7 @@ object ProtoTypes { def newTypeVars(tl: TypeLambda): List[TypeTree] = for (paramRef <- tl.paramRefs) yield { - val tt = TypeVarBinder().withSpan(owningTree.span) + val tt = InferredTypeTree().withSpan(owningTree.span) val tvar = TypeVar(paramRef, state) state.ownedVars += tvar tt.withType(tvar) diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index c313ab7f3ef4..ba7947793435 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -107,7 +107,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: private def resetTypeVars(tree: Tree)(using Context): (Tree, List[TypeVar]) = tree match case tree: TypeApply => val isInferred = tree.args.forall { - case arg: TypeVarBinder[?] => + case arg: InferredTypeTree[?] => arg.tpe match case tvar: TypeVar => tvar.isInstantiated // test makes sure we do not reset typevars again in eta expanded closures From 4a8b619f54739a6f7c95f3d9ce305d1a20401820 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 22 May 2021 10:45:17 +0200 Subject: [PATCH 09/87] Cleanups --- .../dotty/tools/dotc/config/Printers.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 15 ++--- .../dotty/tools/dotc/typer/RefineTypes.scala | 66 +------------------ 3 files changed, 8 insertions(+), 75 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 64a71259e3d8..8b505e792f35 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -39,7 +39,7 @@ object Printers { val quotePickling = noPrinter val plugins = noPrinter val refcheck = noPrinter - val refiner = noPrinter + val refinr = noPrinter val simplify = noPrinter val staging = noPrinter val subtyping = noPrinter diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 15c1e21da214..f1a2d48190e0 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -35,7 +35,7 @@ import config.Feature import annotation.{tailrec, constructorOnly} import language.implicitConversions import scala.util.hashing.{ MurmurHash3 => hashing } -import config.Printers.{core, typr, refiner, matchTypes} +import config.Printers.{core, typr, refinr, matchTypes} import reporting.{trace, Message} import java.lang.ref.WeakReference @@ -4517,10 +4517,10 @@ object Types { // Force new instantiation to be also a singleton. // This is neceesary to make several macro tests pass. An example is pos-macros/i9812.scala. (this <:< defn.SingletonType) - .showing(i"add upper singleton bound to $this, success = $result", refiner) + .showing(i"add upper singleton bound to $this, success = $result", refinr) var inst = TypeComparer.instanceType(origin, instDirection == InstDirection.FromBelow) if linkedVar != null then - refiner.println(i"instantiate $this to $inst, was ${linkedVar.inst}, fromBelow = ${instDirection == InstDirection.FromBelow}") + refinr.println(i"instantiate $this to $inst, was ${linkedVar.inst}, fromBelow = ${instDirection == InstDirection.FromBelow}") // Instead of instantiating to an extremal type Nothing or Any, pick the previous // instantiation as long as it is compatible with the current constraints. // This is needed because of a particular interaction of type variable instantiation @@ -4541,18 +4541,11 @@ object Types { && this <:< linkedVar.inst if needsOldInstance then inst = linkedVar.inst - .showing(i"avoid extremal instance for $this be instantiating with old $inst", refiner) + .showing(i"avoid extremal instance for $this be instantiating with old $inst", refinr) instantiateWith(avoidCaptures(inst)) end instantiate - private def linkForward(using Context): TypeMap = new TypeMap: - def apply(tp: Type): Type = tp match - case tvar: TypeVar if tvar.linkedVar != null && tvar.inst.exists => - tvar.linkedVar - case _ => - mapOver(tp) - /** For uninstantiated type variables: the entry in the constraint (either bounds or * provisional instance value) */ diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index ba7947793435..3d3e40457b07 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -10,7 +10,7 @@ import Symbols._ import StdNames._ import Decorators._ import typer.ProtoTypes._ -import config.Printers.refiner +import config.Printers.refinr import ast.{tpd, untpd, Trees} import core.NameKinds.{DocArtifactName, OuterSelectName} import Trees._ @@ -36,17 +36,15 @@ class RefineTypes extends Phase, IdentityDenotTransformer: override def isTyper: Boolean = true def run(using Context): Unit = - println(i"refine types of ${ctx.compilationUnit}") + refinr.println(i"refine types of ${ctx.compilationUnit}") val refiner = newRefiner() ctx.typerState.constraint = OrderingConstraint.empty ctx.typerState.ownedVars = SimpleIdentitySet.empty val refineCtx = ctx .fresh .setMode(Mode.ImplicitsEnabled) -// .setNewTyperState() .setTyper(refiner) refiner.typedExpr(ctx.compilationUnit.tpdTree)(using refineCtx) -// refineCtx.typerState.commit() def newRefiner(): TypeRefiner = TypeRefiner() @@ -56,23 +54,6 @@ class RefineTypes extends Phase, IdentityDenotTransformer: // don't check value classes after typer, as the constraint about constructors doesn't hold after transform override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(using Context): Unit = () - /* Exclude all typevars that are referred to in a parameter of an enclosing closure - override def qualifyForInterpolation(tvars: TypeVars)(using Context): TypeVars = - var qualifying = tvars - val anonFuns = ctx.owner.ownersIterator.filter(_.isAnonymousFunction).toList - if anonFuns.nonEmpty then - val anonFunParamTypes = anonFuns.flatMap(_.rawParamss.flatten).map(_.info) - qualifying.foreach { tvar => - if anonFunParamTypes.exists(formal => - formal.existsPart({ - case tvar1: TypeVar => tvar1.instanceOpt eq tvar - case _ => false - }, stopAtStatic = true, forceLazy = false)) - then qualifying -= tvar - } - qualifying - */ - /** Exclude from double definition checks any erased symbols that were * made `private` in phase `UnlinkErasedDecls`. These symbols will be removed * completely in phase `Erasure` if they are defined in a currently compiled unit. @@ -81,7 +62,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: sym.isEffectivelyErased && sym.is(Private) && !sym.initial.is(Private) override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = - trace(i"typed $tree, $pt", refiner, show = true) { + trace(i"typed $tree, $pt", refinr, show = true) { tree match case _: untpd.TypedSplice | _: untpd.Thicket | _: EmptyValDef[?] => super.typedUnadapted(tree, pt, locked) @@ -166,52 +147,11 @@ class RefineTypes extends Phase, IdentityDenotTransformer: super.typedDefDef(ddef1, sym) else super.typedDefDef(ddef, sym) -/* - val isInferred = tree.args.forall { - case arg: TypeVarBinder[?] => arg.typeOpt.isInstanceOf[TypeVar] - case _ => false - } - if isInferred then - var origin: Type = null - for arg <- tree.args do - assert(arg.isInstanceOf[untpd.TypeTree], arg) - arg.typeOpt match - case tvar: TypeVar => - if origin == null then origin = tvar.origin.binder - else assert(tvar.origin.binder eq origin) - if isInferred then - val args1 = tree.args.asInstanceOf[List[tpd.Tree]] - val tvars = args1.tpes.asInstanceOf[List[TypeVar]] - for tvar <- tvars do - tvar.resetInst(ctx.typerState) - ctx.typerState.ownedVars += tvar - val binder = tvars.head.origin.binder - val added = ctx.typerState.constraint.ensureFresh(binder) - if added ne binder then - for i <- tvars.indices do - tvars(i).setOrigin(added.paramRefs(i)) - val fun1 = typedExpr(tree.fun, PolyProto(args1, pt)) - //println(i"adding $added, $tree to ${ctx.typerState.constraint}") - TypeComparer.addToConstraint(added, tvars) - assignType(tree, fun1, args1) - else - super.typedTypeApply(tree, pt) -*/ - override def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree = if tree.symbol == defn.StdLibPatchesPackage then promote(tree) // don't check stdlib patches, since their symbols were highjacked by stdlib classes else super.typedPackageDef(tree) - - - //override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(using Context): Tree = - // tree - - //override def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean)(using Context): Tree = - // tree - - //override def simplify(tree: Tree, pt: Type, locked: TypeVars)(using Context): tree.type = tree end TypeRefiner object RefineTypes: From b49fcc518b83435bd658102e8f20366abed4446b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 22 May 2021 11:30:28 +0200 Subject: [PATCH 10/87] Move typedTyped from ReTyper --- .../dotty/tools/dotc/typer/RefineTypes.scala | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 3d3e40457b07..69719eaf0484 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -38,8 +38,6 @@ class RefineTypes extends Phase, IdentityDenotTransformer: def run(using Context): Unit = refinr.println(i"refine types of ${ctx.compilationUnit}") val refiner = newRefiner() - ctx.typerState.constraint = OrderingConstraint.empty - ctx.typerState.ownedVars = SimpleIdentitySet.empty val refineCtx = ctx .fresh .setMode(Mode.ImplicitsEnabled) @@ -51,16 +49,6 @@ class RefineTypes extends Phase, IdentityDenotTransformer: class TypeRefiner extends ReTyper: import ast.tpd.* - // don't check value classes after typer, as the constraint about constructors doesn't hold after transform - override def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(using Context): Unit = () - - /** Exclude from double definition checks any erased symbols that were - * made `private` in phase `UnlinkErasedDecls`. These symbols will be removed - * completely in phase `Erasure` if they are defined in a currently compiled unit. - */ - override def excludeFromDoubleDeclCheck(sym: Symbol)(using Context): Boolean = - sym.isEffectivelyErased && sym.is(Private) && !sym.initial.is(Private) - override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = trace(i"typed $tree, $pt", refinr, show = true) { tree match @@ -85,6 +73,18 @@ class RefineTypes extends Phase, IdentityDenotTransformer: val ownType = qualType.select(name, mbr) untpd.cpy.Select(tree)(qual1, name).withType(ownType) + override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = + val tpt1 = checkSimpleKinded(typedType(tree.tpt)) + val expr1 = tree.expr match + case id: untpd.Ident if (ctx.mode is Mode.Pattern) && untpd.isVarPattern(id) && (id.name == nme.WILDCARD || id.name == nme.WILDCARD_STAR) => + tree.expr.withType(tpt1.tpe) + case _ => + var pt1 = tpt1.tpe + if pt1.isRepeatedParam then + pt1 = pt1.translateFromRepeated(toArray = tree.expr.typeOpt.derivesFrom(defn.ArrayClass)) + typed(tree.expr, pt1) + untpd.cpy.Typed(tree)(expr1, tpt1).withType(tree.typeOpt) + private def resetTypeVars(tree: Tree)(using Context): (Tree, List[TypeVar]) = tree match case tree: TypeApply => val isInferred = tree.args.forall { From 7feecda15118f570b9f2424f22bfbff7acb53cd3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 22 May 2021 17:46:35 +0200 Subject: [PATCH 11/87] Add some logic needed for Namer --- .../dotty/tools/dotc/typer/RefineTypes.scala | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 69719eaf0484..cca1fd0ae376 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -49,15 +49,20 @@ class RefineTypes extends Phase, IdentityDenotTransformer: class TypeRefiner extends ReTyper: import ast.tpd.* + override def newLikeThis: Typer = new TypeRefiner + override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = trace(i"typed $tree, $pt", refinr, show = true) { - tree match - case _: untpd.TypedSplice | _: untpd.Thicket | _: EmptyValDef[?] => - super.typedUnadapted(tree, pt, locked) - case _ if tree.isType => - promote(tree) - case _ => - super.typedUnadapted(tree, pt, locked) + tree.removeAttachment(TypedAhead) match + case Some(ttree) => ttree + case none => + tree match + case _: untpd.TypedSplice | _: untpd.Thicket | _: EmptyValDef[?] => + super.typedUnadapted(tree, pt, locked) + case _ if tree.isType => + promote(tree) + case _ => + super.typedUnadapted(tree, pt, locked) } override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = From 3a7275b6cdf03f581fd8571ebc55ee5c864e73f5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 25 May 2021 11:06:57 +0200 Subject: [PATCH 12/87] Pull out inferredResultType into separate method - that way we avoid an overly long valOrDefDefSig - the method can be reused in RefineTypes --- .../src/dotty/tools/dotc/typer/Namer.scala | 343 +++++++++--------- 1 file changed, 177 insertions(+), 166 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 0f6f7e46a39a..57f7cf0f7548 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -108,6 +108,10 @@ class Namer { typer: Typer => } } + def wrapMethodType(restpe: Type, paramSymss: List[List[Symbol]], isJava: Boolean)(using Context): Type = + instantiateDependent(restpe, paramSymss) + methodType(paramSymss, restpe, isJava) + /** The enclosing class with given name; error if none exists */ def enclosingClassNamed(name: TypeName, span: Span)(using Context): Symbol = if (name.isEmpty) NoSymbol @@ -1431,168 +1435,7 @@ class Namer { typer: Typer => */ def valOrDefDefSig(mdef: ValOrDefDef, sym: Symbol, paramss: List[List[Symbol]], paramFn: Type => Type)(using Context): Type = { - def inferredType = { - /** A type for this definition that might be inherited from elsewhere: - * If this is a setter parameter, the corresponding getter type. - * If this is a class member, the conjunction of all result types - * of overridden methods. - * NoType if neither case holds. - */ - val inherited = - if (sym.owner.isTerm) NoType - else - // TODO: Look only at member of supertype instead? - lazy val schema = paramFn(WildcardType) - val site = sym.owner.thisType - val bcs = sym.owner.info.baseClasses - if bcs.isEmpty then - assert(ctx.reporter.errorsReported) - NoType - else bcs.tail.foldLeft(NoType: Type) { (tp, cls) => - def instantiatedResType(info: Type, paramss: List[List[Symbol]]): Type = info match - case info: PolyType => - paramss match - case TypeSymbols(tparams) :: paramss1 if info.paramNames.length == tparams.length => - instantiatedResType(info.instantiate(tparams.map(_.typeRef)), paramss1) - case _ => - NoType - case info: MethodType => - paramss match - case TermSymbols(vparams) :: paramss1 if info.paramNames.length == vparams.length => - instantiatedResType(info.instantiate(vparams.map(_.termRef)), paramss1) - case _ => - NoType - case _ => - if paramss.isEmpty then info.widenExpr - else NoType - - val iRawInfo = - cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema, sym.targetName).info - val iResType = instantiatedResType(iRawInfo, paramss).asSeenFrom(site, cls) - if (iResType.exists) - typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inherited: $iResType") - tp & iResType - } - end inherited - - /** If this is a default getter, the type of the corresponding method parameter, - * otherwise NoType. - */ - def defaultParamType = sym.name match - case DefaultGetterName(original, idx) => - val meth: Denotation = - if (original.isConstructorName && (sym.owner.is(ModuleClass))) - sym.owner.companionClass.info.decl(nme.CONSTRUCTOR) - else - ctx.defContext(sym).denotNamed(original) - def paramProto(paramss: List[List[Type]], idx: Int): Type = paramss match { - case params :: paramss1 => - if (idx < params.length) params(idx) - else paramProto(paramss1, idx - params.length) - case nil => - NoType - } - val defaultAlts = meth.altsWith(_.hasDefaultParams) - if (defaultAlts.length == 1) - paramProto(defaultAlts.head.info.widen.paramInfoss, idx) - else - NoType - case _ => - NoType - - /** The expected type for a default argument. This is normally the `defaultParamType` - * with references to internal parameters replaced by wildcards. This replacement - * makes it possible that the default argument can have a more specific type than the - * parameter. For instance, we allow - * - * class C[A](a: A) { def copy[B](x: B = a): C[B] = C(x) } - * - * However, if the default parameter type is a context function type, we - * have to make sure that wildcard types do not leak into the implicitly - * generated closure's result type. Test case is pos/i12019.scala. If there - * would be a leakage with the wildcard approximation, we pick the original - * default parameter type as expected type. - */ - def expectedDefaultArgType = - val originalTp = defaultParamType - val approxTp = wildApprox(originalTp) - approxTp.stripPoly match - case atp @ defn.ContextFunctionType(_, resType, _) - if !defn.isNonRefinedFunction(atp) // in this case `resType` is lying, gives us only the non-dependent upper bound - || resType.existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true, forceLazy = false) => - originalTp - case _ => - approxTp - - // println(s"final inherited for $sym: ${inherited.toString}") !!! - // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") - // TODO Scala 3.1: only check for inline vals (no final ones) - def isInlineVal = sym.isOneOf(FinalOrInline, butNot = Method | Mutable) - - var rhsCtx = ctx.fresh.addMode(Mode.InferringReturnType) - if sym.isInlineMethod then rhsCtx = rhsCtx.addMode(Mode.InlineableBody) - if sym.is(ExtensionMethod) then rhsCtx = rhsCtx.addMode(Mode.InExtensionMethod) - val typeParams = paramss.collect { case TypeSymbols(tparams) => tparams }.flatten - if (typeParams.nonEmpty) { - // we'll be typing an expression from a polymorphic definition's body, - // so we must allow constraining its type parameters - // compare with typedDefDef, see tests/pos/gadt-inference.scala - rhsCtx.setFreshGADTBounds - rhsCtx.gadt.addToConstraint(typeParams) - } - - def typedAheadRhs(pt: Type) = - PrepareInlineable.dropInlineIfError(sym, - typedAheadExpr(mdef.rhs, pt)(using rhsCtx)) - - def rhsType = - // For default getters, we use the corresponding parameter type as an - // expected type but we run it through `wildApprox` to allow default - // parameters like in `def mkList[T](value: T = 1): List[T]`. - val defaultTp = defaultParamType - val pt = inherited.orElse(expectedDefaultArgType).orElse(WildcardType).widenExpr - val tp = typedAheadRhs(pt).tpe - if (defaultTp eq pt) && (tp frozen_<:< defaultTp) then - // When possible, widen to the default getter parameter type to permit a - // larger choice of overrides (see `default-getter.scala`). - // For justification on the use of `@uncheckedVariance`, see - // `default-getter-variance.scala`. - AnnotatedType(defaultTp, Annotation(defn.UncheckedVarianceAnnot)) - else - // don't strip @uncheckedVariance annot for default getters - TypeOps.simplify(tp.widenTermRefExpr, - if defaultTp.exists then TypeOps.SimplifyKeepUnchecked() else null) match - case ctp: ConstantType if isInlineVal => ctp - case tp => TypeComparer.widenInferred(tp, pt) - - // Replace aliases to Unit by Unit itself. If we leave the alias in - // it would be erased to BoxedUnit. - def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp - - // Approximate a type `tp` with a type that does not contain skolem types. - val deskolemize = new ApproximatingTypeMap { - def apply(tp: Type) = /*trace(i"deskolemize($tp) at $variance", show = true)*/ - tp match { - case tp: SkolemType => range(defn.NothingType, atVariance(1)(apply(tp.info))) - case _ => mapOver(tp) - } - } - - def cookedRhsType = deskolemize(dealiasIfUnit(rhsType)) - def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.span) - //if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType") - if (inherited.exists) - if (isInlineVal) lhsType else inherited - else { - if (sym.is(Implicit)) - mdef match { - case _: DefDef => missingType(sym, "result ") - case _: ValDef if sym.owner.isType => missingType(sym, "") - case _ => - } - lhsType orElse WildcardType - } - } + def inferredType = inferredResultType(mdef, sym, paramss, paramFn, WildcardType) lazy val termParamss = paramss.collect { case TermSymbols(vparams) => vparams } val tptProto = mdef.tpt match { @@ -1673,10 +1516,8 @@ class Namer { typer: Typer => ddef.trailingParamss.foreach(completeParams) val paramSymss = normalizeIfConstructor(ddef.paramss.nestedMap(symbolOfTree), isConstructor) sym.setParamss(paramSymss) - def wrapMethType(restpe: Type): Type = { - instantiateDependent(restpe, paramSymss) - methodType(paramSymss, restpe, isJava = ddef.mods.is(JavaDefined)) - } + def wrapMethType(restpe: Type): Type = + wrapMethodType(restpe, paramSymss, ddef.mods.is(JavaDefined)) if (isConstructor) { // set result type tree to unit, but take the current class as result type of the symbol typedAheadType(ddef.tpt, defn.UnitType) @@ -1684,4 +1525,174 @@ class Namer { typer: Typer => } else valOrDefDefSig(ddef, sym, paramSymss, wrapMethType) } + + def inferredResultType( + 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. + * If this is a class member, the conjunction of all result types + * of overridden methods. + * NoType if neither case holds. + */ + val inherited = + if (sym.owner.isTerm) NoType + else + // TODO: Look only at member of supertype instead? + lazy val schema = paramFn(WildcardType) + val site = sym.owner.thisType + val bcs = sym.owner.info.baseClasses + if bcs.isEmpty then + assert(ctx.reporter.errorsReported) + NoType + else bcs.tail.foldLeft(NoType: Type) { (tp, cls) => + def instantiatedResType(info: Type, paramss: List[List[Symbol]]): Type = info match + case info: PolyType => + paramss match + case TypeSymbols(tparams) :: paramss1 if info.paramNames.length == tparams.length => + instantiatedResType(info.instantiate(tparams.map(_.typeRef)), paramss1) + case _ => + NoType + case info: MethodType => + paramss match + case TermSymbols(vparams) :: paramss1 if info.paramNames.length == vparams.length => + instantiatedResType(info.instantiate(vparams.map(_.termRef)), paramss1) + case _ => + NoType + case _ => + if paramss.isEmpty then info.widenExpr + else NoType + + val iRawInfo = + cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema, sym.targetName).info + val iResType = instantiatedResType(iRawInfo, paramss).asSeenFrom(site, cls) + if (iResType.exists) + typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inherited: $iResType") + tp & iResType + } + end inherited + + /** If this is a default getter, the type of the corresponding method parameter, + * otherwise NoType. + */ + def defaultParamType = sym.name match + case DefaultGetterName(original, idx) => + val meth: Denotation = + if (original.isConstructorName && (sym.owner.is(ModuleClass))) + sym.owner.companionClass.info.decl(nme.CONSTRUCTOR) + else + ctx.defContext(sym).denotNamed(original) + def paramProto(paramss: List[List[Type]], idx: Int): Type = paramss match { + case params :: paramss1 => + if (idx < params.length) params(idx) + else paramProto(paramss1, idx - params.length) + case nil => + NoType + } + val defaultAlts = meth.altsWith(_.hasDefaultParams) + if (defaultAlts.length == 1) + paramProto(defaultAlts.head.info.widen.paramInfoss, idx) + else + NoType + case _ => + NoType + + /** The expected type for a default argument. This is normally the `defaultParamType` + * with references to internal parameters replaced by wildcards. This replacement + * makes it possible that the default argument can have a more specific type than the + * parameter. For instance, we allow + * + * class C[A](a: A) { def copy[B](x: B = a): C[B] = C(x) } + * + * However, if the default parameter type is a context function type, we + * have to make sure that wildcard types do not leak into the implicitly + * generated closure's result type. Test case is pos/i12019.scala. If there + * would be a leakage with the wildcard approximation, we pick the original + * default parameter type as expected type. + */ + def expectedDefaultArgType = + val originalTp = defaultParamType + val approxTp = wildApprox(originalTp) + approxTp.stripPoly match + case atp @ defn.ContextFunctionType(_, resType, _) + if !defn.isNonRefinedFunction(atp) // in this case `resType` is lying, gives us only the non-dependent upper bound + || resType.existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true, forceLazy = false) => + originalTp + case _ => + approxTp + + // println(s"final inherited for $sym: ${inherited.toString}") !!! + // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") + // TODO Scala 3.1: only check for inline vals (no final ones) + def isInlineVal = sym.isOneOf(FinalOrInline, butNot = Method | Mutable) + + var rhsCtx = ctx.fresh.addMode(Mode.InferringReturnType) + if sym.isInlineMethod then rhsCtx = rhsCtx.addMode(Mode.InlineableBody) + if sym.is(ExtensionMethod) then rhsCtx = rhsCtx.addMode(Mode.InExtensionMethod) + val typeParams = paramss.collect { case TypeSymbols(tparams) => tparams }.flatten + if (typeParams.nonEmpty) { + // we'll be typing an expression from a polymorphic definition's body, + // so we must allow constraining its type parameters + // compare with typedDefDef, see tests/pos/gadt-inference.scala + rhsCtx.setFreshGADTBounds + rhsCtx.gadt.addToConstraint(typeParams) + } + + def typedAheadRhs(pt: Type) = + PrepareInlineable.dropInlineIfError(sym, + typedAheadExpr(mdef.rhs, pt)(using rhsCtx)) + + def rhsType = + // For default getters, we use the corresponding parameter type as an + // expected type but we run it through `wildApprox` to allow default + // parameters like in `def mkList[T](value: T = 1): List[T]`. + val defaultTp = defaultParamType + val pt = inherited.orElse(expectedDefaultArgType).orElse(fallbackProto).widenExpr + val tp = typedAheadRhs(pt).tpe + if (defaultTp eq pt) && (tp frozen_<:< defaultTp) then + // When possible, widen to the default getter parameter type to permit a + // larger choice of overrides (see `default-getter.scala`). + // For justification on the use of `@uncheckedVariance`, see + // `default-getter-variance.scala`. + AnnotatedType(defaultTp, Annotation(defn.UncheckedVarianceAnnot)) + else + // don't strip @uncheckedVariance annot for default getters + TypeOps.simplify(tp.widenTermRefExpr, + if defaultTp.exists then TypeOps.SimplifyKeepUnchecked() else null) match + case ctp: ConstantType if isInlineVal => ctp + case tp => TypeComparer.widenInferred(tp, pt) + + // Replace aliases to Unit by Unit itself. If we leave the alias in + // it would be erased to BoxedUnit. + def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp + + // Approximate a type `tp` with a type that does not contain skolem types. + val deskolemize = new ApproximatingTypeMap { + def apply(tp: Type) = /*trace(i"deskolemize($tp) at $variance", show = true)*/ + tp match { + case tp: SkolemType => range(defn.NothingType, atVariance(1)(apply(tp.info))) + case _ => mapOver(tp) + } + } + + def cookedRhsType = deskolemize(dealiasIfUnit(rhsType)) + def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.span) + //if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType") + if (inherited.exists) + if (isInlineVal) lhsType else inherited + else { + if (sym.is(Implicit)) + mdef match { + case _: DefDef => missingType(sym, "result ") + case _: ValDef if sym.owner.isType => missingType(sym, "") + case _ => + } + lhsType orElse WildcardType + } + end inferredResultType } From 6158498407d9e3c24594a4d628421dd6fbdf4a89 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 25 May 2021 11:11:26 +0200 Subject: [PATCH 13/87] Re-infer inferred types of ValDefs and DefDefs --- compiler/src/dotty/tools/dotc/Compiler.scala | 3 +- .../src/dotty/tools/dotc/core/Phases.scala | 3 +- .../tools/dotc/printing/RefinedPrinter.scala | 1 + .../dotty/tools/dotc/typer/PreRefine.scala | 19 ++++++ .../dotty/tools/dotc/typer/RefineTypes.scala | 65 +++++++++++++++++-- .../src/dotty/tools/dotc/typer/Typer.scala | 17 +++-- 6 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/typer/PreRefine.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index b7094f0e9e6f..438cd3418330 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -3,7 +3,7 @@ package dotc import core._ import Contexts._ -import typer.{FrontEnd, RefChecks, RefineTypes} +import typer.{FrontEnd, RefChecks, PreRefine, RefineTypes} import Phases.Phase import transform._ import dotty.tools.backend.jvm.{CollectSuperCalls, GenBCode} @@ -37,6 +37,7 @@ class Compiler { /** Phases dealing with the frontend up to trees ready for TASTY pickling */ protected def frontendPhases: List[List[Phase]] = List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer + List(new PreRefine) :: List(new RefineTypes) :: List(new YCheckPositions) :: // YCheck positions List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 524991347050..37adb7c46ac8 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -106,7 +106,8 @@ object Phases { phase } fusedPhases += phaseToAdd - val shouldAddYCheck = YCheckAfter.containsPhase(phaseToAdd) || YCheckAll + val shouldAddYCheck = + phaseToAdd.isCheckable && (YCheckAfter.containsPhase(phaseToAdd) || YCheckAll) if (shouldAddYCheck) { val checker = new TreeChecker fusedPhases += checker diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index d51a28a2c51f..e92ca51dc9ec 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -500,6 +500,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "" case TypeTree() => typeText(toText(tree.typeOpt)) + ~ Str("(inf)").provided(tree.isInstanceOf[InferredTypeTree[_]] && printDebug) case SingletonTypeTree(ref) => toTextLocal(ref) ~ "." ~ keywordStr("type") case RefinedTypeTree(tpt, refines) => diff --git a/compiler/src/dotty/tools/dotc/typer/PreRefine.scala b/compiler/src/dotty/tools/dotc/typer/PreRefine.scala new file mode 100644 index 000000000000..231bb4db2f43 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/PreRefine.scala @@ -0,0 +1,19 @@ +package dotty.tools.dotc +package typer + +import core.Phases.Phase +import core.DenotTransformers.IdentityDenotTransformer +import core.Contexts.Context + +/** A phase that precedes the refiner and that allows installing + * completers for local symbols + */ +class PreRefine extends Phase, IdentityDenotTransformer: + + def phaseName: String = "preRefine" + + def run(using Context): Unit = + assert(next.isInstanceOf[RefineTypes], + s"misconfigured phases: phase PreRefine must be followed by phase RefineTypes") + + override def isCheckable = false diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index cca1fd0ae376..64fc965d74b1 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -9,10 +9,11 @@ import Types._ import Symbols._ import StdNames._ import Decorators._ -import typer.ProtoTypes._ +import ProtoTypes._ +import Inferencing.isFullyDefined import config.Printers.refinr import ast.{tpd, untpd, Trees} -import core.NameKinds.{DocArtifactName, OuterSelectName} +import NameKinds.{DocArtifactName, OuterSelectName, DefaultGetterName} import Trees._ import scala.util.control.NonFatal import typer.ErrorReporting._ @@ -26,7 +27,6 @@ import reporting._ import ProtoTypes._ import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions - class RefineTypes extends Phase, IdentityDenotTransformer: import RefineTypes.* import ast.tpd.* @@ -38,11 +38,17 @@ class RefineTypes extends Phase, IdentityDenotTransformer: def run(using Context): Unit = refinr.println(i"refine types of ${ctx.compilationUnit}") val refiner = newRefiner() + val unit = ctx.compilationUnit val refineCtx = ctx .fresh .setMode(Mode.ImplicitsEnabled) .setTyper(refiner) - refiner.typedExpr(ctx.compilationUnit.tpdTree)(using refineCtx) + val refinedTree = refiner.typedExpr(unit.tpdTree)(using refineCtx) + if ctx.settings.Xprint.value.containsPhase(this) then + report.echo(i"discarded result of $unit after refineTypes:\n\n$refinedTree") + + def preRefinePhase = this.prev.asInstanceOf[PreRefine] + def thisPhase = this def newRefiner(): TypeRefiner = TypeRefiner() @@ -51,6 +57,53 @@ class RefineTypes extends Phase, IdentityDenotTransformer: override def newLikeThis: Typer = new TypeRefiner + class RefineCompleter(val original: ValOrDefDef)(using Context) extends LazyType: + def completeInCreationContext(symd: SymDenotation): Unit = + val (paramss, paramFn) = original match + case ddef: DefDef => + val paramss = ddef.paramss.nestedMap(_.symbol) + (paramss, wrapMethodType(_: Type, paramss, isJava = false)) + case _: ValDef => + (Nil, (x: Type) => x) + val rhsType = inferredResultType(original, symd.symbol, paramss, paramFn, WildcardType) + typedAheadType(original.tpt, rhsType) + symd.info = paramFn(rhsType) + + def complete(symd: SymDenotation)(using Context): Unit = completeInCreationContext(symd) + end RefineCompleter + + override def typedAhead(tree: untpd.Tree, typed: untpd.Tree => Tree)(using Context): Tree = + tree.getAttachment(TypedAhead) match + case Some(ttree) => ttree + case none => + val ttree = typed(tree) + tree.putAttachment(TypedAhead, ttree) + ttree + + def resultNeedsRetyping(tree: untpd.ValOrDefDef)(using Context): Boolean = + val sym = tree.symbol + tree.tpt.isInstanceOf[InferredTypeTree[?]] + && (sym.is(Private) || sym.owner.isTerm) + && !sym.isOneOf(Param | JavaDefined) + && !sym.isOneOf(FinalOrInline, butNot = Method | Mutable) + && !sym.name.is(DefaultGetterName) + + override def index(trees: List[untpd.Tree])(using Context): Context = + for case tree: ValOrDefDef <- trees.asInstanceOf[List[Tree]] do + val sym = tree.symbol + if tree.tpt.isInstanceOf[InferredTypeTree[?]] + && (sym.is(Private) || sym.owner.isTerm) + && !sym.isOneOf(Param | JavaDefined) + && !sym.isOneOf(FinalOrInline, butNot = Method | Mutable) + && !sym.name.is(DefaultGetterName) + then + tree.symbol.copySymDenotation().installAfter(thisPhase) // reset + tree.symbol.copySymDenotation( + info = RefineCompleter(tree), + initFlags = tree.symbol.flags &~ Touched + ).installAfter(preRefinePhase) + ctx + override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = trace(i"typed $tree, $pt", refinr, show = true) { tree.removeAttachment(TypedAhead) match @@ -152,6 +205,10 @@ class RefineTypes extends Phase, IdentityDenotTransformer: super.typedDefDef(ddef1, sym) else super.typedDefDef(ddef, sym) + override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = + sym.ensureCompleted() + super.typedValDef(vdef, sym) + override def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree = if tree.symbol == defn.StdLibPatchesPackage then promote(tree) // don't check stdlib patches, since their symbols were highjacked by stdlib classes diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 662866551c7c..c48a2bc47cb3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1800,8 +1800,15 @@ class Typer extends Namer bindings1, expansion1) } + def completeTypeTree(tree: untpd.TypeTree, pt: Type, original: untpd.Tree)(using Context): TypeTree = + tree.withSpan(original.span).withAttachmentsFrom(original) + .withType( + if isFullyDefined(pt, ForceDegree.flipBottom) then pt + else if ctx.reporter.errorsReported then UnspecifiedErrorType + else errorType(i"cannot infer type; expected type $pt is not fully defined", tree.srcPos)) + def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): Tree = - tree match { + tree match case tree: untpd.DerivedTypeTree => tree.ensureCompletions tree.getAttachment(untpd.OriginalSymbol) match { @@ -1815,11 +1822,7 @@ class Typer extends Namer errorTree(tree, "Something's wrong: missing original symbol for type tree") } case _ => - tree.withType( - if (isFullyDefined(pt, ForceDegree.flipBottom)) pt - else if (ctx.reporter.errorsReported) UnspecifiedErrorType - else errorType(i"cannot infer type; expected type $pt is not fully defined", tree.srcPos)) - } + completeTypeTree(InferredTypeTree(), pt, tree) def typedSingletonTypeTree(tree: untpd.SingletonTypeTree)(using Context): SingletonTypeTree = { val ref1 = typedExpr(tree.ref) @@ -2734,7 +2737,7 @@ class Typer extends Namer case tree: untpd.TypedSplice => typedTypedSplice(tree) case tree: untpd.UnApply => typedUnApply(tree, pt) case tree: untpd.Tuple => typedTuple(tree, pt) - case tree: untpd.DependentTypeTree => typed(untpd.TypeTree().withSpan(tree.span), pt) + case tree: untpd.DependentTypeTree => completeTypeTree(untpd.TypeTree(), pt, tree) case tree: untpd.InfixOp => typedInfixOp(tree, pt) case tree: untpd.ParsedTry => typedTry(tree, pt) case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt) From 800e8314dc8b4be19dce72ac44b2fc1c0d38b768 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 25 May 2021 12:28:36 +0200 Subject: [PATCH 14/87] Improve expected types for dependent functions --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c48a2bc47cb3..0438d197aa56 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1144,12 +1144,17 @@ class Typer extends Namer // if expected result type is a wildcard, approximate from above. // this can type the greatest set of admissible closures. (pt1.argTypesLo.init, typeTree(interpolateWildcards(pt1.argTypesHi.last))) - case SAMType(sam @ MethodTpe(_, formals, restpe)) => + case RefinedType(parent, nme.apply, mt @ MethodTpe(_, formals, restpe)) + if defn.isNonRefinedFunction(parent) && formals.length == defaultArity => + (formals, untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef)))) + case SAMType(mt @ MethodTpe(_, formals, restpe)) => (formals, - if sam.isResultDependent then - untpd.DependentTypeTree(syms => restpe.substParams(sam, syms.map(_.termRef))) - else - typeTree(restpe)) + if (mt.isResultDependent) + untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef))) + else + typeTree(restpe)) + case tp: TypeParamRef => + decomposeProtoFunction(ctx.typerState.constraint.entry(tp).bounds.hi, defaultArity, tree) case _ => (List.tabulate(defaultArity)(alwaysWildcardType), untpd.TypeTree()) } From 52be81e515d89387f8843eda62768c7718bd0ec6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 25 May 2021 16:22:02 +0200 Subject: [PATCH 15/87] Fix owners for completion contexts --- .../dotty/tools/dotc/typer/RefineTypes.scala | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 64fc965d74b1..1663817ecbd2 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -65,9 +65,11 @@ class RefineTypes extends Phase, IdentityDenotTransformer: (paramss, wrapMethodType(_: Type, paramss, isJava = false)) case _: ValDef => (Nil, (x: Type) => x) - val rhsType = inferredResultType(original, symd.symbol, paramss, paramFn, WildcardType) - typedAheadType(original.tpt, rhsType) - symd.info = paramFn(rhsType) + inContext(ctx.fresh.setOwner(symd.symbol).setTree(original)) { + val rhsType = inferredResultType(original, symd.symbol, paramss, paramFn, WildcardType) + typedAheadType(original.tpt, rhsType) + symd.info = paramFn(rhsType) + } def complete(symd: SymDenotation)(using Context): Unit = completeInCreationContext(symd) end RefineCompleter @@ -110,7 +112,10 @@ class RefineTypes extends Phase, IdentityDenotTransformer: case Some(ttree) => ttree case none => tree match - case _: untpd.TypedSplice | _: untpd.Thicket | _: EmptyValDef[?] => + case _: untpd.TypedSplice + | _: untpd.Thicket + | _: EmptyValDef[?] + | _: untpd.TypeTree => super.typedUnadapted(tree, pt, locked) case _ if tree.isType => promote(tree) @@ -131,6 +136,12 @@ class RefineTypes extends Phase, IdentityDenotTransformer: val ownType = qualType.select(name, mbr) untpd.cpy.Select(tree)(qual1, name).withType(ownType) + override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): TypeTree = + if tree.isInstanceOf[InferredTypeTree[_]] && isFullyDefined(pt, ForceDegree.flipBottom) then + tree.withType(pt) + else + promote(tree) + override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = val tpt1 = checkSimpleKinded(typedType(tree.tpt)) val expr1 = tree.expr match @@ -186,6 +197,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: super.typedTypeApply(tree1, pt) override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = + sym.ensureCompleted() if sym.isAnonymousFunction then val ddef0 = ddef.asInstanceOf[tpd.DefDef] val (rhs2, newTvars) = resetTypeVars(ddef0.rhs) From f0f1fb0755abfab49b55e2e04ee6a95e4d8fc299 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 25 May 2021 18:44:24 +0200 Subject: [PATCH 16/87] Make refiners conditional on -Yrefine-types Add refiner tests that exercise the setting --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 1 + compiler/src/dotty/tools/dotc/typer/PreRefine.scala | 4 +++- compiler/src/dotty/tools/dotc/typer/RefineTypes.scala | 2 ++ compiler/test/dotc/pos-test-refiner.exludes | 4 ++++ compiler/test/dotc/run-test-refiner.exludes | 2 ++ compiler/test/dotty/tools/TestSources.scala | 4 ++++ compiler/test/dotty/tools/dotc/CompilationTests.scala | 9 +++++++++ compiler/test/dotty/tools/vulpix/TestConfiguration.scala | 1 + 8 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 compiler/test/dotc/pos-test-refiner.exludes create mode 100644 compiler/test/dotc/run-test-refiner.exludes diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 047d46b6ca31..7ed417fc9cf9 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -201,6 +201,7 @@ trait AllScalaSettings extends CommonScalaSettings { self: Settings.SettingGroup val YexplicitNulls: Setting[Boolean] = BooleanSetting("-Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.") val YcheckInit: Setting[Boolean] = BooleanSetting("-Ysafe-init", "Ensure safe initialization of objects") val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation") + val YrefineTypes: Setting[Boolean] = BooleanSetting("-Yrefine-types", "Run experimental type refiner (test only)") /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/compiler/src/dotty/tools/dotc/typer/PreRefine.scala b/compiler/src/dotty/tools/dotc/typer/PreRefine.scala index 231bb4db2f43..0e605a7d73e8 100644 --- a/compiler/src/dotty/tools/dotc/typer/PreRefine.scala +++ b/compiler/src/dotty/tools/dotc/typer/PreRefine.scala @@ -3,7 +3,7 @@ package typer import core.Phases.Phase import core.DenotTransformers.IdentityDenotTransformer -import core.Contexts.Context +import core.Contexts.{Context, ctx} /** A phase that precedes the refiner and that allows installing * completers for local symbols @@ -12,6 +12,8 @@ class PreRefine extends Phase, IdentityDenotTransformer: def phaseName: String = "preRefine" + override def isEnabled(using Context) = ctx.settings.YrefineTypes.value + def run(using Context): Unit = assert(next.isInstanceOf[RefineTypes], s"misconfigured phases: phase PreRefine must be followed by phase RefineTypes") diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 1663817ecbd2..5617d5c6bf6f 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -33,6 +33,8 @@ class RefineTypes extends Phase, IdentityDenotTransformer: def phaseName: String = RefineTypes.name + override def isEnabled(using Context) = ctx.settings.YrefineTypes.value + override def isTyper: Boolean = true def run(using Context): Unit = diff --git a/compiler/test/dotc/pos-test-refiner.exludes b/compiler/test/dotc/pos-test-refiner.exludes new file mode 100644 index 000000000000..1afdea2140b2 --- /dev/null +++ b/compiler/test/dotc/pos-test-refiner.exludes @@ -0,0 +1,4 @@ +i7056.scala + + + diff --git a/compiler/test/dotc/run-test-refiner.exludes b/compiler/test/dotc/run-test-refiner.exludes new file mode 100644 index 000000000000..b429a8ee4862 --- /dev/null +++ b/compiler/test/dotc/run-test-refiner.exludes @@ -0,0 +1,2 @@ +enrich-gentraversable.scala +typeclass-derivation3.scala diff --git a/compiler/test/dotty/tools/TestSources.scala b/compiler/test/dotty/tools/TestSources.scala index 4fbf0e9fc5dd..d66e38fc6e48 100644 --- a/compiler/test/dotty/tools/TestSources.scala +++ b/compiler/test/dotty/tools/TestSources.scala @@ -11,17 +11,21 @@ object TestSources { def posFromTastyBlacklistFile: String = "compiler/test/dotc/pos-from-tasty.blacklist" def posTestPicklingBlacklistFile: String = "compiler/test/dotc/pos-test-pickling.blacklist" + def posTestRefinerExcludesFile = "compiler/test/dotc/pos-test-refiner.exludes" def posFromTastyBlacklisted: List[String] = loadList(posFromTastyBlacklistFile) def posTestPicklingBlacklisted: List[String] = loadList(posTestPicklingBlacklistFile) + def posTestRefinerExcluded = loadList(posTestRefinerExcludesFile) // run tests lists def runFromTastyBlacklistFile: String = "compiler/test/dotc/run-from-tasty.blacklist" def runTestPicklingBlacklistFile: String = "compiler/test/dotc/run-test-pickling.blacklist" + def runTestRefinerExcludesFile = "compiler/test/dotc/run-test-refiner.exludes" def runFromTastyBlacklisted: List[String] = loadList(runFromTastyBlacklistFile) def runTestPicklingBlacklisted: List[String] = loadList(runTestPicklingBlacklistFile) + def runTestRefinerExcluded = loadList(runTestRefinerExcludesFile) // load lists diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index e264e0a19159..6ff2401faa8d 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -217,6 +217,15 @@ class CompilationTests { ).checkCompile() } + @Test def refiner: Unit = + given TestGroup = TestGroup("testRefiner") + aggregateTests( + compileFilesInDir("tests/new", refinerOptions), + compileFilesInDir("tests/pos", refinerOptions, FileFilter.exclude(TestSources.posTestRefinerExcluded)), + compileFilesInDir("tests/run", refinerOptions, FileFilter.exclude(TestSources.runTestRefinerExcluded)) + ).checkCompile() + + /** The purpose of this test is three-fold, being able to compile dotty * bootstrapped, and making sure that TASTY can link against a compiled * version of Dotty, and compiling the compiler using the SemanticDB generation diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 8d1c9fa5cd86..cd1e3aede382 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -78,6 +78,7 @@ object TestConfiguration { ) val picklingWithCompilerOptions = picklingOptions.withClasspath(withCompilerClasspath).withRunClasspath(withCompilerClasspath) + val refinerOptions = defaultOptions.and("-Yrefine-types") val scala2CompatMode = defaultOptions.and("-source", "3.0-migration") val explicitUTF8 = defaultOptions and ("-encoding", "UTF8") val explicitUTF16 = defaultOptions and ("-encoding", "UTF16") From fc04aa1fd655b7a2f3eb14948b5c5117712122b8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 25 May 2021 20:30:27 +0200 Subject: [PATCH 17/87] Fix typevar instantiation to allow several instantiate calls --- .../src/dotty/tools/dotc/core/Types.scala | 75 ++++++++++--------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index f1a2d48190e0..5829ee772b18 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4441,7 +4441,6 @@ object Types { private[dotc] def link(previous: TypeVar): Unit = linkedVar = previous - instDirection = previous.instDirection private[dotc] def isLinked(previous: TypeVar): Boolean = previous eq linkedVar @@ -4509,41 +4508,45 @@ object Types { * is also a singleton type. */ def instantiate(fromBelow: Boolean)(using Context): Type = - if instDirection == InstDirection.Other then - instDirection = if fromBelow then InstDirection.FromBelow else InstDirection.FromAbove - if instDirection == InstDirection.FromBelow - && linkedVar != null && linkedVar.inst.isSingleton - then - // Force new instantiation to be also a singleton. - // This is neceesary to make several macro tests pass. An example is pos-macros/i9812.scala. - (this <:< defn.SingletonType) - .showing(i"add upper singleton bound to $this, success = $result", refinr) - var inst = TypeComparer.instanceType(origin, instDirection == InstDirection.FromBelow) - if linkedVar != null then - refinr.println(i"instantiate $this to $inst, was ${linkedVar.inst}, fromBelow = ${instDirection == InstDirection.FromBelow}") - // Instead of instantiating to an extremal type Nothing or Any, pick the previous - // instantiation as long as it is compatible with the current constraints. - // This is needed because of a particular interaction of type variable instantiation - // and implicit search. Before doing an implicit search, some type variables are - // instantiated via `instantiatSelected`. During retyping, the implicit argument - // is passed explicitly, but if it has type parameters, those parameters become - // fresh type variables. Instantiating these type variables is now done in a constraint - // that is weaker than the original typing constraint, since the `instantiateSelected` - // step is missing. An example with more explanations is run/i7960.scala. - val needsOldInstance = - if instDirection == InstDirection.FromBelow then - inst.isExactlyNothing - && !linkedVar.inst.isExactlyNothing - && linkedVar.inst <:< this - else - inst.isExactlyAny - && !linkedVar.inst.isExactlyAny - && this <:< linkedVar.inst - if needsOldInstance then - inst = linkedVar.inst - .showing(i"avoid extremal instance for $this be instantiating with old $inst", refinr) - - instantiateWith(avoidCaptures(inst)) + instDirection = if fromBelow then InstDirection.FromBelow else InstDirection.FromAbove + if linkedVar != null + && linkedVar.instDirection != instDirection + && linkedVar.instDirection != InstDirection.Other then + instantiate(!fromBelow) + else + if instDirection == InstDirection.FromBelow + && linkedVar != null && linkedVar.inst.isSingleton + then + // Force new instantiation to be also a singleton. + // This is neceesary to make several macro tests pass. An example is pos-macros/i9812.scala. + (this <:< defn.SingletonType) + .showing(i"add upper singleton bound to $this, success = $result", refinr) + var inst = TypeComparer.instanceType(origin, fromBelow) + if linkedVar != null then + refinr.println(i"instantiate $this to $inst, was ${linkedVar.inst}, fromBelow = ${instDirection == InstDirection.FromBelow}") + // Instead of instantiating to an extremal type Nothing or Any, pick the previous + // instantiation as long as it is compatible with the current constraints. + // This is needed because of a particular interaction of type variable instantiation + // and implicit search. Before doing an implicit search, some type variables are + // instantiated via `instantiatSelected`. During retyping, the implicit argument + // is passed explicitly, but if it has type parameters, those parameters become + // fresh type variables. Instantiating these type variables is now done in a constraint + // that is weaker than the original typing constraint, since the `instantiateSelected` + // step is missing. An example with more explanations is run/i7960.scala. + val needsOldInstance = + if instDirection == InstDirection.FromBelow then + inst.isExactlyNothing + && !linkedVar.inst.isExactlyNothing + && linkedVar.inst <:< this + else + inst.isExactlyAny + && !linkedVar.inst.isExactlyAny + && this <:< linkedVar.inst + if needsOldInstance then + inst = linkedVar.inst + .showing(i"avoid extremal instance for $this be instantiating with old $inst", refinr) + + instantiateWith(avoidCaptures(inst)) end instantiate /** For uninstantiated type variables: the entry in the constraint (either bounds or From dfeea9b6bbeb5409094d0b281d031e47df3d6c38 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 27 May 2021 12:12:53 +0200 Subject: [PATCH 18/87] Add InferredTypeTree to Trees.Instance --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 1 + .../src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Inferencing.scala | 2 +- compiler/src/dotty/tools/dotc/typer/RefineTypes.scala | 8 ++++---- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index df913a0f4405..5bb3b4ccbcc9 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1081,6 +1081,7 @@ object Trees { type JavaSeqLiteral = Trees.JavaSeqLiteral[T] type Inlined = Trees.Inlined[T] type TypeTree = Trees.TypeTree[T] + type InferredTypeTree = Trees.InferredTypeTree[T] type SingletonTypeTree = Trees.SingletonTypeTree[T] type RefinedTypeTree = Trees.RefinedTypeTree[T] type AppliedTypeTree = Trees.AppliedTypeTree[T] diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index e92ca51dc9ec..f83e65499900 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -500,7 +500,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "" case TypeTree() => typeText(toText(tree.typeOpt)) - ~ Str("(inf)").provided(tree.isInstanceOf[InferredTypeTree[_]] && printDebug) + ~ Str("(inf)").provided(tree.isInstanceOf[InferredTypeTree] && printDebug) case SingletonTypeTree(ref) => toTextLocal(ref) ~ "." ~ keywordStr("type") case RefinedTypeTree(tpt, refines) => diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 7e19e48426a4..fa26bd937d79 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -333,7 +333,7 @@ object Inferencing { @tailrec def boundVars(tree: Tree, acc: List[TypeVar]): List[TypeVar] = tree match { case Apply(fn, _) => boundVars(fn, acc) case TypeApply(fn, targs) => - val tvars = targs.filter(_.isInstanceOf[InferredTypeTree[?]]).tpes.collect { + val tvars = targs.filter(_.isInstanceOf[InferredTypeTree]).tpes.collect { case tvar: TypeVar if !tvar.isInstantiated && ctx.typerState.ownedVars.contains(tvar) && diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 5617d5c6bf6f..21ce3c45cb93 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -86,7 +86,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: def resultNeedsRetyping(tree: untpd.ValOrDefDef)(using Context): Boolean = val sym = tree.symbol - tree.tpt.isInstanceOf[InferredTypeTree[?]] + tree.tpt.isInstanceOf[untpd.InferredTypeTree] && (sym.is(Private) || sym.owner.isTerm) && !sym.isOneOf(Param | JavaDefined) && !sym.isOneOf(FinalOrInline, butNot = Method | Mutable) @@ -95,7 +95,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: override def index(trees: List[untpd.Tree])(using Context): Context = for case tree: ValOrDefDef <- trees.asInstanceOf[List[Tree]] do val sym = tree.symbol - if tree.tpt.isInstanceOf[InferredTypeTree[?]] + if tree.tpt.isInstanceOf[untpd.InferredTypeTree] && (sym.is(Private) || sym.owner.isTerm) && !sym.isOneOf(Param | JavaDefined) && !sym.isOneOf(FinalOrInline, butNot = Method | Mutable) @@ -139,7 +139,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: untpd.cpy.Select(tree)(qual1, name).withType(ownType) override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): TypeTree = - if tree.isInstanceOf[InferredTypeTree[_]] && isFullyDefined(pt, ForceDegree.flipBottom) then + if tree.isInstanceOf[untpd.InferredTypeTree] && isFullyDefined(pt, ForceDegree.flipBottom) then tree.withType(pt) else promote(tree) @@ -159,7 +159,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: private def resetTypeVars(tree: Tree)(using Context): (Tree, List[TypeVar]) = tree match case tree: TypeApply => val isInferred = tree.args.forall { - case arg: InferredTypeTree[?] => + case arg: InferredTypeTree => arg.tpe match case tvar: TypeVar => tvar.isInstantiated // test makes sure we do not reset typevars again in eta expanded closures From 47dd4a327f8f7a73a3862ef193887b3351bbd2c5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 27 May 2021 17:40:57 +0200 Subject: [PATCH 19/87] Retype inferred closure parameters --- .../dotty/tools/dotc/reporting/messages.scala | 3 +- .../dotty/tools/dotc/typer/RefineTypes.scala | 147 ++++++++++++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 128 +++++++-------- 3 files changed, 181 insertions(+), 97 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 8a7852e56f9a..fb7c6d41e885 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -149,7 +149,6 @@ import transform.SymUtils._ } class AnonymousFunctionMissingParamType(param: untpd.ValDef, - args: List[untpd.Tree], tree: untpd.Function, pt: Type) (using Context) @@ -157,7 +156,7 @@ import transform.SymUtils._ def msg = { val ofFun = if param.name.is(WildcardParamName) - || (MethodType.syntheticParamNames(args.length + 1) contains param.name) + || (MethodType.syntheticParamNames(tree.args.length + 1) contains param.name) then i" of expanded function:\n$tree" else "" diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 21ce3c45cb93..3b91ae994e3f 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -27,6 +27,26 @@ import reporting._ import ProtoTypes._ import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions +/** A class that can be used to do type checking again after the first typer phase + * is run. This phase will use the output of the previous typer but "forget" certain things + * so that they can be reinferred. Things that are forgotten fall into the following + * categories: + * + * 1. Bindings of inferred type variables in type applications. + * 2. Inferred types of local or private vals or vars. Exception: Types of + * inline vals and Java-defined fields are kept. + * 3. Inferred result types of local or private methods. Eception: Types + * of default getters and Java-defined methods are kept. + * (The default getter restriction is there for technical reason, we should be + * able to lift it once we change the scheme for default arguments). + * 4. Types of closure parameters that are inferred from the expected type. + * Types of closure parameters that are inferred from the called method + * are left alone (also for technical reasons). + * + * The re-typed trees and associated symbol infos are thrown away once the phase + * has ended. So the phase can be only used for more refined type checking, but + * not for code transformation. + */ class RefineTypes extends Phase, IdentityDenotTransformer: import RefineTypes.* import ast.tpd.* @@ -59,6 +79,29 @@ class RefineTypes extends Phase, IdentityDenotTransformer: override def newLikeThis: Typer = new TypeRefiner + /* override def typedAhead(tree: untpd.Tree, typed: untpd.Tree => Tree)(using Context): Tree = + tree.getAttachment(TypedAhead) match + case Some(ttree) => ttree + case none => + val ttree = typed(tree) + tree.putAttachment(TypedAhead, ttree) + ttree*/ + + /** Update the symbol's info to `newInfo` for the current phase, and + * to the symbol's orginal info for the phase afterwards. + */ + def updateInfo(sym: Symbol, newInfo: Type)(using Context): Unit = + sym.copySymDenotation().installAfter(thisPhase) // reset + sym.copySymDenotation( + info = newInfo, + initFlags = + if newInfo.isInstanceOf[LazyType] then sym.flags &~ Touched + else sym.flags + ).installAfter(preRefinePhase) + + /** A completer for local and provate vals, vars, and defs. Re-infers + * the type from the type of the right-hand side expression. + */ class RefineCompleter(val original: ValOrDefDef)(using Context) extends LazyType: def completeInCreationContext(symd: SymDenotation): Unit = val (paramss, paramFn) = original match @@ -76,22 +119,14 @@ class RefineTypes extends Phase, IdentityDenotTransformer: def complete(symd: SymDenotation)(using Context): Unit = completeInCreationContext(symd) end RefineCompleter - override def typedAhead(tree: untpd.Tree, typed: untpd.Tree => Tree)(using Context): Tree = - tree.getAttachment(TypedAhead) match - case Some(ttree) => ttree - case none => - val ttree = typed(tree) - tree.putAttachment(TypedAhead, ttree) - ttree - - def resultNeedsRetyping(tree: untpd.ValOrDefDef)(using Context): Boolean = - val sym = tree.symbol - tree.tpt.isInstanceOf[untpd.InferredTypeTree] - && (sym.is(Private) || sym.owner.isTerm) - && !sym.isOneOf(Param | JavaDefined) - && !sym.isOneOf(FinalOrInline, butNot = Method | Mutable) - && !sym.name.is(DefaultGetterName) - + /** Update the infos of all symbols defined `trees` that have (result) types + * that need to be reinferred. This is the case if + * 1. the type was inferred originally, and + * 2. the definition is private or local, + * 3. the definition is not a parameter or Java defined + * 4. the definition is not an inline value + * 5. the definition is not a default getter + */ override def index(trees: List[untpd.Tree])(using Context): Context = for case tree: ValOrDefDef <- trees.asInstanceOf[List[Tree]] do val sym = tree.symbol @@ -101,13 +136,10 @@ class RefineTypes extends Phase, IdentityDenotTransformer: && !sym.isOneOf(FinalOrInline, butNot = Method | Mutable) && !sym.name.is(DefaultGetterName) then - tree.symbol.copySymDenotation().installAfter(thisPhase) // reset - tree.symbol.copySymDenotation( - info = RefineCompleter(tree), - initFlags = tree.symbol.flags &~ Touched - ).installAfter(preRefinePhase) + updateInfo(sym, RefineCompleter(tree)) ctx + /** Keep the types of all source-written type trees; re-typecheck the rest */ override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = trace(i"typed $tree, $pt", refinr, show = true) { tree.removeAttachment(TypedAhead) match @@ -125,6 +157,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: super.typedUnadapted(tree, pt, locked) } + /** Keep the symbol of the `select` but re-infer its type */ override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = val Select(qual, name) = tree if name.is(OuterSelectName) then promote(tree) @@ -138,12 +171,16 @@ class RefineTypes extends Phase, IdentityDenotTransformer: val ownType = qualType.select(name, mbr) untpd.cpy.Select(tree)(qual1, name).withType(ownType) + /** Set the type of inferred TypeTrees to the expected type. Keep the others unchanged. */ override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): TypeTree = if tree.isInstanceOf[untpd.InferredTypeTree] && isFullyDefined(pt, ForceDegree.flipBottom) then tree.withType(pt) else promote(tree) + /** Redo core steps of type checking from Typer (they were overridden in ReTyper). + * Compare with `typedTyped` in TreeChecker that does essentially the same thing + */ override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = val tpt1 = checkSimpleKinded(typedType(tree.tpt)) val expr1 = tree.expr match @@ -156,7 +193,13 @@ class RefineTypes extends Phase, IdentityDenotTransformer: typed(tree.expr, pt1) untpd.cpy.Typed(tree)(expr1, tpt1).withType(tree.typeOpt) - private def resetTypeVars(tree: Tree)(using Context): (Tree, List[TypeVar]) = tree match + /** Replace all type variables in a (possibly embedded) type application + * by fresh, uninstantiated type variables that are pairwise linked with + * the old ones. The type application can either be the toplevel tree `tree` + * or wrapped in one or more closures. + * @return The changed tree with the new type variables, and the list of freshly created type variables + */ + private def resetTypeVars[T <: tpd.Tree](tree: T)(using Context): (T, List[TypeVar]) = tree match case tree: TypeApply => val isInferred = tree.args.forall { case arg: InferredTypeTree => @@ -171,7 +214,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: val args1 = constrained(tree.fun.tpe.widen.asInstanceOf[TypeLambda], tree)._2 for i <- args.indices do args1(i).tpe.asInstanceOf[TypeVar].link(args(i).tpe.asInstanceOf[TypeVar]) - (cpy.TypeApply(tree)(tree.fun, args1), args1.tpes.asInstanceOf[List[TypeVar]]) + (cpy.TypeApply(tree)(tree.fun, args1).asInstanceOf[T], args1.tpes.asInstanceOf[List[TypeVar]]) else (tree, Nil) case Block(stats, closure: Closure) => @@ -183,25 +226,65 @@ class RefineTypes extends Phase, IdentityDenotTransformer: cpy.DefDef(stat)(rhs = rhs1) case stat => stat } - (cpy.Block(tree)(stats1, closure), tvars) + (cpy.Block(tree)(stats1, closure).asInstanceOf[T], tvars) case Block(Nil, expr) => val (rhs1, tvars1) = resetTypeVars(expr) - (cpy.Block(tree)(Nil, rhs1), tvars1) + (cpy.Block(tree)(Nil, rhs1).asInstanceOf[T], tvars1) case _ => (tree, Nil) end resetTypeVars - override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = - if tree.symbol == defn.Predef_classOf then - promote(tree) - else - val tree1 = resetTypeVars(tree.asInstanceOf[TypeApply])._1.asInstanceOf[TypeApply] - super.typedTypeApply(tree1, pt) + /** The application with all inferred type arguments reset to fresh type variab;es + * classOf[...] applications are left alone. + */ + override def typedTypeApply(app: untpd.TypeApply, pt: Type)(using Context): Tree = + val app0 = promote(app) + if app0.symbol == defn.Predef_classOf then app0 + else super.typedTypeApply(resetTypeVars(app0)._1, pt) + + /** If block is defines closure, replace all parameters that were inferred + * from the expected type by corresponding parts of the new expected type. + * Update infos of parameter symbols and the anonymous function accordingly. + */ + override def typedBlock(blk: untpd.Block, pt: Type)(using Context): Tree = + val blk0 = promote(blk) + val blk1 = blk0.expr match + case closure: Closure => + val stats1 = blk0.stats.mapConserve { + case stat: DefDef if stat.symbol == closure.meth.symbol => + stat.paramss match + case ValDefs(params) :: Nil => + val (protoFormals, _) = decomposeProtoFunction(pt, params.length, stat.srcPos) + val params1 = params.zipWithConserve(protoFormals) { + case (param @ ValDef(_, tpt: InferredTypeTree, _), formal) + if isFullyDefined(formal, ForceDegree.failBottom) => + updateInfo(param.symbol, formal) + cpy.ValDef(param)(tpt = param.tpt.withType(formal)) + case (param, _) => + param + } + if params eq params1 then stat + else + val mt = stat.symbol.info.asInstanceOf[MethodType] + val formals1 = + for i <- mt.paramInfos.indices.toList yield + if params(i) eq params1(i) then mt.paramInfos(i) else protoFormals(i) + updateInfo(stat.symbol, mt.derivedLambdaType(paramInfos = formals1)) + cpy.DefDef(stat)(paramss = params1 :: Nil) + case _ => + stat + } + cpy.Block(blk0)(stats1, closure) + case _ => + blk + super.typedBlock(blk1, pt) + /** If tree defines an anonymous function,??? + */ override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = sym.ensureCompleted() if sym.isAnonymousFunction then - val ddef0 = ddef.asInstanceOf[tpd.DefDef] + val ddef0 = promote(ddef) val (rhs2, newTvars) = resetTypeVars(ddef0.rhs) val ddef1 = cpy.DefDef(ddef0)(rhs = rhs2) val bindsNestedTypeVar = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0438d197aa56..c097584f2192 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1114,7 +1114,7 @@ class Typer extends Namer * def double(x: Char): String = s"$x$x" * "abc" flatMap double */ - private def decomposeProtoFunction(pt: Type, defaultArity: Int, tree: untpd.Tree)(using Context): (List[Type], untpd.Tree) = { + protected def decomposeProtoFunction(pt: Type, defaultArity: Int, pos: SrcPos)(using Context): (List[Type], untpd.Tree) = { def typeTree(tp: Type) = tp match { case _: WildcardType => untpd.TypeTree() case _ => untpd.TypeTree(tp) @@ -1133,34 +1133,60 @@ class Typer extends Namer report.error( i"""Implementation restriction: Expected result type $pt1 |is a curried dependent context function type. Such types are not yet supported.""", - tree.srcPos) - + pos) pt1 match { + case pt1 if defn.isNonRefinedFunction(pt1) => + // if expected parameter type(s) are wildcards, approximate from below. + // if expected result type is a wildcard, approximate from above. + // this can type the greatest set of admissible closures. + (pt1.argTypesLo.init, typeTree(interpolateWildcards(pt1.argTypesHi.last))) + case RefinedType(parent, nme.apply, mt @ MethodTpe(_, formals, restpe)) + if defn.isNonRefinedFunction(parent) && formals.length == defaultArity => + (formals, untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef)))) + case SAMType(mt @ MethodTpe(_, formals, restpe)) => + (formals, + if (mt.isResultDependent) + untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef))) + else + typeTree(restpe)) case tp: TypeParamRef => - decomposeProtoFunction(ctx.typerState.constraint.entry(tp).bounds.hi, defaultArity, tree) - case _ => pt1.findFunctionTypeInUnion match { - case pt1 if defn.isNonRefinedFunction(pt1) => - // if expected parameter type(s) are wildcards, approximate from below. - // if expected result type is a wildcard, approximate from above. - // this can type the greatest set of admissible closures. - (pt1.argTypesLo.init, typeTree(interpolateWildcards(pt1.argTypesHi.last))) - case RefinedType(parent, nme.apply, mt @ MethodTpe(_, formals, restpe)) - if defn.isNonRefinedFunction(parent) && formals.length == defaultArity => - (formals, untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef)))) - case SAMType(mt @ MethodTpe(_, formals, restpe)) => - (formals, - if (mt.isResultDependent) - untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef))) - else - typeTree(restpe)) - case tp: TypeParamRef => - decomposeProtoFunction(ctx.typerState.constraint.entry(tp).bounds.hi, defaultArity, tree) - case _ => - (List.tabulate(defaultArity)(alwaysWildcardType), untpd.TypeTree()) - } + decomposeProtoFunction(ctx.typerState.constraint.entry(tp).bounds.hi, defaultArity, pos) + case _ => + (List.tabulate(defaultArity)(alwaysWildcardType), untpd.TypeTree()) } } + /** The parameter type for a parameter in a lambda that does + * not have an explicit type given, and where the type is not known from the context. + * In this case the paranmeter type needs to be inferred the "target type" T known + * from the callee `f` if the lambda is of a form like `x => f(x)`. + * If `T` exists, we know that `S <: I <: T`. + * + * The inference makes two attempts: + * + * 1. Compute the target type `T` and make it known that `S <: T`. + * If the expected type `S` can be fully defined under ForceDegree.flipBottom, + * pick this one (this might use the fact that S <: T for an upper approximation). + * 2. Otherwise, if the target type `T` can be fully defined under ForceDegree.flipBottom, + * pick this one. + * + * If both attempts fail, issue a "missing parameter type" error. + */ + def inferredFromTarget( + param: untpd.ValDef, formal: Type, calleeType: Type, paramIndex: Name => Int)(using Context): Type = + val target = calleeType.widen match + case mtpe: MethodType => + val pos = paramIndex(param.name) + if pos < mtpe.paramInfos.length then + val ptype = mtpe.paramInfos(pos) + if ptype.isRepeatedParam then NoType else ptype + else NoType + case _ => NoType + if target.exists then formal <:< target + if isFullyDefined(formal, ForceDegree.flipBottom) then formal + else if target.exists && isFullyDefined(target, ForceDegree.flipBottom) then target + else NoType + def typedFunction(tree: untpd.Function, pt: Type)(using Context): Tree = if (ctx.mode is Mode.Type) typedFunctionType(tree, pt) else typedFunctionValue(tree, pt) @@ -1333,41 +1359,7 @@ class Typer extends Namer case _ => } - val (protoFormals, resultTpt) = decomposeProtoFunction(pt, params.length, tree) - - /** The inferred parameter type for a parameter in a lambda that does - * not have an explicit type given. - * An inferred parameter type I has two possible sources: - * - the type S known from the context - * - the "target type" T known from the callee `f` if the lambda is of a form like `x => f(x)` - * If `T` exists, we know that `S <: I <: T`. - * - * The inference makes three attempts: - * - * 1. If the expected type `S` is already fully defined under ForceDegree.failBottom - * pick this one. - * 2. Compute the target type `T` and make it known that `S <: T`. - * If the expected type `S` can be fully defined under ForceDegree.flipBottom, - * pick this one (this might use the fact that S <: T for an upper approximation). - * 3. Otherwise, if the target type `T` can be fully defined under ForceDegree.flipBottom, - * pick this one. - * - * If all attempts fail, issue a "missing parameter type" error. - */ - def inferredParamType(param: untpd.ValDef, formal: Type): Type = - if isFullyDefined(formal, ForceDegree.failBottom) then return formal - val target = calleeType.widen match - case mtpe: MethodType => - val pos = paramIndex(param.name) - if pos < mtpe.paramInfos.length then - val ptype = mtpe.paramInfos(pos) - if ptype.isRepeatedParam then NoType else ptype - else NoType - case _ => NoType - if target.exists then formal <:< target - if isFullyDefined(formal, ForceDegree.flipBottom) then formal - else if target.exists && isFullyDefined(target, ForceDegree.flipBottom) then target - else errorType(AnonymousFunctionMissingParamType(param, params, tree, formal), param.srcPos) + val (protoFormals, resultTpt) = decomposeProtoFunction(pt, params.length, tree.srcPos) def protoFormal(i: Int): Type = if (protoFormals.length == params.length) protoFormals(i) @@ -1393,9 +1385,19 @@ class Typer extends Namer val inferredParams: List[untpd.ValDef] = for ((param, i) <- params.zipWithIndex) yield if (!param.tpt.isEmpty) param - else cpy.ValDef(param)( - tpt = untpd.TypeTree( - inferredParamType(param, protoFormal(i)).translateFromRepeated(toArray = false))) + else + val formal = protoFormal(i) + val knownFormal = isFullyDefined(formal, ForceDegree.failBottom) + val paramType = + if knownFormal then formal + else inferredFromTarget(param, formal, calleeType, paramIndex) + .orElse(errorType(AnonymousFunctionMissingParamType(param, tree, formal), param.srcPos)) + val paramTpt = untpd.TypedSplice( + (if knownFormal then InferredTypeTree() else untpd.TypeTree()) + .withType(paramType.translateFromRepeated(toArray = false)) + .withSpan(param.span.endPos) + ) + cpy.ValDef(param)(tpt = paramTpt) desugar.makeClosure(inferredParams, fnBody, resultTpt, isContextual, tree.span) } typed(desugared, pt) @@ -1461,7 +1463,7 @@ class Typer extends Namer typedMatchFinish(tree, tpd.EmptyTree, defn.ImplicitScrutineeTypeRef, cases1, pt) } else { - val (protoFormals, _) = decomposeProtoFunction(pt, 1, tree) + val (protoFormals, _) = decomposeProtoFunction(pt, 1, tree.srcPos) val checkMode = if (pt.isRef(defn.PartialFunctionClass)) desugar.MatchCheck.None else desugar.MatchCheck.Exhaustive From 054708e78a52e24b95f5e9a580ea3904b1e139f8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 28 May 2021 10:57:16 +0200 Subject: [PATCH 20/87] Add tastyBootstrap to refine tests --- compiler/test/dotty/tools/dotc/CompilationTests.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 6ff2401faa8d..d4af8b3639bb 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -252,7 +252,7 @@ class CompilationTests { Properties.compilerInterface, Properties.scalaLibrary, Properties.scalaAsm, Properties.dottyInterfaces, Properties.jlineTerminal, Properties.jlineReader, ).mkString(File.pathSeparator), - Array("-Ycheck-reentrant", "-language:postfixOps", "-Xsemanticdb") + Array("-Ycheck-reentrant", "-Yrefine-types", "-language:postfixOps", "-Xsemanticdb") ) val libraryDirs = List(Paths.get("library/src"), Paths.get("library/src-bootstrapped")) @@ -260,7 +260,7 @@ class CompilationTests { val lib = compileList("lib", librarySources, - defaultOptions.and("-Ycheck-reentrant", + defaultOptions.and("-Ycheck-reentrant", "-Yrefine-types", "-language:experimental.erasedDefinitions", // support declaration of scala.compiletime.erasedValue // "-source", "future", // TODO: re-enable once we allow : @unchecked in pattern definitions. Right now, lots of narrowing pattern definitions fail. ))(libGroup) From 9776b16aafee0f0648e9643205672b8a4afd632c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 28 May 2021 11:00:58 +0200 Subject: [PATCH 21/87] Replace leaked type variables bound in callee --- .../src/dotty/tools/dotc/core/Types.scala | 2 + .../dotty/tools/dotc/typer/RefineTypes.scala | 88 +++++++++++-------- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5829ee772b18..5e314f17a583 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4439,6 +4439,8 @@ object Types { private var instDirection: InstDirection = InstDirection.Other private var linkedVar: TypeVar = null + private[dotc] def linkedOriginal: TypeVar = linkedVar + private[dotc] def link(previous: TypeVar): Unit = linkedVar = previous diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 3b91ae994e3f..db5c807159ba 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -197,41 +197,47 @@ class RefineTypes extends Phase, IdentityDenotTransformer: * by fresh, uninstantiated type variables that are pairwise linked with * the old ones. The type application can either be the toplevel tree `tree` * or wrapped in one or more closures. - * @return The changed tree with the new type variables, and the list of freshly created type variables + * Replacement insde closures is necessary since sometimes type variables + * bound in a callee are leaked in the parameter types of an enclosing closure + * that infers its parameter types from the callee. + * @return The changed tree with the new type variables, + * and a map from old type variables to corresponding freshly created type variables */ - private def resetTypeVars[T <: tpd.Tree](tree: T)(using Context): (T, List[TypeVar]) = tree match + private def resetTypeVars[T <: tpd.Tree](tree: T)(using Context): (T, Map[TypeVar, TypeVar]) = tree match case tree: TypeApply => - val isInferred = tree.args.forall { - case arg: InferredTypeTree => - arg.tpe match - case tvar: TypeVar => - tvar.isInstantiated // test makes sure we do not reset typevars again in eta expanded closures - case _ => false - case _ => false - } - if isInferred then - val args = tree.args - val args1 = constrained(tree.fun.tpe.widen.asInstanceOf[TypeLambda], tree)._2 - for i <- args.indices do - args1(i).tpe.asInstanceOf[TypeVar].link(args(i).tpe.asInstanceOf[TypeVar]) - (cpy.TypeApply(tree)(tree.fun, args1).asInstanceOf[T], args1.tpes.asInstanceOf[List[TypeVar]]) + val tvars = for arg <- tree.args; case tvar: TypeVar <- arg.tpe :: Nil yield tvar + if tvars.nonEmpty && tvars.length == tree.args.length then + val (args1, tvars1) = + if tvars.head.isInstantiated then + // we are seeing type variables that are not yet copied by a previous resetTypeVars + val args1 = constrained(tree.fun.tpe.widen.asInstanceOf[TypeLambda], tree)._2 + val tvars1 = args1.tpes.asInstanceOf[List[TypeVar]] + for (tvar, tvar1) <- tvars.lazyZip(tvars1) do tvar1.link(tvar) + (args1, tvars1) + else + (tree.args, tvars) + (cpy.TypeApply(tree)(tree.fun, args1).asInstanceOf[T], + tvars1.map(tvar => (tvar.linkedOriginal, tvar)).toMap) else - (tree, Nil) + (tree, Map.empty) + case tree @ Apply(fn, args) => + val (fn1, map1) = resetTypeVars(fn) + (cpy.Apply(tree)(fn, args).asInstanceOf[T], map1) case Block(stats, closure: Closure) => - var tvars: List[TypeVar] = Nil + var tvmap: Map[TypeVar, TypeVar] = Map.empty val stats1 = stats.mapConserve { case stat: DefDef if stat.symbol == closure.meth.symbol => - val (rhs1, tvars1) = resetTypeVars(stat.rhs) - tvars = tvars1 + val (rhs1, map1) = resetTypeVars(stat.rhs) + tvmap = map1 cpy.DefDef(stat)(rhs = rhs1) case stat => stat } - (cpy.Block(tree)(stats1, closure).asInstanceOf[T], tvars) + (cpy.Block(tree)(stats1, closure).asInstanceOf[T], tvmap) case Block(Nil, expr) => - val (rhs1, tvars1) = resetTypeVars(expr) - (cpy.Block(tree)(Nil, rhs1).asInstanceOf[T], tvars1) + val (rhs1, map1) = resetTypeVars(expr) + (cpy.Block(tree)(Nil, rhs1).asInstanceOf[T], map1) case _ => - (tree, Nil) + (tree, Map.empty) end resetTypeVars /** The application with all inferred type arguments reset to fresh type variab;es @@ -279,27 +285,33 @@ class RefineTypes extends Phase, IdentityDenotTransformer: blk super.typedBlock(blk1, pt) - /** If tree defines an anonymous function,??? + /** If tree defines an anonymous function, make sure that any type variables + * defined in the callee rhs are replaced in the function itself. */ override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = sym.ensureCompleted() if sym.isAnonymousFunction then val ddef0 = promote(ddef) - val (rhs2, newTvars) = resetTypeVars(ddef0.rhs) - val ddef1 = cpy.DefDef(ddef0)(rhs = rhs2) - val bindsNestedTypeVar = - newTvars.nonEmpty - && sym.rawParamss.nestedExists(param => - param.info.existsPart({ - case tvar1: TypeVar => newTvars.exists(_.isLinked(tvar1)) - case _ => false - }, stopAtStatic = true, forceLazy = false)) - if bindsNestedTypeVar then - val nestedCtx = ctx.fresh.setNewTyperState() + val (rhs1, tvmap) = resetTypeVars(ddef0.rhs) + if tvmap.nonEmpty then + val tmap = new TypeMap: + def apply(t: Type) = mapOver { + t match + case t: TypeVar => tvmap.getOrElse(t, t) + case _ => t + } + val ValDefs(params) :: Nil = ddef0.paramss + val params1 = params.mapConserve { param => + updateInfo(param.symbol, tmap(param.symbol.info)) + cpy.ValDef(param)(tpt = param.tpt.withType(tmap(param.tpt.tpe))) + } + val mt = sym.info.asInstanceOf[MethodType] + updateInfo(sym, mt.derivedLambdaType(paramInfos = mt.paramInfos.mapConserve(tmap))) + val ddef1 = cpy.DefDef(ddef0)(paramss = params1 :: Nil, rhs = rhs1) + val nestedCtx = ctx.fresh.setNewTyperState() // needed so that leaked type variables properly nest in their owning typerstate try inContext(nestedCtx) { super.typedDefDef(ddef1, sym) } finally nestedCtx.typerState.commit() - else - super.typedDefDef(ddef1, sym) + else super.typedDefDef(ddef, sym) else super.typedDefDef(ddef, sym) override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = From 72caef34e3d71799080dae4d6ec2c32a672a4690 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 14:58:26 +0200 Subject: [PATCH 22/87] Fix rebase breakage and drop unused hook method --- .../dotty/tools/dotc/typer/Inferencing.scala | 4 +- .../dotty/tools/dotc/typer/RefineTypes.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 45 ++++++++++--------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index fa26bd937d79..d608ede93a22 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -533,8 +533,6 @@ trait Inferencing { this: Typer => import Inferencing._ import tpd._ - def qualifyForInterpolation(tvars: TypeVars)(using Context): TypeVars = tvars - /** Interpolate undetermined type variables in the widened type of this tree. * @param tree the tree whose type is interpolated * @param pt the expected result type @@ -565,7 +563,7 @@ trait Inferencing { this: Typer => val ownedVars = state.ownedVars if ((ownedVars ne locked) && !ownedVars.isEmpty) { - val qualifying = qualifyForInterpolation(ownedVars -- locked) + val qualifying = ownedVars -- locked if (!qualifying.isEmpty) { typr.println(i"interpolate $tree: ${tree.tpe.widen} in $state, owned vars = ${state.ownedVars.toList}%, %, qualifying = ${qualifying.toList}%, %, previous = ${locked.toList}%, % / ${state.constraint}") val resultAlreadyConstrained = diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index db5c807159ba..ac235c7abd3b 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -260,7 +260,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: case stat: DefDef if stat.symbol == closure.meth.symbol => stat.paramss match case ValDefs(params) :: Nil => - val (protoFormals, _) = decomposeProtoFunction(pt, params.length, stat.srcPos) + val (protoFormals, _) = decomposeProtoFunction(pt, params.length, stat) val params1 = params.zipWithConserve(protoFormals) { case (param @ ValDef(_, tpt: InferredTypeTree, _), formal) if isFullyDefined(formal, ForceDegree.failBottom) => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c097584f2192..b3deebeecf79 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1114,7 +1114,7 @@ class Typer extends Namer * def double(x: Char): String = s"$x$x" * "abc" flatMap double */ - protected def decomposeProtoFunction(pt: Type, defaultArity: Int, pos: SrcPos)(using Context): (List[Type], untpd.Tree) = { + protected def decomposeProtoFunction(pt: Type, defaultArity: Int, tree: untpd.Tree)(using Context): (List[Type], untpd.Tree) = { def typeTree(tp: Type) = tp match { case _: WildcardType => untpd.TypeTree() case _ => untpd.TypeTree(tp) @@ -1133,26 +1133,29 @@ class Typer extends Namer report.error( i"""Implementation restriction: Expected result type $pt1 |is a curried dependent context function type. Such types are not yet supported.""", - pos) + tree.srcPos) + pt1 match { - case pt1 if defn.isNonRefinedFunction(pt1) => - // if expected parameter type(s) are wildcards, approximate from below. - // if expected result type is a wildcard, approximate from above. - // this can type the greatest set of admissible closures. - (pt1.argTypesLo.init, typeTree(interpolateWildcards(pt1.argTypesHi.last))) - case RefinedType(parent, nme.apply, mt @ MethodTpe(_, formals, restpe)) - if defn.isNonRefinedFunction(parent) && formals.length == defaultArity => - (formals, untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef)))) - case SAMType(mt @ MethodTpe(_, formals, restpe)) => - (formals, - if (mt.isResultDependent) - untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef))) - else - typeTree(restpe)) case tp: TypeParamRef => - decomposeProtoFunction(ctx.typerState.constraint.entry(tp).bounds.hi, defaultArity, pos) - case _ => - (List.tabulate(defaultArity)(alwaysWildcardType), untpd.TypeTree()) + decomposeProtoFunction(ctx.typerState.constraint.entry(tp).bounds.hi, defaultArity, tree) + case _ => pt1.findFunctionTypeInUnion match { + case pt1 if defn.isNonRefinedFunction(pt1) => + // if expected parameter type(s) are wildcards, approximate from below. + // if expected result type is a wildcard, approximate from above. + // this can type the greatest set of admissible closures. + (pt1.argTypesLo.init, typeTree(interpolateWildcards(pt1.argTypesHi.last))) + case RefinedType(parent, nme.apply, mt @ MethodTpe(_, formals, restpe)) + if defn.isNonRefinedFunction(parent) && formals.length == defaultArity => + (formals, untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef)))) + case SAMType(mt @ MethodTpe(_, formals, restpe)) => + (formals, + if mt.isResultDependent then + untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef))) + else + typeTree(restpe)) + case _ => + (List.tabulate(defaultArity)(alwaysWildcardType), untpd.TypeTree()) + } } } @@ -1359,7 +1362,7 @@ class Typer extends Namer case _ => } - val (protoFormals, resultTpt) = decomposeProtoFunction(pt, params.length, tree.srcPos) + val (protoFormals, resultTpt) = decomposeProtoFunction(pt, params.length, tree) def protoFormal(i: Int): Type = if (protoFormals.length == params.length) protoFormals(i) @@ -1463,7 +1466,7 @@ class Typer extends Namer typedMatchFinish(tree, tpd.EmptyTree, defn.ImplicitScrutineeTypeRef, cases1, pt) } else { - val (protoFormals, _) = decomposeProtoFunction(pt, 1, tree.srcPos) + val (protoFormals, _) = decomposeProtoFunction(pt, 1, tree) val checkMode = if (pt.isRef(defn.PartialFunctionClass)) desugar.MatchCheck.None else desugar.MatchCheck.Exhaustive From 87a3e02c6378ff8cf004addf2c153b958e6ea146 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Jun 2021 16:28:44 +0200 Subject: [PATCH 23/87] Re-propagate result types of anonymous functions --- .../src/dotty/tools/dotc/core/Types.scala | 3 ++- .../dotty/tools/dotc/typer/RefineTypes.scala | 27 ++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5e314f17a583..9256b2f084a8 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4482,7 +4482,8 @@ object Types { def msg = i"Inaccessible variables captured in instantation of type variable $this.\n$tp was fixed to $atp" typr.println(msg) val bound = TypeComparer.fullUpperBound(origin) - if !(atp <:< bound) then + if !(atp <:< bound) && !ctx.isAfterTyper then + // t2444.scala fails refining if the second condition is dropped throw new TypeError(i"$msg,\nbut the latter type does not conform to the upper bound $bound") atp // AVOIDANCE TODO: This really works well only if variables are instantiated from below diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index ac235c7abd3b..5029c87c7730 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -260,7 +260,7 @@ class RefineTypes extends Phase, IdentityDenotTransformer: case stat: DefDef if stat.symbol == closure.meth.symbol => stat.paramss match case ValDefs(params) :: Nil => - val (protoFormals, _) = decomposeProtoFunction(pt, params.length, stat) + val (protoFormals, untpdProtoResult) = decomposeProtoFunction(pt, params.length, stat) val params1 = params.zipWithConserve(protoFormals) { case (param @ ValDef(_, tpt: InferredTypeTree, _), formal) if isFullyDefined(formal, ForceDegree.failBottom) => @@ -269,14 +269,33 @@ class RefineTypes extends Phase, IdentityDenotTransformer: case (param, _) => param } - if params eq params1 then stat + def protoResult = untpd.unsplice(untpdProtoResult).asInstanceOf[Tree] + val tpt1 = stat.tpt match + case tpt: InferredTypeTree => + tpt + case tpt => + if protoResult.hasType then tpt.withType(protoResult.tpe) + else tpt + // TODO: Handle DependentTypeTrees. The following does not work, unfortunately: + // val newType = protoResult match + // case untpd.DependentTypeTree(tpFun) => + // tpt.tpe + // val ptpe = tpFun(params1.map(_.symbol)) + // if isFullyDefined(ptpe, ForceDegree.none) then ptpe else tpt.tpe + // case _ => + // protoResult.tpe + // tpt.withType(newType) + if (params eq params1) && (stat.tpt eq tpt1) then stat else val mt = stat.symbol.info.asInstanceOf[MethodType] val formals1 = for i <- mt.paramInfos.indices.toList yield if params(i) eq params1(i) then mt.paramInfos(i) else protoFormals(i) - updateInfo(stat.symbol, mt.derivedLambdaType(paramInfos = formals1)) - cpy.DefDef(stat)(paramss = params1 :: Nil) + val resType1 = + if tpt1 eq stat.tpt then mt.resType else tpt1.tpe + updateInfo(stat.symbol, + mt.derivedLambdaType(paramInfos = formals1, resType = resType1)) + cpy.DefDef(stat)(paramss = params1 :: Nil, tpt = tpt1) case _ => stat } From 0d2de674767e322515e8daa4617bf253d96c356d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Jun 2021 18:51:46 +0200 Subject: [PATCH 24/87] Readapt to latest master --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b3deebeecf79..5e8498f1670c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3074,7 +3074,7 @@ class Typer extends Namer else Some(adapt(tree1, pt, locked)) } { (_, _) => None } - case TypeApply(fn, args) if args.forall(_.isInstanceOf[TypeVarBinder[_]]) => + case TypeApply(fn, args) if args.forall(_.isInstanceOf[InferredTypeTree]) => tryInsertImplicitOnQualifier(fn, pt, locked) case _ => None } From c565d087805415ea82648a7bfbb599bfbcccd809 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 9 Jun 2021 17:17:03 +0200 Subject: [PATCH 25/87] Re-infer type ascriptions added in ensureNoLocalRefs --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 10 ++++---- .../dotty/tools/dotc/typer/RefineTypes.scala | 23 +++++++++++-------- .../src/dotty/tools/dotc/typer/Typer.scala | 5 ++-- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 990bbf4155e9..fd781f1725c5 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -979,11 +979,13 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } /** cast tree to `tp`, assuming no exception is raised, i.e the operation is pure */ - def cast(tp: Type)(using Context): Tree = { - assert(tp.isValueType, i"bad cast: $tree.asInstanceOf[$tp]") + def cast(tp: Type)(using Context): Tree = cast(TypeTree(tp)) + + /** cast tree to `tp`, assuming no exception is raised, i.e the operation is pure */ + def cast(tpt: TypeTree)(using Context): Tree = + assert(tpt.tpe.isValueType, i"bad cast: $tree.asInstanceOf[$tpt]") tree.select(if (ctx.erasedTypes) defn.Any_asInstanceOf else defn.Any_typeCast) - .appliedToType(tp) - } + .appliedToTypeTree(tpt) /** cast `tree` to `tp` (or its box/unbox/cast equivalent when after * erasure and value and non-value types are mixed), diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 5029c87c7730..44306ce95244 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -182,16 +182,21 @@ class RefineTypes extends Phase, IdentityDenotTransformer: * Compare with `typedTyped` in TreeChecker that does essentially the same thing */ override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = - val tpt1 = checkSimpleKinded(typedType(tree.tpt)) - val expr1 = tree.expr match - case id: untpd.Ident if (ctx.mode is Mode.Pattern) && untpd.isVarPattern(id) && (id.name == nme.WILDCARD || id.name == nme.WILDCARD_STAR) => - tree.expr.withType(tpt1.tpe) + tree.tpt match + case _: untpd.InferredTypeTree => + // type tree was introduced by ensureNoLocalRefs, drop the ascription and reinfer + typed(tree.expr, pt) case _ => - var pt1 = tpt1.tpe - if pt1.isRepeatedParam then - pt1 = pt1.translateFromRepeated(toArray = tree.expr.typeOpt.derivesFrom(defn.ArrayClass)) - typed(tree.expr, pt1) - untpd.cpy.Typed(tree)(expr1, tpt1).withType(tree.typeOpt) + val tpt1 = checkSimpleKinded(typedType(tree.tpt)) + val expr1 = tree.expr match + case id: untpd.Ident if (ctx.mode is Mode.Pattern) && untpd.isVarPattern(id) && (id.name == nme.WILDCARD || id.name == nme.WILDCARD_STAR) => + tree.expr.withType(tpt1.tpe) + case _ => + var pt1 = tpt1.tpe + if pt1.isRepeatedParam then + pt1 = pt1.translateFromRepeated(toArray = tree.expr.typeOpt.derivesFrom(defn.ArrayClass)) + typed(tree.expr, pt1) + untpd.cpy.Typed(tree)(expr1, tpt1).withType(tree.typeOpt) /** Replace all type variables in a (possibly embedded) type application * by fresh, uninstantiated type variables that are pairwise linked with diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5e8498f1670c..111f5f42a525 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1056,13 +1056,14 @@ class Typer extends Namer cpy.Block(block)(stats, expr1) withType expr1.tpe // no assignType here because avoid is redundant case _ => val target = pt.simplified - if tree.tpe <:< target then Typed(tree, TypeTree(pt.simplified)) + val targetTpt = InferredTypeTree().withType(target) + if tree.tpe <:< target then Typed(tree, targetTpt) else // This case should not normally arise. It currently does arise in test cases // pos/t4080b.scala and pos/i7067.scala. In that case, a type ascription is wrong // and would not pass Ycheck. We have to use a cast instead. TODO: follow-up why // the cases arise and eliminate them, if possible. - tree.cast(target) + tree.cast(targetTpt) } def noLeaks(t: Tree): Boolean = escapingRefs(t, localSyms).isEmpty if (noLeaks(tree)) tree From 06d809318941cf776b4402031be4f9ed6d848cf7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 9 Jun 2021 17:18:52 +0200 Subject: [PATCH 26/87] Declare that PreRefine can change base types --- compiler/src/dotty/tools/dotc/typer/PreRefine.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/typer/PreRefine.scala b/compiler/src/dotty/tools/dotc/typer/PreRefine.scala index 0e605a7d73e8..f7b73890d6aa 100644 --- a/compiler/src/dotty/tools/dotc/typer/PreRefine.scala +++ b/compiler/src/dotty/tools/dotc/typer/PreRefine.scala @@ -14,6 +14,8 @@ class PreRefine extends Phase, IdentityDenotTransformer: override def isEnabled(using Context) = ctx.settings.YrefineTypes.value + override def changesBaseTypes: Boolean = true + def run(using Context): Unit = assert(next.isInstanceOf[RefineTypes], s"misconfigured phases: phase PreRefine must be followed by phase RefineTypes") From 538e2114d4fe1f9d3bbed8c78b6722ff6a08937d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 12 Jun 2021 14:45:25 +0200 Subject: [PATCH 27/87] Re-infer types of symbols in local and private classes --- .../src/dotty/tools/dotc/typer/RefineTypes.scala | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 44306ce95244..65ef6cb04b89 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -130,11 +130,17 @@ class RefineTypes extends Phase, IdentityDenotTransformer: override def index(trees: List[untpd.Tree])(using Context): Context = for case tree: ValOrDefDef <- trees.asInstanceOf[List[Tree]] do val sym = tree.symbol + def isLocalOnly = + val transOwner = sym.owner.ownersIterator + .dropWhile(owner => owner.isClass && !owner.isStatic && !owner.is(Private)) + .next + sym.is(Private) || transOwner.is(Private) || transOwner.isTerm if tree.tpt.isInstanceOf[untpd.InferredTypeTree] - && (sym.is(Private) || sym.owner.isTerm) + && isLocalOnly && !sym.isOneOf(Param | JavaDefined) && !sym.isOneOf(FinalOrInline, butNot = Method | Mutable) && !sym.name.is(DefaultGetterName) + && !sym.isConstructor then updateInfo(sym, RefineCompleter(tree)) ctx @@ -342,6 +348,11 @@ class RefineTypes extends Phase, IdentityDenotTransformer: sym.ensureCompleted() super.typedValDef(vdef, sym) + override def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(using Context): Tree = + val (impl: untpd.Template) = cdef.rhs: @unchecked + index(impl.body) + super.typedClassDef(cdef, cls) + override def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree = if tree.symbol == defn.StdLibPatchesPackage then promote(tree) // don't check stdlib patches, since their symbols were highjacked by stdlib classes From 6c95cccca40c3038f8189d56c179e4f1b89574ca Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 17:09:09 +0200 Subject: [PATCH 28/87] Add functionality to SimpleIdentitySet --- .../tools/dotc/util/SimpleIdentitySet.scala | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala index 5b3544b894c4..ffca320d53d3 100644 --- a/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala @@ -7,7 +7,6 @@ import collection.mutable */ abstract class SimpleIdentitySet[+Elem <: AnyRef] { def size: Int - final def isEmpty: Boolean = size == 0 def + [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[E] def - [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[Elem] def contains[E >: Elem <: AnyRef](x: E): Boolean @@ -15,20 +14,38 @@ abstract class SimpleIdentitySet[+Elem <: AnyRef] { def exists[E >: Elem <: AnyRef](p: E => Boolean): Boolean def /: [A, E >: Elem <: AnyRef](z: A)(f: (A, E) => A): A def toList: List[Elem] + + final def isEmpty: Boolean = size == 0 + + def forall[E >: Elem <: AnyRef](p: E => Boolean): Boolean = !exists(!p(_)) + + def filter(p: Elem => Boolean): SimpleIdentitySet[Elem] = + val z: SimpleIdentitySet[Elem] = SimpleIdentitySet.empty + (z /: this)((s, x) => if p(x) then s + x else s) + def ++ [E >: Elem <: AnyRef](that: SimpleIdentitySet[E]): SimpleIdentitySet[E] = if (this.size == 0) that else if (that.size == 0) this else ((this: SimpleIdentitySet[E]) /: that)(_ + _) + def -- [E >: Elem <: AnyRef](that: SimpleIdentitySet[E]): SimpleIdentitySet[E] = if (that.size == 0) this else ((SimpleIdentitySet.empty: SimpleIdentitySet[E]) /: this) { (s, x) => if (that.contains(x)) s else s + x } - override def toString: String = toList.mkString("(", ", ", ")") + override def toString: String = toList.mkString("{", ", ", "}") } object SimpleIdentitySet { + + def apply[Elem <: AnyRef](elems: Elem*): SimpleIdentitySet[Elem] = + elems.foldLeft(empty: SimpleIdentitySet[Elem])(_ + _) + + extension [E <: AnyRef](xs: SimpleIdentitySet[E]) + def intersect(ys: SimpleIdentitySet[E]): SimpleIdentitySet[E] = + xs.filter(ys.contains) + object empty extends SimpleIdentitySet[Nothing] { def size: Int = 0 def + [E <: AnyRef](x: E): SimpleIdentitySet[E] = From 5a2cd34096006cbac212931e15c6a3a5b75b2a59 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 17:17:28 +0200 Subject: [PATCH 29/87] Basic support for CapturingType --- .../dotty/tools/dotc/config/Printers.scala | 1 + .../dotty/tools/dotc/core/Definitions.scala | 10 +- compiler/src/dotty/tools/dotc/core/Mode.scala | 2 + .../src/dotty/tools/dotc/core/StdNames.scala | 4 +- .../src/dotty/tools/dotc/core/Types.scala | 158 ++++++++++++++++-- .../tools/dotc/printing/PlainPrinter.scala | 7 + .../dotty/tools/dotc/printing/Printer.scala | 3 + 7 files changed, 165 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 8b505e792f35..fcbe578206cc 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -12,6 +12,7 @@ object Printers { val default = new Printer + val capt = noPrinter val constr = noPrinter val core = noPrinter val checks = noPrinter diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 547ceb292055..366d30c5d0be 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -143,11 +143,13 @@ class Definitions { private def enterMethod(cls: ClassSymbol, name: TermName, info: Type, flags: FlagSet = EmptyFlags): TermSymbol = newMethod(cls, name, info, flags).entered - private def enterAliasType(name: TypeName, tpe: Type, flags: FlagSet = EmptyFlags): TypeSymbol = { - val sym = newPermanentSymbol(ScalaPackageClass, name, flags, TypeAlias(tpe)) + private def enterType(name: TypeName, info: Type, flags: FlagSet = EmptyFlags): TypeSymbol = + val sym = newPermanentSymbol(ScalaPackageClass, name, flags, info) ScalaPackageClass.currentPackageDecls.enter(sym) sym - } + + private def enterAliasType(name: TypeName, tpe: Type, flags: FlagSet = EmptyFlags): TypeSymbol = + enterType(name, TypeAlias(tpe), flags) private def enterBinaryAlias(name: TypeName, op: (Type, Type) => Type): TypeSymbol = enterAliasType(name, @@ -440,6 +442,7 @@ class Definitions { @tu lazy val andType: TypeSymbol = enterBinaryAlias(tpnme.AND, AndType(_, _)) @tu lazy val orType: TypeSymbol = enterBinaryAlias(tpnme.OR, OrType(_, _, soft = false)) + @tu lazy val captureRootType: TypeSymbol = enterType(tpnme.CAPTURE_ROOT, TypeBounds.empty) /** Marker method to indicate an argument to a call-by-name parameter. * Created by byNameClosures and elimByName, eliminated by Erasure, @@ -1747,6 +1750,7 @@ class Definitions { AnyKindClass, andType, orType, + captureRootType, RepeatedParamClass, ByNameParamClass2x, AnyValClass, diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 9f5b8a9a1c05..a752a7e7f5b4 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -124,4 +124,6 @@ object Mode { * This mode forces expansion of inline calls in those positions even during typing. */ val ForceInline: Mode = newMode(29, "ForceInline") + + val RelaxedCapturing: Mode = newMode(30, "RelaxedCapturing") } diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 5c718d4af0da..96fc1e789944 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -275,6 +275,7 @@ object StdNames { // Compiler-internal val ANYname: N = "" + val CAPTURE_ROOT: N = "*" val COMPANION: N = "" val CONSTRUCTOR: N = "" val STATIC_CONSTRUCTOR: N = "" @@ -361,6 +362,7 @@ object StdNames { val AppliedTypeTree: N = "AppliedTypeTree" val ArrayAnnotArg: N = "ArrayAnnotArg" val CAP: N = "CAP" + val ClassManifestFactory: N = "ClassManifestFactory" val Constant: N = "Constant" val ConstantType: N = "ConstantType" val Eql: N = "Eql" @@ -438,7 +440,6 @@ object StdNames { val canEqualAny : N = "canEqualAny" val cbnArg: N = "" val checkInitialized: N = "checkInitialized" - val ClassManifestFactory: N = "ClassManifestFactory" val classOf: N = "classOf" val clone_ : N = "clone" val common: N = "common" @@ -496,6 +497,7 @@ object StdNames { val hash_ : N = "hash" val head: N = "head" val higherKinds: N = "higherKinds" + val holds: N = "holds" val identity: N = "identity" val implicitConversions: N = "implicitConversions" val implicitly: N = "implicitly" diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9256b2f084a8..f6ec9cdc0baa 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -67,11 +67,12 @@ object Types { * | | +--- SkolemType * | +- TypeParamRef * | +- RefinedOrRecType -+-- RefinedType - * | | -+-- RecType + * | | +-- RecType * | +- AppliedType * | +- TypeBounds * | +- ExprType - * | +- AnnotatedType + * | +- AnnotOrCaptType -+-- AnnotatedType + * | | +-- CapturingType * | +- TypeVar * | +- HKTypeLambda * | +- MatchType @@ -173,6 +174,7 @@ object Types { // not on types. Allowing it on types is a Scala 3 extension. See: // https://www.scala-lang.org/files/archive/spec/2.11/11-annotations.html#scala-compiler-annotations tp.annot.symbol == defn.UncheckedStableAnnot || tp.parent.isStable + case tp: CapturingType => tp.parent.isStable case tp: AndType => // TODO: fix And type check when tp contains type parames for explicit-nulls flow-typing // see: tests/explicit-nulls/pos/flow-stable.scala.disabled @@ -187,18 +189,24 @@ object Types { * It makes no sense for it to be an alias type because isRef would always * return false in that case. */ - def isRef(sym: Symbol, skipRefined: Boolean = true)(using Context): Boolean = stripped match { + def isRef(sym: Symbol, skipRefined: Boolean = true, skipCapturing: Boolean = false)(using Context): Boolean = this match { case this1: TypeRef => this1.info match { // see comment in Namer#typeDefSig - case TypeAlias(tp) => tp.isRef(sym, skipRefined) + case TypeAlias(tp) => tp.isRef(sym, skipRefined, skipCapturing) case _ => this1.symbol eq sym } case this1: RefinedOrRecType if skipRefined => - this1.parent.isRef(sym, skipRefined) + this1.parent.isRef(sym, skipRefined, skipCapturing) case this1: AppliedType => val this2 = this1.dealias - if (this2 ne this1) this2.isRef(sym, skipRefined) - else this1.underlying.isRef(sym, skipRefined) + if (this2 ne this1) this2.isRef(sym, skipRefined, skipCapturing) + else this1.underlying.isRef(sym, skipRefined, skipCapturing) + case this1: TypeVar => + this1.instanceOpt.isRef(sym, skipRefined, skipCapturing) + case this1: AnnotatedType => + this1.parent.isRef(sym, skipRefined, skipCapturing) + case this1: CapturingType if skipCapturing => + this1.parent.isRef(sym, skipRefined, skipCapturing) case _ => false } @@ -365,6 +373,7 @@ object Types { case tp: AndOrType => tp.tp1.unusableForInference || tp.tp2.unusableForInference case tp: LambdaType => tp.resultType.unusableForInference || tp.paramInfos.exists(_.unusableForInference) case WildcardType(optBounds) => optBounds.unusableForInference + case CapturingType(parent, ref) => parent.unusableForInference || ref.unusableForInference case _: ErrorType => true case _ => false @@ -1177,7 +1186,7 @@ object Types { */ def stripAnnots(using Context): Type = this - /** Strip TypeVars and Annotation wrappers */ + /** Strip TypeVars and Annotation and CapturingType wrappers */ def stripped(using Context): Type = this def rewrapAnnots(tp: Type)(using Context): Type = tp.stripTypeVar match { @@ -1375,6 +1384,8 @@ object Types { case tp: AnnotatedType => val tp1 = tp.parent.dealias1(keep) if keep(tp) then tp.derivedAnnotatedType(tp1, tp.annot) else tp1 + case tp: CapturingType => + tp.derivedCapturingType(tp.parent.dealias1(keep), tp.ref) case tp: LazyRef => tp.ref.dealias1(keep) case _ => this @@ -1455,8 +1466,8 @@ object Types { case tp: AppliedType => if (tp.tycon.isLambdaSub) NoType else tp.superType.underlyingClassRef(refinementOK) - case tp: AnnotatedType => - tp.underlying.underlyingClassRef(refinementOK) + case tp: AnnotOrCaptType => + tp.parent.underlyingClassRef(refinementOK) case tp: RefinedType => if (refinementOK) tp.underlying.underlyingClassRef(refinementOK) else NoType case tp: RecType => @@ -1499,6 +1510,10 @@ object Types { case _ => if (isRepeatedParam) this.argTypesHi.head else this } + def captureSet(using Context): CaptureSet = CaptureSet.ofType(this) + def noCaptures(using Context): Boolean = + ctx.mode.is(Mode.RelaxedCapturing) || captureSet.isEmpty + // ----- Normalizing typerefs over refined types ---------------------------- /** If this normalizes* to a refinement type that has a refinement for `name` (which might be followed @@ -1822,6 +1837,12 @@ object Types { case _ => this } + def capturing(ref: CaptureRef)(using Context): Type = + if captureSet.accountsFor(ref) then this else CapturingType(this, ref) + + def capturing(cs: CaptureSet)(using Context): Type = + (this /: cs.elems)(_.capturing(_)) + /** The set of distinct symbols referred to by this type, after all aliases are expanded */ def coveringSet(using Context): Set[Symbol] = (new CoveringSetAccumulator).apply(Set.empty[Symbol], this) @@ -2002,6 +2023,36 @@ object Types { def isOverloaded(using Context): Boolean = false } + /** A trait for references in CaptureSets. These can be NamedTypes or ParamRefs */ + trait CaptureRef extends TypeProxy, ValueType: + private var myCaptureSet: CaptureSet = _ + private var myCaptureSetRunId: Int = NoRunId + private val singletonCaptureSet = CaptureSet(this) + + def isRootCapability(using Context): Boolean + def canBeTracked(using Context): Boolean + def isTracked(using Context): Boolean = canBeTracked && captureSetOfInfo.nonEmpty + + def captureSetOfInfo(using Context): CaptureSet = + if ctx.runId == myCaptureSetRunId then myCaptureSet + else if myCaptureSet eq CaptureSet.Pending then CaptureSet.empty + else + myCaptureSet = CaptureSet.Pending + val computed = + if isRootCapability then singletonCaptureSet + else CaptureSet.ofType(underlying) + if underlying.isProvisional then + myCaptureSet = null + else + myCaptureSet = computed + myCaptureSetRunId = ctx.runId + computed + + override def captureSet(using Context): CaptureSet = + val cs = captureSetOfInfo + if canBeTracked && cs.nonEmpty then singletonCaptureSet else cs + end CaptureRef + /** A trait for types that bind other types that refer to them. * Instances are: LambdaType, RecType. */ @@ -2049,7 +2100,7 @@ object Types { // --- NamedTypes ------------------------------------------------------------------ - abstract class NamedType extends CachedProxyType with ValueType { self => + abstract class NamedType extends CachedProxyType, CaptureRef { self => type ThisType >: this.type <: NamedType type ThisName <: Name @@ -2068,6 +2119,9 @@ object Types { private var mySignature: Signature = _ private var mySignatureRunId: Int = NoRunId + private var myCaptureSet: CaptureSet = _ + private var myCaptureSetRunId: Int = NoRunId + // Invariants: // (1) checkedPeriod != Nowhere => lastDenotation != null // (2) lastDenotation != null => lastSymbol != null @@ -2323,6 +2377,19 @@ object Types { checkDenot() } + /** A reference can be tracked if it is + * (1) local, + * (2) a type parameter, + * (3) a method term parameter + * References to term parameters of classes cannot be tracked individually. + * They are subsumed in the capture sets of the enclosing class. + */ + def canBeTracked(using Context) = + (prefix eq NoPrefix) || symbol.is(TypeParam) || isRootCapability + + def isRootCapability(using Context): Boolean = + name == tpnme.CAPTURE_ROOT && symbol == defn.captureRootType + private def checkDenot()(using Context) = {} private def checkSymAssign(sym: Symbol)(using Context) = { @@ -4253,7 +4320,7 @@ object Types { override def hashIsStable: Boolean = false } - abstract class ParamRef extends BoundType { + abstract class ParamRef extends BoundType, CaptureRef { type BT <: LambdaType def paramNum: Int def paramName: binder.ThisName = binder.paramNames(paramNum) @@ -4264,6 +4331,9 @@ object Types { else infos(paramNum) } + override def canBeTracked(using Context) = true + override def isRootCapability(using Context): Boolean = false + override def computeHash(bs: Binders): Int = doHash(paramNum, binder.identityHash(bs)) override def equals(that: Any): Boolean = equals(that, null) @@ -4993,8 +5063,12 @@ object Types { // ----- Annotated and Import types ----------------------------------------------- + abstract class AnnotOrCaptType extends CachedProxyType with ValueType: + def parent: Type + override def stripped(using Context): Type = parent.stripped + /** An annotated type tpe @ annot */ - abstract case class AnnotatedType(parent: Type, annot: Annotation) extends CachedProxyType with ValueType { + abstract case class AnnotatedType(parent: Type, annot: Annotation) extends AnnotOrCaptType { override def underlying(using Context): Type = parent @@ -5007,8 +5081,6 @@ object Types { override def stripAnnots(using Context): Type = parent.stripAnnots - override def stripped(using Context): Type = parent.stripped - private var isRefiningKnown = false private var isRefiningCache: Boolean = _ @@ -5043,6 +5115,42 @@ object Types { annots.foldLeft(underlying)(apply(_, _)) def apply(parent: Type, annot: Annotation)(using Context): AnnotatedType = unique(CachedAnnotatedType(parent, annot)) + end AnnotatedType + + abstract case class CapturingType(parent: Type, ref: CaptureRef) extends AnnotOrCaptType: + override def underlying(using Context): Type = parent + + def derivedCapturingType(parent: Type, ref: CaptureRef)(using Context): CapturingType = + if (parent eq this.parent) && (ref eq this.ref) then this + else CapturingType(parent, ref) + + def derivedCapturing(parent: Type, capt: Type)(using Context): Type = + if (parent eq this.parent) && (capt eq this.ref) then this + else parent.capturing(capt.captureSet) + + // equals comes from case class; no matching override is needed + + override def computeHash(bs: Binders): Int = + doHash(bs, parent, ref) + override def hashIsStable: Boolean = + parent.hashIsStable && ref.hashIsStable + + override def eql(that: Type): Boolean = that match + case that: CapturingType => (parent eq that.parent) && (ref eq that.ref) + case _ => false + + override def iso(that: Any, bs: BinderPairs): Boolean = that match + case that: CapturingType => parent.equals(that.parent, bs) && ref.equals(that.ref, bs) + case _ => false + + class CachedCapturingType(parent: Type, ref: CaptureRef) extends CapturingType(parent, ref) + + object CapturingType: + def apply(parent: Type, ref: CaptureRef)(using Context): CapturingType = + unique(CachedCapturingType(parent, ref)) + def checked(parent: Type, ref: Type)(using Context): CapturingType = ref match + case ref: CaptureRef => apply(parent, ref) + end CapturingType // Special type objects and classes ----------------------------------------------------- @@ -5180,7 +5288,7 @@ object Types { zeroParamClass(tp.underlying) case tp: TypeVar => zeroParamClass(tp.underlying) - case tp: AnnotatedType => + case tp: AnnotOrCaptType => zeroParamClass(tp.underlying) case _ => NoType @@ -5305,6 +5413,8 @@ object Types { tp.derivedMatchType(bound, scrutinee, cases) protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation): Type = tp.derivedAnnotatedType(underlying, annot) + protected def derivedCapturing(tp: CapturingType, parent: Type, capt: Type): Type = + tp.derivedCapturing(parent, capt) protected def derivedWildcardType(tp: WildcardType, bounds: Type): Type = tp.derivedWildcardType(bounds) protected def derivedSkolemType(tp: SkolemType, info: Type): Type = @@ -5384,6 +5494,9 @@ object Types { if (underlying1 eq underlying) tp else derivedAnnotatedType(tp, underlying1, mapOver(annot)) + case tp @ CapturingType(parent, ref) => + derivedCapturing(tp, this(parent), atVariance(0)(this(ref))) + case _: ThisType | _: BoundType | NoPrefix => @@ -5703,6 +5816,16 @@ object Types { if (underlying.isExactlyNothing) underlying else tp.derivedAnnotatedType(underlying, annot) } + override protected def derivedCapturing(tp: CapturingType, parent: Type, capt: Type): Type = + capt match + case Range(lo, hi) => + range(derivedCapturing(tp, parent, hi), derivedCapturing(tp, parent, lo)) + case _ => parent match + case Range(lo, hi) => + range(derivedCapturing(tp, lo, capt), derivedCapturing(tp, hi, capt)) + case _ => + tp.derivedCapturing(parent, capt) + override protected def derivedWildcardType(tp: WildcardType, bounds: Type): WildcardType = tp.derivedWildcardType(rangeToBounds(bounds)) @@ -5842,6 +5965,9 @@ object Types { case AnnotatedType(underlying, annot) => this(applyToAnnot(x, annot), underlying) + case CapturingType(parent, ref) => + this(this(x, parent), ref) + case tp: ProtoType => tp.fold(x, this) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 45ba09f9d82d..9b8510539f0f 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -187,6 +187,8 @@ class PlainPrinter(_ctx: Context) extends Printer { keywordStr(" match ") ~ "{" ~ casesText ~ "}" ~ (" <: " ~ toText(bound) provided !bound.isAny) }.close + case CapturingType(parent, ref) => + changePrec(InfixPrec)(toText(parent) ~ " holds " ~ toTextCaptureRef(ref)) case tp: PreviousErrorType if ctx.settings.XprintTypes.value => "" // do not print previously reported error message because they may try to print this error type again recuresevely case tp: ErrorType => @@ -335,6 +337,11 @@ class PlainPrinter(_ctx: Context) extends Printer { } } + def toTextCaptureRef(tp: Type): Text = + homogenize(tp) match + case tp: SingletonType => toTextRef(tp) + case _ => toText(tp) + protected def isOmittablePrefix(sym: Symbol): Boolean = defn.unqualifiedOwnerTypes.exists(_.symbol == sym) || isEmptyPrefix(sym) diff --git a/compiler/src/dotty/tools/dotc/printing/Printer.scala b/compiler/src/dotty/tools/dotc/printing/Printer.scala index 8584c889eeda..d1b409e7d45d 100644 --- a/compiler/src/dotty/tools/dotc/printing/Printer.scala +++ b/compiler/src/dotty/tools/dotc/printing/Printer.scala @@ -103,6 +103,9 @@ abstract class Printer { /** Textual representation of a prefix of some reference, ending in `.` or `#` */ def toTextPrefix(tp: Type): Text + /** Textual representation of a reference in a capture set */ + def toTextCaptureRef(tp: Type): Text + /** Textual representation of symbol's declaration */ def dclText(sym: Symbol): Text From 7ae43f3ccc88d13a22de9aa9c2895c0ae497c438 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 17:44:53 +0200 Subject: [PATCH 30/87] Pickling and unpickling of capturing types --- .../dotty/tools/dotc/core/CaptureSet.scala | 103 ++++++++++++++++++ .../dotty/tools/dotc/core/Definitions.scala | 1 + .../tools/dotc/core/tasty/TreePickler.scala | 7 ++ .../tools/dotc/core/tasty/TreeUnpickler.scala | 18 +-- .../dotty/tools/dotc/typer/TypeAssigner.scala | 37 ++++++- .../scala/runtime/stdLibPatches/Predef.scala | 3 + 6 files changed, 157 insertions(+), 12 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/core/CaptureSet.scala diff --git a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala new file mode 100644 index 000000000000..36e7d781af3c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala @@ -0,0 +1,103 @@ +package dotty.tools +package dotc +package core + +import util.* +import Types.*, Symbols.*, Flags.*, Contexts.*, Decorators.* +import config.Printers.capt +import annotation.threadUnsafe +import annotation.internal.sharable +import reporting.trace +import printing.{Showable, Printer} +import printing.Texts.* + +case class CaptureSet(elems: CaptureSet.Refs) extends Showable: + import CaptureSet.* + + def isEmpty: Boolean = elems.isEmpty + def nonEmpty: Boolean = !isEmpty + + private var myClosure: Refs | Null = null + + def closure(using Context): Refs = + if myClosure == null then + var cl = elems + var seen: Refs = SimpleIdentitySet.empty + while + val prev = cl + for ref <- cl do + if !seen.contains(ref) then + seen += ref + cl = cl ++ ref.captureSetOfInfo.elems + prev ne cl + do () + myClosure = cl + myClosure + + /** {x} <:< this where <:< is subcapturing */ + def accountsFor(x: CaptureRef)(using Context) = + elems.contains(x) || !x.isRootCapability && x.captureSetOfInfo <:< this + + /** The subcapturing test */ + def <:< (that: CaptureSet)(using Context): Boolean = + elems.isEmpty || elems.forall(that.accountsFor) + + override def toString = elems.toString + + override def toText(printer: Printer): Text = + Str("{") ~ Text(elems.toList.map(printer.toTextCaptureRef), ", ") ~ Str("}") + +object CaptureSet: + type Refs = SimpleIdentitySet[CaptureRef] + + @sharable val empty: CaptureSet = CaptureSet(SimpleIdentitySet.empty) + + /** Used as a recursion brake */ + @sharable private[core] val Pending = CaptureSet(SimpleIdentitySet.empty) + + def apply(elems: CaptureRef*): CaptureSet = + if elems.isEmpty then empty + else CaptureSet(SimpleIdentitySet(elems*)) + + def ofType(tp: Type)(using Context): CaptureSet = + val collect = new TypeAccumulator[Refs]: + var localBinders: SimpleIdentitySet[BindingType] = SimpleIdentitySet.empty + var seenLazyRefs: SimpleIdentitySet[LazyRef] = SimpleIdentitySet.empty + def apply(elems: Refs, tp: Type): Refs = trace(i"capt $elems, $tp", show = true) { + tp match + case tp: NamedType => + if variance < 0 then elems + else elems ++ tp.captureSet.elems + case tp: ParamRef => + if variance < 0 || localBinders.contains(tp.binder) then elems + else elems ++ tp.captureSet.elems + case tp: LambdaType => + localBinders += tp + try apply(elems, tp.resultType) + finally localBinders -= tp + case AndType(tp1, tp2) => + val elems1 = apply(SimpleIdentitySet.empty, tp1) + val elems2 = apply(SimpleIdentitySet.empty, tp2) + elems ++ elems1.intersect(elems2) + case CapturingType(parent, ref) => + val elems1 = apply(elems, parent) + if variance >= 0 then elems1 + ref else elems1 + case TypeBounds(_, hi) => + apply(elems, hi) + case tp: LazyRef => + if seenLazyRefs.contains(tp) + || tp.evaluating // shapeless gets an assertion error without this test + then elems + else + seenLazyRefs += tp + foldOver(elems, tp) +// case tp: MatchType => +// val normed = tp.tryNormalize +// if normed.exists then apply(elems, normed) else foldOver(elems, tp) + case _ => + foldOver(elems, tp) + } + + CaptureSet(collect(empty.elems, tp)) + .showing(i"capture set of $tp = $result", capt) + diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 366d30c5d0be..7ed9f2ce3ef2 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -473,6 +473,7 @@ class Definitions { @tu lazy val Predef_classOf : Symbol = ScalaPredefModule.requiredMethod(nme.classOf) @tu lazy val Predef_identity : Symbol = ScalaPredefModule.requiredMethod(nme.identity) @tu lazy val Predef_undefined: Symbol = ScalaPredefModule.requiredMethod(nme.???) + @tu lazy val Predef_holdsType: Symbol = ScalaPredefModule.requiredType(tpnme.holds) @tu lazy val ScalaPredefModuleClass: ClassSymbol = ScalaPredefModule.moduleClass.asClass @tu lazy val SubTypeClass: ClassSymbol = requiredClass("scala.<:<") diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 825df846ae0e..9596ba1d25e8 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -288,6 +288,13 @@ class TreePickler(pickler: TastyPickler) { pickleType(tpe.scrutinee) tpe.cases.foreach(pickleType(_)) } + case tp: CapturingType => + writeByte(APPLIEDtype) + withLength { + pickleType(defn.Predef_holdsType.typeRef) + pickleType(tp.parent) + pickleType(tp.ref) + } case tpe: PolyType if richTypes => pickleMethodic(POLYtype, tpe, EmptyFlags) case tpe: MethodType if richTypes => diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index c8220d7e7604..94c471451428 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -357,7 +357,13 @@ class TreeUnpickler(reader: TastyReader, // Note that the lambda "rt => ..." is not equivalent to a wildcard closure! // Eta expansion of the latter puts readType() out of the expression. case APPLIEDtype => - readType().appliedTo(until(end)(readType())) + val tycon = readType() + val args = until(end)(readType()) + tycon match + case tycon: TypeRef if tycon.symbol == defn.Predef_holdsType => + CapturingType.checked(args(0), args(1)) + case _ => + tycon.appliedTo(args) case TYPEBOUNDS => val lo = readType() if nothingButMods(end) then @@ -821,7 +827,7 @@ class TreeUnpickler(reader: TastyReader, def TypeDef(rhs: Tree) = ta.assignType(untpd.TypeDef(sym.name.asTypeName, rhs), sym) - def ta = ctx.typeAssigner + def ta = ctx.typeAssigner val name = readName() pickling.println(s"reading def of $name at $start") @@ -1256,11 +1262,9 @@ class TreeUnpickler(reader: TastyReader, // types. This came up in #137 of collection strawman. val tycon = readTpt() val args = until(end)(readTpt()) - val ownType = - if (tycon.symbol == defn.andType) AndType(args(0).tpe, args(1).tpe) - else if (tycon.symbol == defn.orType) OrType(args(0).tpe, args(1).tpe, soft = false) - else tycon.tpe.safeAppliedTo(args.tpes) - untpd.AppliedTypeTree(tycon, args).withType(ownType) + val tree = untpd.AppliedTypeTree(tycon, args) + val ownType = ctx.typeAssigner.processAppliedType(tree, tycon.tpe.safeAppliedTo(args.tpes)) + tree.withType(ownType) case ANNOTATEDtpt => Annotated(readTpt(), readTerm()) case LAMBDAtpt => diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index ea702e47e673..daf34adbf204 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -185,6 +185,34 @@ trait TypeAssigner { if tpe.isError then tpe else errorType(ex"$whatCanNot be accessed as a member of $pre$where.$whyNot", pos) + def processAppliedType(tree: untpd.Tree, tp: Type)(using Context): Type = + def captType(tp: Type, refs: Type): Type = refs match + case ref: NamedType => + if ref.isTracked then + if tp.captureSet.accountsFor(ref) then + report.warning(em"redundant capture: $tp already contains $ref with cs ${ref.captureSet} in its capture set ${tp.captureSet}", tree.srcPos) + CapturingType(tp, ref) + else + val reason = + if ref.canBeTracked then "its capture set is empty" + else "it is not a parameter or a local variable" + report.error(em"$ref cannot be tracked since $reason", tree.srcPos) + tp + case OrType(refs1, refs2) => + captType(captType(tp, refs1), refs2) + case _ => + report.error(em"$refs is not a legal type for a capture set", tree.srcPos) + tp + tp match + case AppliedType(tycon, args) => + val constr = tycon.typeSymbol + if constr == defn.andType then AndType(args(0), args(1)) + else if constr == defn.orType then OrType(args(0), args(1), soft = false) + else if constr == defn.Predef_holdsType then captType(args(0), args(1)) + else tp + case _ => tp + end processAppliedType + /** Type assignment method. Each method takes as parameters * - an untpd.Tree to which it assigns a type, * - typed child trees it needs to access to cpmpute that type, @@ -449,11 +477,10 @@ trait TypeAssigner { assert(!hasNamedArg(args) || ctx.reporter.errorsReported, tree) val tparams = tycon.tpe.typeParams val ownType = - if (sameLength(tparams, args)) - if (tycon.symbol == defn.andType) AndType(args(0).tpe, args(1).tpe) - else if (tycon.symbol == defn.orType) OrType(args(0).tpe, args(1).tpe, soft = false) - else tycon.tpe.appliedTo(args.tpes) - else wrongNumberOfTypeArgs(tycon.tpe, tparams, args, tree.srcPos) + if !sameLength(tparams, args) then + wrongNumberOfTypeArgs(tycon.tpe, tparams, args, tree.srcPos) + else + processAppliedType(tree, tycon.tpe.appliedTo(args.tpes)) tree.withType(ownType) } diff --git a/library/src/scala/runtime/stdLibPatches/Predef.scala b/library/src/scala/runtime/stdLibPatches/Predef.scala index 13dfc77ac60b..ecabcf9960ba 100644 --- a/library/src/scala/runtime/stdLibPatches/Predef.scala +++ b/library/src/scala/runtime/stdLibPatches/Predef.scala @@ -47,4 +47,7 @@ object Predef: */ extension [T](x: T | Null) inline def nn: x.type & T = scala.runtime.Scala3RunTime.nn(x) + + /** type `A` with capture set `B` */ + infix type holds[A, B] end Predef From 758d7cd818c93126f615e720c697bb390305d795 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 17:53:19 +0200 Subject: [PATCH 31/87] Add support for CapturingTypes elsewhere --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 8 +------- .../src/dotty/tools/dotc/core/OrderingConstraint.scala | 3 +++ .../src/dotty/tools/dotc/core/SymDenotations.scala | 8 +++++--- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 10 +++++++++- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 3 +++ compiler/src/dotty/tools/dotc/core/Variances.scala | 2 ++ compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala | 4 ++++ .../dotty/tools/dotc/transform/GenericSignatures.scala | 10 ++++------ .../dotty/tools/dotc/transform/TryCatchPatterns.scala | 2 +- .../dotty/tools/dotc/transform/TypeTestsCasts.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Checking.scala | 1 + compiler/src/dotty/tools/dotc/typer/Inferencing.scala | 5 +++-- 12 files changed, 38 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 5bb3b4ccbcc9..08ad47c5359d 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -261,16 +261,10 @@ object Trees { /** Tree's denotation can be derived from its type */ abstract class DenotingTree[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends Tree[T] { type ThisTree[-T >: Untyped] <: DenotingTree[T] - override def denot(using Context): Denotation = typeOpt match { + override def denot(using Context): Denotation = typeOpt.stripped match case tpe: NamedType => tpe.denot case tpe: ThisType => tpe.cls.denot - case tpe: AnnotatedType => tpe.stripAnnots match { - case tpe: NamedType => tpe.denot - case tpe: ThisType => tpe.cls.denot - case _ => NoDenotation - } case _ => NoDenotation - } } /** Tree's denot/isType/isTerm properties come from a subtree diff --git a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala index 89bf84d1ed03..4302a9f54fb8 100644 --- a/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -329,6 +329,9 @@ class OrderingConstraint(private val boundsMap: ParamBounds, case tp: AnnotatedType => val parent1 = recur(tp.parent, fromBelow) if parent1 ne tp.parent then tp.derivedAnnotatedType(parent1, tp.annot) else tp + case tp: CapturingType => + val parent1 = recur(tp.parent, fromBelow) + if parent1 ne tp.parent then tp.derivedCapturingType(parent1, tp.ref) else tp case _ => val tp1 = tp.dealiasKeepAnnots if tp1 ne tp then diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index ba094c587038..7c1aee33ca43 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1497,9 +1497,8 @@ object SymDenotations { case tp: ExprType => hasSkolems(tp.resType) case tp: AppliedType => hasSkolems(tp.tycon) || tp.args.exists(hasSkolems) case tp: LambdaType => tp.paramInfos.exists(hasSkolems) || hasSkolems(tp.resType) - case tp: AndType => hasSkolems(tp.tp1) || hasSkolems(tp.tp2) - case tp: OrType => hasSkolems(tp.tp1) || hasSkolems(tp.tp2) - case tp: AnnotatedType => hasSkolems(tp.parent) + case tp: AndOrType => hasSkolems(tp.tp1) || hasSkolems(tp.tp2) + case tp: AnnotOrCaptType => hasSkolems(tp.parent) case _ => false } @@ -2151,6 +2150,9 @@ object SymDenotations { case tp: TypeParamRef => // uncachable, since baseType depends on context bounds recur(TypeComparer.bounds(tp).hi) + case tp: CapturingType => + tp.derivedCapturingType(recur(tp.parent), tp.ref) + case tp: TypeProxy => def computeTypeProxy = { val superTp = tp.superType diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index c536fae0cec8..2daaba37ac0b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -489,7 +489,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // and then need to check that they are indeed supertypes of the original types // under -Ycheck. Test case is i7965.scala. - case tp1: MatchType => + case tp1: CapturingType => + if tp2.captureSet.accountsFor(tp1.ref) then recur(tp1.parent, tp2) + else thirdTry + case tp1: MatchType => val reduced = tp1.reduced if (reduced.exists) recur(reduced, tp2) else thirdTry case _: FlexType => @@ -737,6 +740,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case tp2: AnnotatedType if tp2.isRefining => (tp1.derivesAnnotWith(tp2.annot.sameAnnotation) || tp1.isBottomType) && recur(tp1, tp2.parent) + case tp2: CapturingType => + recur(tp1, tp2.parent) || fourthTry case ClassInfo(pre2, cls2, _, _, _) => def compareClassInfo = tp1 match { case ClassInfo(pre1, cls1, _, _, _) => @@ -778,6 +783,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case tp: AppliedType => isNullable(tp.tycon) case AndType(tp1, tp2) => isNullable(tp1) && isNullable(tp2) case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2) + case CapturingType(tp1, _) => isNullable(tp1) case _ => false } val sym1 = tp1.symbol @@ -2336,6 +2342,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling tp1.underlying & tp2 case tp1: AnnotatedType if !tp1.isRefining => tp1.underlying & tp2 + case tp1: CapturingType if !tp2.captureSet.accountsFor(tp1.ref) => + tp1.parent & tp2 case _ => NoType } diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 98b8b6ee51d4..39aacf8aacab 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -168,6 +168,9 @@ object TypeOps: case _: MatchType => val normed = tp.tryNormalize if (normed.exists) normed else mapOver + case tp: CapturingType + if !ctx.mode.is(Mode.Type) && tp.parent.captureSet.accountsFor(tp.ref) => + simplify(tp.parent, theMap) case tp: MethodicType => tp // See documentation of `Types#simplified` case _ => diff --git a/compiler/src/dotty/tools/dotc/core/Variances.scala b/compiler/src/dotty/tools/dotc/core/Variances.scala index 122c7a10e4b7..5dbee234adca 100644 --- a/compiler/src/dotty/tools/dotc/core/Variances.scala +++ b/compiler/src/dotty/tools/dotc/core/Variances.scala @@ -101,6 +101,8 @@ object Variances { varianceInArgs(varianceInType(tycon)(tparam), args, tycon.typeParams) case AnnotatedType(tp, annot) => varianceInType(tp)(tparam) & varianceInAnnot(annot)(tparam) + case CapturingType(tp, _) => + varianceInType(tp)(tparam) case AndType(tp1, tp2) => varianceInType(tp1)(tparam) & varianceInType(tp2)(tparam) case OrType(tp1, tp2) => diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 6e198bbeada9..74c48e4ace8d 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -175,6 +175,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { private val byNameMarker = marker("ByName") private val matchMarker = marker("Match") private val superMarker = marker("Super") + private val holdsMarker = marker("Holds") /** Extract the API representation of a source file */ def apiSource(tree: Tree): Seq[api.ClassLike] = { @@ -520,6 +521,9 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { case SuperType(thistpe, supertpe) => val s = combineApiTypes(apiType(thistpe), apiType(supertpe)) withMarker(s, superMarker) + case CapturingType(parent, ref) => + val s = combineApiTypes(apiType(parent), apiType(ref)) + withMarker(s, holdsMarker) case _ => { internalError(i"Unhandled type $tp of class ${tp.getClass}") Constants.emptyType diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index 9024296eae89..a5acc707831c 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -347,8 +347,8 @@ object GenericSignatures { if (toplevel) polyParamSig(tParams) superSig(ci.typeSymbol, ci.parents) - case AnnotatedType(atp, _) => - jsig(atp, toplevel, primitiveOK) + case tp: AnnotOrCaptType => + jsig(tp.parent, toplevel, primitiveOK) case hktl: HKTypeLambda => jsig(hktl.finalResultType, toplevel, primitiveOK) @@ -469,10 +469,8 @@ object GenericSignatures { true case ClassInfo(_, _, parents, _, _) => foldOver(tp.typeParams.nonEmpty, parents) - case AnnotatedType(tpe, _) => - foldOver(x, tpe) - case proxy: TypeProxy => - foldOver(x, proxy) + case tp: AnnotOrCaptType => + foldOver(x, tp.parent) case _ => foldOver(x, tp) } diff --git a/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala b/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala index 6be58352e6dc..26bea001d1eb 100644 --- a/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala +++ b/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala @@ -70,7 +70,7 @@ class TryCatchPatterns extends MiniPhase { case _ => isDefaultCase(cdef) } - private def isSimpleThrowable(tp: Type)(using Context): Boolean = tp.stripAnnots match { + private def isSimpleThrowable(tp: Type)(using Context): Boolean = tp.stripped match { case tp @ TypeRef(pre, _) => (pre == NoPrefix || pre.typeSymbol.isStatic) && // Does not require outer class check !tp.symbol.is(Flags.Trait) && // Traits not supported by JVM diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 5d03d5381eed..60aff5f85307 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -148,7 +148,7 @@ object TypeTestsCasts { } case AndType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) - case AnnotatedType(t, _) => recur(X, t) + case tp: AnnotOrCaptType => recur(X, tp.parent) case _: RefinedType => false case _ => true }) @@ -217,7 +217,7 @@ object TypeTestsCasts { * can be true in some cases. Issues a warning or an error otherwise. */ def checkSensical(foundClasses: List[Symbol])(using Context): Boolean = - def exprType = i"type ${expr.tpe.widen.stripAnnots}" + def exprType = i"type ${expr.tpe.widen.stripped}" def check(foundCls: Symbol): Boolean = if (!isCheckable(foundCls)) true else if (!foundCls.derivesFrom(testCls)) { diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 6abc4ccfd090..4beb0082dc4f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -284,6 +284,7 @@ object Checking { case AndType(tp1, tp2) => isInteresting(tp1) || isInteresting(tp2) case OrType(tp1, tp2) => isInteresting(tp1) && isInteresting(tp2) case _: RefinedOrRecType | _: AppliedType => true + case tp: AnnotOrCaptType => isInteresting(tp.parent) case _ => false } diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index d608ede93a22..93466b536f64 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -126,8 +126,8 @@ object Inferencing { couldInstantiateTypeVar(parent) case tp: AndOrType => couldInstantiateTypeVar(tp.tp1) || couldInstantiateTypeVar(tp.tp2) - case AnnotatedType(tp, _) => - couldInstantiateTypeVar(tp) + case tp: AnnotOrCaptType => + couldInstantiateTypeVar(tp.parent) case _ => false @@ -525,6 +525,7 @@ object Inferencing { case tp: RecType => tp.derivedRecType(captureWildcards(tp.parent)) case tp: LazyRef => captureWildcards(tp.ref) case tp: AnnotatedType => tp.derivedAnnotatedType(captureWildcards(tp.parent), tp.annot) + case tp: CapturingType => tp.derivedCapturingType(captureWildcards(tp.parent), tp.ref) case _ => tp } } From 789b04e895d5fcc704437c4c63dc29edd62da987 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 17:57:44 +0200 Subject: [PATCH 32/87] Add TopType --- .../src/dotty/tools/dotc/core/Definitions.scala | 1 + .../src/dotty/tools/dotc/core/TypeComparer.scala | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 7ed9f2ce3ef2..a46dfd937927 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -264,6 +264,7 @@ class Definitions { */ @tu lazy val AnyClass: ClassSymbol = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Any, Abstract, Nil), ensureCtor = false) def AnyType: TypeRef = AnyClass.typeRef + @tu lazy val TopType: Type = CapturingType(AnyType, captureRootType.typeRef) @tu lazy val MatchableClass: ClassSymbol = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Matchable, Trait, AnyType :: Nil), ensureCtor = false) def MatchableType: TypeRef = MatchableClass.typeRef @tu lazy val AnyValClass: ClassSymbol = diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 2daaba37ac0b..6ac206b091ea 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -530,8 +530,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // Note: We would like to replace this by `if (tp1.hasHigherKind)` // but right now we cannot since some parts of the standard library rely on the // idiom that e.g. `List <: Any`. We have to bootstrap without scalac first. - if (cls2 eq AnyClass) return true - if (cls2 == defn.SingletonClass && tp1.isStable) return true + if (cls2 eq AnyClass) && tp1.noCaptures then return true + if cls2 == defn.SingletonClass && tp1.isStable then return true return tryBaseType(cls2) } else if (cls2.is(JavaDefined)) { @@ -730,7 +730,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def compareTypeBounds = tp1 match { case tp1 @ TypeBounds(lo1, hi1) => ((lo2 eq NothingType) || isSubType(lo2, lo1)) && - ((hi2 eq AnyType) && !hi1.isLambdaSub || (hi2 eq AnyKindType) || isSubType(hi1, hi2)) + ((hi2 eq AnyType) && !hi1.isLambdaSub && hi1.noCaptures + || (hi2 eq AnyKindType) || isSubType(hi1, hi2)) case tp1: ClassInfo => tp2 contains tp1 case _ => @@ -773,7 +774,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling tp1.symbol.onGadtBounds(gbounds1 => isSubTypeWhenFrozen(gbounds1.hi, tp2) || narrowGADTBounds(tp1, tp2, approx, isUpper = true)) - && (tp2.isAny || GADTusage(tp1.symbol)) + && (tp2.isAny && tp1.noCaptures || GADTusage(tp1.symbol)) isSubType(hi1, tp2, approx.addLow) || compareGADT || tryLiftedToThis1 case _ => @@ -2021,8 +2022,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if (tp1 eq tp2) tp1 else if (!tp1.exists) tp2 else if (!tp2.exists) tp1 - else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || isBottom(tp2) then tp2 - else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || isBottom(tp1) then tp1 + else if tp1.isAny && !tp2.isLambdaSub && tp2.noCaptures || tp1.isAnyKind || isBottom(tp2) then tp2 + else if tp2.isAny && !tp1.isLambdaSub && tp1.noCaptures || tp2.isAnyKind || isBottom(tp1) then tp1 else tp2 match case tp2: LazyRef => glb(tp1, tp2.ref) @@ -2744,7 +2745,7 @@ object TypeComparer { /** The greatest lower bound of a list types */ def glb(tps: List[Type])(using Context): Type = - tps.foldLeft(defn.AnyType: Type)(glb) + tps.foldLeft(defn.TopType: Type)(glb) def orType(using Context)(tp1: Type, tp2: Type, isSoft: Boolean = true, isErased: Boolean = ctx.erasedTypes): Type = comparing(_.orType(tp1, tp2, isSoft = isSoft, isErased = isErased)) From 9457e540d9a5e548670acb5079a940dabb76c225 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 17:58:11 +0200 Subject: [PATCH 33/87] Don't check capture sets in bounds checks Since almost all types parameterized have Any as upper bound, there are few useful programs we could write. We therefore treat Any as `Any holds *` for bounds checks. This is of course unsound, and should be fixed eventually. --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 4beb0082dc4f..3f4893b93b5e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -70,11 +70,13 @@ object Checking { errorTree(arg, showInferred(MissingTypeParameterInTypeApp(arg.tpe), app, tpt)) } - for (arg, which, bound) <- TypeOps.boundsViolations(args, boundss, instantiate, app) do - report.error( - showInferred(DoesNotConformToBound(arg.tpe, which, bound), - app, tpt), - arg.srcPos.focus) + withMode(Mode.RelaxedCapturing) { + for (arg, which, bound) <- TypeOps.boundsViolations(args, boundss, instantiate, app) do + report.error( + showInferred(DoesNotConformToBound(arg.tpe, which, bound), + app, tpt), + arg.srcPos.focus) + } /** Check that type arguments `args` conform to corresponding bounds in `tl` * Note: This does not check the bounds of AppliedTypeTrees. These From 7b65772cecac981f6edd9b4f9b4244bfe78d5c47 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 5 Jun 2021 18:05:18 +0200 Subject: [PATCH 34/87] Some tests --- .../allow-deep-subtypes}/i9325.scala | 0 tests/neg/cc1.scala | 4 ++++ tests/pos/capturing.scala | 8 ++++++++ 3 files changed, 12 insertions(+) rename tests/{neg => neg-custom-args/allow-deep-subtypes}/i9325.scala (100%) create mode 100644 tests/neg/cc1.scala create mode 100644 tests/pos/capturing.scala diff --git a/tests/neg/i9325.scala b/tests/neg-custom-args/allow-deep-subtypes/i9325.scala similarity index 100% rename from tests/neg/i9325.scala rename to tests/neg-custom-args/allow-deep-subtypes/i9325.scala diff --git a/tests/neg/cc1.scala b/tests/neg/cc1.scala new file mode 100644 index 000000000000..e116c33bab30 --- /dev/null +++ b/tests/neg/cc1.scala @@ -0,0 +1,4 @@ +object Test: + + def f[A <: Any holds *](x: A): Any = x // error + diff --git a/tests/pos/capturing.scala b/tests/pos/capturing.scala new file mode 100644 index 000000000000..693e51952e38 --- /dev/null +++ b/tests/pos/capturing.scala @@ -0,0 +1,8 @@ +object Test: + + extension [A <: Any holds *] (xs: LazyList[A]) + def lazyMap[B <: Any holds *] (f: A => B holds *): LazyList[B] holds f.type | A | B = + val x: Int holds f.type | A = ??? + val y = x + val z: Int holds A holds f.type = y + ??? From 1a6d694560d58a70e502b3946b8d9c6f3ca84590 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Jun 2021 11:47:49 +0200 Subject: [PATCH 35/87] Refactor LambdaLift Break out the dependency collection into a separate class. That makes it more re-usable and makes LambdaLift itself easier to understand. # Conflicts: # compiler/src/dotty/tools/dotc/transform/LambdaLift.scala --- .../dotc/transform/DependencyCollector.scala | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala diff --git a/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala b/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala new file mode 100644 index 000000000000..150d248af6ed --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala @@ -0,0 +1,266 @@ +package dotty.tools.dotc +package transform + +import MegaPhase._ +import core.Denotations.NonSymSingleDenotation +import core.DenotTransformers._ +import core.Symbols._ +import core.Contexts._ +import core.Types._ +import core.Flags._ +import core.Decorators._ +import core.StdNames.nme +import core.Names._ +import core.NameOps._ +import core.NameKinds.ExpandPrefixName +import ast.Trees._ +import SymUtils._ +import ExplicitOuter.outer +import util.Store +import collection.mutable +import collection.mutable.{ HashMap, HashSet, LinkedHashMap, TreeSet } + +abstract class DependencyCollector: + import ast.tpd._ + + def enclosure(using Context): Symbol + def isExpr(sym: Symbol)(using Context): Boolean + + protected type SymSet = TreeSet[Symbol] + + /** A map storing free variables of functions and classes */ + val free: mutable.LinkedHashMap[Symbol, SymSet] = new LinkedHashMap + + /** A hashtable storing calls between functions */ + val called = new LinkedHashMap[Symbol, SymSet] + + /** A map from local methods and classes to the owners to which they will be lifted as members. + * For methods and classes that do not have any dependencies this will be the enclosing package. + * symbols with packages as lifted owners will subsequently represented as static + * members of their toplevel class, unless their enclosing class was already static. + * Note: During tree transform (which runs at phase LambdaLift + 1), liftedOwner + * is also used to decide whether a method had a term owner before. + */ + val liftedOwner = new LinkedHashMap[Symbol, Symbol] + + /** A flag to indicate whether new free variables have been found */ + private var changedFreeVars: Boolean = _ + + /** A flag to indicate whether lifted owners have changed */ + private var changedLiftedOwner: Boolean = _ + + private val ord: Ordering[Symbol] = Ordering.by(_.id) + private def newSymSet = TreeSet.empty[Symbol](ord) + + private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet = + f.getOrElseUpdate(sym, newSymSet) + + def freeVars(sym: Symbol): List[Symbol] = free.get(sym) match + case Some(set) => set.toList + case None => Nil + + /** A symbol is local if it is owned by a term or a local trait, + * or if it is a constructor of a local symbol. + * Note: we count members of local traits as local since their free variables + * have to be passed on from their callers. By contrast, class members get their + * free variable proxies from their enclosing class. + */ + def isLocal(sym: Symbol)(using Context): Boolean = + val owner = sym.maybeOwner + owner.isTerm + || owner.is(Trait) && isLocal(owner) + || sym.isConstructor && isLocal(owner) + + /** Set `liftedOwner(sym)` to `owner` if `owner` is more deeply nested + * than the previous value of `liftedowner(sym)`. + */ + def narrowLiftedOwner(sym: Symbol, owner: Symbol)(using Context): Unit = + if sym.maybeOwner.isTerm + && owner.isProperlyContainedIn(liftedOwner(sym)) + && owner != sym + then + report.log(i"narrow lifted $sym to $owner") + changedLiftedOwner = true + liftedOwner(sym) = owner + + private class NoPath extends Exception + + /** Mark symbol `sym` as being free in `enclosure`, unless `sym` is defined + * in `enclosure` or there is an intermediate class properly containing `enclosure` + * in which `sym` is also free. Also, update `liftedOwner` of `enclosure` so + * that `enclosure` can access `sym`, or its proxy in an intermediate class. + * This means: + * + * 1. If there is an intermediate class in which `sym` is free, `enclosure` + * must be contained in that class (in order to access the `sym proxy stored + * in the class). + * + * 2. If there is no intermediate class, `enclosure` must be contained + * in the class enclosing `sym`. + * + * @return If there is a non-trait class between `enclosure` and + * the owner of `sym`, the largest such class. + * Otherwise, if there is a trait between `enclosure` and + * the owner of `sym`, the largest such trait. + * Otherwise, NoSymbol. + * + * @pre sym.owner.isTerm, (enclosure.isMethod || enclosure.isClass) + * + * The idea of `markFree` is illustrated with an example: + * + * def f(x: int) = { + * class C { + * class D { + * val y = x + * } + * } + * } + * + * In this case `x` is free in the primary constructor of class `C`. + * but it is not free in `D`, because after lambda lift the code would be transformed + * as follows: + * + * def f(x$0: int) { + * class C(x$0: int) { + * val x$1 = x$0 + * class D { + * val y = outer.x$1 + * } + * } + * } + */ + private def markFree(sym: Symbol, enclosure: Symbol)(using Context): Symbol = try { + if (!enclosure.exists) throw new NoPath + if (enclosure == sym.enclosure) NoSymbol + else { + def nestedInConstructor(sym: Symbol): Boolean = + sym.isConstructor || + sym.isTerm && nestedInConstructor(sym.enclosure) + report.debuglog(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure") + val intermediate = + if (enclosure.is(PackageClass)) enclosure + else if (enclosure.isConstructor) markFree(sym, enclosure.owner.enclosure) + else markFree(sym, enclosure.enclosure) + if (intermediate.exists) narrowLiftedOwner(enclosure, intermediate) + if !intermediate.isRealClass || nestedInConstructor(enclosure) then + // Constructors and methods nested inside traits get the free variables + // of the enclosing trait or class. + // Conversely, local traits do not get free variables. + // Methods inside constructors also don't have intermediates, + // need to get all their free variables passed directly. + if (!enclosure.is(Trait)) + if (symSet(free, enclosure).add(sym)) { + changedFreeVars = true + report.log(i"$sym is free in $enclosure") + } + if (intermediate.isRealClass) intermediate + else if (enclosure.isRealClass) enclosure + else if (intermediate.isClass) intermediate + else if (enclosure.isClass) enclosure + else NoSymbol + } + } + catch { + case ex: NoPath => + println(i"error lambda lifting ${ctx.compilationUnit}: $sym is not visible from $enclosure") + throw ex + } + + private def markCalled(callee: Symbol, caller: Symbol)(using Context): Unit = { + report.debuglog(i"mark called: $callee of ${callee.owner} is called by $caller in ${caller.owner}") + assert(isLocal(callee)) + symSet(called, caller) += callee + } + + def process(tree: Tree)(using Context) = + val sym = tree.symbol + + def narrowTo(thisClass: ClassSymbol) = + val enclMethod = enclosure + val enclClass = enclMethod.enclosingClass + narrowLiftedOwner(enclMethod, + if enclClass.isContainedIn(thisClass) then thisClass + else enclClass) // unknown this reference, play it safe and assume the narrowest possible owner + + tree match + case tree: Ident => + if isLocal(sym) then + if isExpr(sym) then markCalled(sym, enclosure) + else if sym.isTerm then markFree(sym, enclosure) + def captureImplicitThis(x: Type): Unit = x match + case tr@TermRef(x, _) if !tr.termSymbol.isStatic => captureImplicitThis(x) + case x: ThisType if !x.tref.typeSymbol.isStaticOwner => narrowTo(x.tref.typeSymbol.asClass) + case _ => + captureImplicitThis(tree.tpe) + case tree: Select => + if isExpr(sym) && isLocal(sym) then markCalled(sym, enclosure) + case tree: This => + narrowTo(tree.symbol.asClass) + case tree: DefDef => + if sym.owner.isTerm then + liftedOwner(sym) = sym.enclosingPackageClass + // this will make methods in supercall constructors of top-level classes owned + // by the enclosing package, which means they will be static. + // On the other hand, all other methods will be indirectly owned by their + // top-level class. This avoids possible deadlocks when a static method + // has to access its enclosing object from the outside. + else if sym.isConstructor then + if sym.isPrimaryConstructor && isLocal(sym.owner) && !sym.owner.is(Trait) then + // add a call edge from the constructor of a local non-trait class to + // the class itself. This is done so that the constructor inherits + // the free variables of the class. + symSet(called, sym) += sym.owner + case tree: TypeDef => + if sym.owner.isTerm then liftedOwner(sym) = sym.topLevelClass.owner + case _ => + end process + + private class CollectDependencies extends TreeTraverser: + def traverse(tree: Tree)(using Context) = + try + process(tree) + traverseChildren(tree) + catch case ex: Exception => + println(i"$ex while traversing $tree") + throw ex + + /** Compute final free variables map `fvs by closing over caller dependencies. */ + private def computeFreeVars()(using Context): Unit = + while + changedFreeVars = false + for + caller <- called.keys + callee <- called(caller) + fvs <- free get callee + fv <- fvs + do + markFree(fv, caller) + changedFreeVars + do () + + /** Compute final liftedOwner map by closing over caller dependencies */ + private def computeLiftedOwners()(using Context): Unit = + while + changedLiftedOwner = false + for + caller <- called.keys + callee <- called(caller) + do + val normalizedCallee = callee.skipConstructor + val calleeOwner = normalizedCallee.owner + if calleeOwner.isTerm then narrowLiftedOwner(caller, liftedOwner(normalizedCallee)) + else + assert(calleeOwner.is(Trait)) + // methods nested inside local trait methods cannot be lifted out + // beyond the trait. Note that we can also call a trait method through + // a qualifier; in that case no restriction to lifted owner arises. + if caller.isContainedIn(calleeOwner) then + narrowLiftedOwner(caller, calleeOwner) + changedLiftedOwner + do () + + def collectDependencies()(using Context): Unit = + CollectDependencies().traverse(ctx.compilationUnit.tpdTree) + computeFreeVars() + computeLiftedOwners() +end DependencyCollector From 7bffc31027324a007f62be5430c9d8c82d3a6c2b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Jun 2021 18:23:06 +0200 Subject: [PATCH 36/87] Enrich Types infrastructure for captures - ThisTypes are also CaptureRefs - New dependency status: CaptureDependent --- .../src/dotty/tools/dotc/core/Types.scala | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index f6ec9cdc0baa..c809845fa2af 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2023,7 +2023,7 @@ object Types { def isOverloaded(using Context): Boolean = false } - /** A trait for references in CaptureSets. These can be NamedTypes or ParamRefs */ + /** A trait for references in CaptureSets. These can be NamedTypes, ThisTypes or ParamRefs */ trait CaptureRef extends TypeProxy, ValueType: private var myCaptureSet: CaptureSet = _ private var myCaptureSetRunId: Int = NoRunId @@ -2826,7 +2826,7 @@ object Types { * Note: we do not pass a class symbol directly, because symbols * do not survive runs whereas typerefs do. */ - abstract case class ThisType(tref: TypeRef) extends CachedProxyType with SingletonType { + abstract case class ThisType(tref: TypeRef) extends CachedProxyType, SingletonType, CaptureRef { def cls(using Context): ClassSymbol = tref.stableInRunSymbol match { case cls: ClassSymbol => cls case _ if ctx.mode.is(Mode.Interactive) => defn.AnyClass // was observed to happen in IDE mode @@ -2840,6 +2840,9 @@ object Types { // can happen in IDE if `cls` is stale } + override def canBeTracked(using Context) = cls.owner.isTerm + override def isRootCapability(using Context): Boolean = false + override def computeHash(bs: Binders): Int = doHash(bs, tref) override def eql(that: Type): Boolean = that match { @@ -3602,6 +3605,11 @@ object Types { case tp: AppliedType => tp.fold(status, compute(_, _, theAcc)) case tp: TypeVar if !tp.isInstantiated => combine(status, Provisional) case tp: TermParamRef if tp.binder eq thisLambdaType => TrueDeps + case tp: CapturingType => + val status1 = compute(status, tp.parent, theAcc) + tp.ref match + case tp: TermParamRef if tp.binder eq thisLambdaType => combine(status1, CaptureDeps) + case _ => status1 case _: ThisType | _: BoundType | NoPrefix => status case _ => (if theAcc != null then theAcc else DepAcc()).foldOver(status, tp) @@ -3647,6 +3655,11 @@ object Types { */ def isParamDependent(using Context): Boolean = paramDependencyStatus == TrueDeps + /** Is there either a true or false type dependency, or does the result + * type capture a parameter? + */ + def isCaptureDependent(using Context) = dependencyStatus >= CaptureDeps + def newParamRef(n: Int): TermParamRef = new TermParamRefImpl(this, n) /** The least supertype of `resultType` that does not contain parameter dependencies */ @@ -4028,10 +4041,11 @@ object Types { type DependencyStatus = Byte final val Unknown: DependencyStatus = 0 // not yet computed final val NoDeps: DependencyStatus = 1 // no dependent parameters found - final val FalseDeps: DependencyStatus = 2 // all dependent parameters are prefixes of non-depended alias types - final val TrueDeps: DependencyStatus = 3 // some truly dependent parameters exist - final val StatusMask: DependencyStatus = 3 // the bits indicating actual dependency status - final val Provisional: DependencyStatus = 4 // set if dependency status can still change due to type variable instantiations + final val CaptureDeps: DependencyStatus = 2 + final val FalseDeps: DependencyStatus = 3 // all dependent parameters are prefixes of non-depended alias types + final val TrueDeps: DependencyStatus = 4 // some truly dependent parameters exist + final val StatusMask: DependencyStatus = 7 // the bits indicating actual dependency status + final val Provisional: DependencyStatus = 8 // set if dependency status can still change due to type variable instantiations } // ----- Type application: LambdaParam, AppliedType --------------------- @@ -5125,7 +5139,7 @@ object Types { else CapturingType(parent, ref) def derivedCapturing(parent: Type, capt: Type)(using Context): Type = - if (parent eq this.parent) && (capt eq this.ref) then this + if (parent eq this.parent) && (capt eq this.ref) then this else parent.capturing(capt.captureSet) // equals comes from case class; no matching override is needed From 9af849847fca3bd7bdf689e9353f73148c9a8d57 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 6 Jun 2021 22:56:54 +0200 Subject: [PATCH 37/87] Add CheckCaptures phase --- compiler/src/dotty/tools/dotc/Compiler.scala | 4 +- .../tools/dotc/transform/Dependencies.scala | 26 +- .../tools/dotc/typer/CheckCaptures.scala | 351 ++++++++++++++++++ .../dotty/tools/dotc/typer/RefineTypes.scala | 28 +- 4 files changed, 375 insertions(+), 34 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 438cd3418330..2c8bcbb2c9c9 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -3,7 +3,7 @@ package dotc import core._ import Contexts._ -import typer.{FrontEnd, RefChecks, PreRefine, RefineTypes} +import typer.{FrontEnd, RefChecks, PreRefine, CheckCaptures} import Phases.Phase import transform._ import dotty.tools.backend.jvm.{CollectSuperCalls, GenBCode} @@ -38,7 +38,7 @@ class Compiler { protected def frontendPhases: List[List[Phase]] = List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer List(new PreRefine) :: - List(new RefineTypes) :: + List(new CheckCaptures) :: List(new YCheckPositions) :: // YCheck positions List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files diff --git a/compiler/src/dotty/tools/dotc/transform/Dependencies.scala b/compiler/src/dotty/tools/dotc/transform/Dependencies.scala index 0503dd71601c..c5c6c5baaa7b 100644 --- a/compiler/src/dotty/tools/dotc/transform/Dependencies.scala +++ b/compiler/src/dotty/tools/dotc/transform/Dependencies.scala @@ -194,20 +194,18 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co if isExpr(sym) && isLocal(sym) then markCalled(sym, enclosure) case tree: This => narrowTo(tree.symbol.asClass) - case tree: DefDef => - if sym.owner.isTerm then - logicOwner(sym) = sym.enclosingPackageClass - // this will make methods in supercall constructors of top-level classes owned - // by the enclosing package, which means they will be static. - // On the other hand, all other methods will be indirectly owned by their - // top-level class. This avoids possible deadlocks when a static method - // has to access its enclosing object from the outside. - else if sym.isConstructor then - if sym.isPrimaryConstructor && isLocal(sym.owner) && !sym.owner.is(Trait) then - // add a call edge from the constructor of a local non-trait class to - // the class itself. This is done so that the constructor inherits - // the free variables of the class. - symSet(called, sym) += sym.owner + case tree: MemberDef if isExpr(sym) && sym.owner.isTerm => + logicOwner(sym) = sym.enclosingPackageClass + // this will make methods in supercall constructors of top-level classes owned + // by the enclosing package, which means they will be static. + // On the other hand, all other methods will be indirectly owned by their + // top-level class. This avoids possible deadlocks when a static method + // has to access its enclosing object from the outside. + case tree: DefDef if sym.isPrimaryConstructor && isLocal(sym.owner) && !sym.owner.is(Trait) => + // add a call edge from the constructor of a local non-trait class to + // the class itself. This is done so that the constructor inherits + // the free variables of the class. + symSet(called, sym) += sym.owner case tree: TypeDef => if sym.owner.isTerm then logicOwner(sym) = sym.topLevelClass.owner case _ => diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala new file mode 100644 index 000000000000..f927fcfb8788 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -0,0 +1,351 @@ +package dotty.tools +package dotc +package typer + +import core._ +import Phases.*, DenotTransformers.*, SymDenotations.* +import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.* +import Types._ +import Symbols._ +import StdNames._ +import Decorators._ +import ProtoTypes._ +import Inferencing.isFullyDefined +import config.Printers.refinr +import ast.{tpd, untpd, Trees} +import NameKinds.{DocArtifactName, OuterSelectName, DefaultGetterName} +import Trees._ +import scala.util.control.NonFatal +import typer.ErrorReporting._ +import util.Spans.Span +import util.SimpleIdentitySet +import util.Chars.* +import Nullables._ +import transform.* +import scala.collection.mutable +import reporting._ +import ProtoTypes._ +import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions + +/** A class that can be used to do type checking again after the first typer phase + * is run. This phase will use the output of the previous typer but "forget" certain things + * so that they can be reinferred. Things that are forgotten fall into the following + * categories: + * + * 1. Bindings of inferred type variables in type applications. + * 2. Inferred types of local or private vals or vars. Exception: Types of + * inline vals and Java-defined fields are kept. + * 3. Inferred result types of local or private methods. Eception: Types + * of default getters and Java-defined methods are kept. + * (The default getter restriction is there for technical reason, we should be + * able to lift it once we change the scheme for default arguments). + * 4. Types of closure parameters that are inferred from the expected type. + * Types of closure parameters that are inferred from the called method + * are left alone (also for technical reasons). + * + * The re-typed trees and associated symbol infos are thrown away once the phase + * has ended. So the phase can be only used for more refined type checking, but + * not for code transformation. + */ +class CheckCaptures extends RefineTypes: + import ast.tpd.* + + def phaseName: String = "checkCaptures" + override def isEnabled(using Context) = ctx.settings.YrefineTypes.value + + def newRefiner() = CaptureChecker() + + class CaptureChecker extends TypeRefiner: + import ast.tpd.* + override def newLikeThis: Typer = CaptureChecker() + + private var myDeps: Dependencies = null + + def deps(using Context): Dependencies = + if myDeps == null then + myDeps = new Dependencies(ctx.compilationUnit.tpdTree, ctx): + def isExpr(sym: Symbol)(using Context): Boolean = + sym.isRealClass || sym.isOneOf(MethodOrLazy) + def enclosure(using Context) = + def recur(owner: Symbol): Symbol = + if isExpr(owner) || !owner.exists then owner else recur(owner.owner) + recur(ctx.owner) + myDeps + + override def typedClosure(tree: untpd.Closure, pt: Type)(using Context): Tree = + val tree1 = super.typedClosure(tree, pt).asInstanceOf[Closure] + val captured = CaptureSet( + deps.freeVars(tree1.meth.symbol).toList.map(_.termRef).filter(_.isTracked)*) + tree1.withType(tree1.tpe.capturing(captured)) + end CaptureChecker +end CheckCaptures + +/* + def run(using Context): Unit = + refinr.println(i"refine types of ${ctx.compilationUnit}") + val refiner = newRefiner() + val unit = ctx.compilationUnit + val refineCtx = ctx + .fresh + .setMode(Mode.ImplicitsEnabled) + .setTyper(refiner) + val refinedTree = refiner.typedExpr(unit.tpdTree)(using refineCtx) + if ctx.settings.Xprint.value.containsPhase(this) then + report.echo(i"discarded result of $unit after refineTypes:\n\n$refinedTree") + + def preRefinePhase = this.prev.asInstanceOf[PreRefine] + def thisPhase = this + + def newRefiner(): TypeRefiner = TypeRefiner() + + class TypeRefiner extends ReTyper: + import ast.tpd.* + + override def newLikeThis: Typer = new TypeRefiner + + /* override def typedAhead(tree: untpd.Tree, typed: untpd.Tree => Tree)(using Context): Tree = + tree.getAttachment(TypedAhead) match + case Some(ttree) => ttree + case none => + val ttree = typed(tree) + tree.putAttachment(TypedAhead, ttree) + ttree*/ + + /** Update the symbol's info to `newInfo` for the current phase, and + * to the symbol's orginal info for the phase afterwards. + */ + def updateInfo(sym: Symbol, newInfo: Type)(using Context): Unit = + sym.copySymDenotation().installAfter(thisPhase) // reset + sym.copySymDenotation( + info = newInfo, + initFlags = + if newInfo.isInstanceOf[LazyType] then sym.flags &~ Touched + else sym.flags + ).installAfter(preRefinePhase) + + /** A completer for local and provate vals, vars, and defs. Re-infers + * the type from the type of the right-hand side expression. + */ + class RefineCompleter(val original: ValOrDefDef)(using Context) extends LazyType: + def completeInCreationContext(symd: SymDenotation): Unit = + val (paramss, paramFn) = original match + case ddef: DefDef => + val paramss = ddef.paramss.nestedMap(_.symbol) + (paramss, wrapMethodType(_: Type, paramss, isJava = false)) + case _: ValDef => + (Nil, (x: Type) => x) + inContext(ctx.fresh.setOwner(symd.symbol).setTree(original)) { + val rhsType = inferredResultType(original, symd.symbol, paramss, paramFn, WildcardType) + typedAheadType(original.tpt, rhsType) + symd.info = paramFn(rhsType) + } + + def complete(symd: SymDenotation)(using Context): Unit = completeInCreationContext(symd) + end RefineCompleter + + /** Update the infos of all symbols defined `trees` that have (result) types + * that need to be reinferred. This is the case if + * 1. the type was inferred originally, and + * 2. the definition is private or local, + * 3. the definition is not a parameter or Java defined + * 4. the definition is not an inline value + * 5. the definition is not a default getter + */ + override def index(trees: List[untpd.Tree])(using Context): Context = + for case tree: ValOrDefDef <- trees.asInstanceOf[List[Tree]] do + val sym = tree.symbol + if tree.tpt.isInstanceOf[untpd.InferredTypeTree] + && (sym.is(Private) || sym.owner.isTerm) + && !sym.isOneOf(Param | JavaDefined) + && !sym.isOneOf(FinalOrInline, butNot = Method | Mutable) + && !sym.name.is(DefaultGetterName) + then + updateInfo(sym, RefineCompleter(tree)) + ctx + + /** Keep the types of all source-written type trees; re-typecheck the rest */ + override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = + trace(i"typed $tree, $pt", refinr, show = true) { + tree.removeAttachment(TypedAhead) match + case Some(ttree) => ttree + case none => + tree match + case _: untpd.TypedSplice + | _: untpd.Thicket + | _: EmptyValDef[?] + | _: untpd.TypeTree => + super.typedUnadapted(tree, pt, locked) + case _ if tree.isType => + promote(tree) + case _ => + super.typedUnadapted(tree, pt, locked) + } + + /** Keep the symbol of the `select` but re-infer its type */ + override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = + val Select(qual, name) = tree + if name.is(OuterSelectName) then promote(tree) + else + val qual1 = withoutMode(Mode.Pattern)(typed(qual, AnySelectionProto)) + val qualType = qual1.tpe.widenIfUnstable + val pre = maybeSkolemizePrefix(qualType, name) + val mbr = qualType.findMember(name, pre, + excluded = if tree.symbol.is(Private) then EmptyFlags else Private) + .suchThat(tree.symbol ==) + val ownType = qualType.select(name, mbr) + untpd.cpy.Select(tree)(qual1, name).withType(ownType) + + /** Set the type of inferred TypeTrees to the expected type. Keep the others unchanged. */ + override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): TypeTree = + if tree.isInstanceOf[untpd.InferredTypeTree] && isFullyDefined(pt, ForceDegree.flipBottom) then + tree.withType(pt) + else + promote(tree) + + /** Redo core steps of type checking from Typer (they were overridden in ReTyper). + * Compare with `typedTyped` in TreeChecker that does essentially the same thing + */ + override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = + val tpt1 = checkSimpleKinded(typedType(tree.tpt)) + val expr1 = tree.expr match + case id: untpd.Ident if (ctx.mode is Mode.Pattern) && untpd.isVarPattern(id) && (id.name == nme.WILDCARD || id.name == nme.WILDCARD_STAR) => + tree.expr.withType(tpt1.tpe) + case _ => + var pt1 = tpt1.tpe + if pt1.isRepeatedParam then + pt1 = pt1.translateFromRepeated(toArray = tree.expr.typeOpt.derivesFrom(defn.ArrayClass)) + typed(tree.expr, pt1) + untpd.cpy.Typed(tree)(expr1, tpt1).withType(tree.typeOpt) + + /** Replace all type variables in a (possibly embedded) type application + * by fresh, uninstantiated type variables that are pairwise linked with + * the old ones. The type application can either be the toplevel tree `tree` + * or wrapped in one or more closures. + * Replacement insde closures is necessary since sometimes type variables + * bound in a callee are leaked in the parameter types of an enclosing closure + * that infers its parameter types from the callee. + * @return The changed tree with the new type variables, + * and a map from old type variables to corresponding freshly created type variables + */ + private def resetTypeVars[T <: tpd.Tree](tree: T)(using Context): (T, Map[TypeVar, TypeVar]) = tree match + case tree: TypeApply => + val tvars = for arg <- tree.args; case tvar: TypeVar <- arg.tpe :: Nil yield tvar + if tvars.nonEmpty && tvars.length == tree.args.length then + val (args1, tvars1) = + if tvars.head.isInstantiated then + // we are seeing type variables that are not yet copied by a previous resetTypeVars + val args1 = constrained(tree.fun.tpe.widen.asInstanceOf[TypeLambda], tree)._2 + val tvars1 = args1.tpes.asInstanceOf[List[TypeVar]] + for (tvar, tvar1) <- tvars.lazyZip(tvars1) do tvar1.link(tvar) + (args1, tvars1) + else + (tree.args, tvars) + (cpy.TypeApply(tree)(tree.fun, args1).asInstanceOf[T], + tvars1.map(tvar => (tvar.linkedOriginal, tvar)).toMap) + else + (tree, Map.empty) + case tree @ Apply(fn, args) => + val (fn1, map1) = resetTypeVars(fn) + (cpy.Apply(tree)(fn, args).asInstanceOf[T], map1) + case Block(stats, closure: Closure) => + var tvmap: Map[TypeVar, TypeVar] = Map.empty + val stats1 = stats.mapConserve { + case stat: DefDef if stat.symbol == closure.meth.symbol => + val (rhs1, map1) = resetTypeVars(stat.rhs) + tvmap = map1 + cpy.DefDef(stat)(rhs = rhs1) + case stat => stat + } + (cpy.Block(tree)(stats1, closure).asInstanceOf[T], tvmap) + case Block(Nil, expr) => + val (rhs1, map1) = resetTypeVars(expr) + (cpy.Block(tree)(Nil, rhs1).asInstanceOf[T], map1) + case _ => + (tree, Map.empty) + end resetTypeVars + + /** The application with all inferred type arguments reset to fresh type variab;es + * classOf[...] applications are left alone. + */ + override def typedTypeApply(app: untpd.TypeApply, pt: Type)(using Context): Tree = + val app0 = promote(app) + if app0.symbol == defn.Predef_classOf then app0 + else super.typedTypeApply(resetTypeVars(app0)._1, pt) + + /** If block is defines closure, replace all parameters that were inferred + * from the expected type by corresponding parts of the new expected type. + * Update infos of parameter symbols and the anonymous function accordingly. + */ + override def typedBlock(blk: untpd.Block, pt: Type)(using Context): Tree = + val blk0 = promote(blk) + val blk1 = blk0.expr match + case closure: Closure => + val stats1 = blk0.stats.mapConserve { + case stat: DefDef if stat.symbol == closure.meth.symbol => + stat.paramss match + case ValDefs(params) :: Nil => + val (protoFormals, _) = decomposeProtoFunction(pt, params.length, stat) + val params1 = params.zipWithConserve(protoFormals) { + case (param @ ValDef(_, tpt: InferredTypeTree, _), formal) + if isFullyDefined(formal, ForceDegree.failBottom) => + updateInfo(param.symbol, formal) + cpy.ValDef(param)(tpt = param.tpt.withType(formal)) + case (param, _) => + param + } + if params eq params1 then stat + else + val mt = stat.symbol.info.asInstanceOf[MethodType] + val formals1 = + for i <- mt.paramInfos.indices.toList yield + if params(i) eq params1(i) then mt.paramInfos(i) else protoFormals(i) + updateInfo(stat.symbol, mt.derivedLambdaType(paramInfos = formals1)) + cpy.DefDef(stat)(paramss = params1 :: Nil) + case _ => + stat + } + cpy.Block(blk0)(stats1, closure) + case _ => + blk + super.typedBlock(blk1, pt) + + /** If tree defines an anonymous function, make sure that any type variables + * defined in the callee rhs are replaced in the function itself. + */ + override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = + sym.ensureCompleted() + if sym.isAnonymousFunction then + val ddef0 = promote(ddef) + val (rhs1, tvmap) = resetTypeVars(ddef0.rhs) + if tvmap.nonEmpty then + val tmap = new TypeMap: + def apply(t: Type) = mapOver { + t match + case t: TypeVar => tvmap.getOrElse(t, t) + case _ => t + } + val ValDefs(params) :: Nil = ddef0.paramss + val params1 = params.mapConserve { param => + updateInfo(param.symbol, tmap(param.symbol.info)) + cpy.ValDef(param)(tpt = param.tpt.withType(tmap(param.tpt.tpe))) + } + val mt = sym.info.asInstanceOf[MethodType] + updateInfo(sym, mt.derivedLambdaType(paramInfos = mt.paramInfos.mapConserve(tmap))) + val ddef1 = cpy.DefDef(ddef0)(paramss = params1 :: Nil, rhs = rhs1) + val nestedCtx = ctx.fresh.setNewTyperState() // needed so that leaked type variables properly nest in their owning typerstate + try inContext(nestedCtx) { super.typedDefDef(ddef1, sym) } + finally nestedCtx.typerState.commit() + else super.typedDefDef(ddef, sym) + else super.typedDefDef(ddef, sym) + + override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = + sym.ensureCompleted() + super.typedValDef(vdef, sym) + + override def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree = + if tree.symbol == defn.StdLibPatchesPackage then + promote(tree) // don't check stdlib patches, since their symbols were highjacked by stdlib classes + else + super.typedPackageDef(tree) + end TypeRefiner +*/ diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 65ef6cb04b89..4b69c2b8c739 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -47,14 +47,9 @@ import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions * has ended. So the phase can be only used for more refined type checking, but * not for code transformation. */ -class RefineTypes extends Phase, IdentityDenotTransformer: - import RefineTypes.* +abstract class RefineTypes extends Phase, IdentityDenotTransformer: import ast.tpd.* - def phaseName: String = RefineTypes.name - - override def isEnabled(using Context) = ctx.settings.YrefineTypes.value - override def isTyper: Boolean = true def run(using Context): Unit = @@ -72,21 +67,13 @@ class RefineTypes extends Phase, IdentityDenotTransformer: def preRefinePhase = this.prev.asInstanceOf[PreRefine] def thisPhase = this - def newRefiner(): TypeRefiner = TypeRefiner() + def newRefiner(): TypeRefiner class TypeRefiner extends ReTyper: import ast.tpd.* override def newLikeThis: Typer = new TypeRefiner - /* override def typedAhead(tree: untpd.Tree, typed: untpd.Tree => Tree)(using Context): Tree = - tree.getAttachment(TypedAhead) match - case Some(ttree) => ttree - case none => - val ttree = typed(tree) - tree.putAttachment(TypedAhead, ttree) - ttree*/ - /** Update the symbol's info to `newInfo` for the current phase, and * to the symbol's orginal info for the phase afterwards. */ @@ -359,7 +346,12 @@ class RefineTypes extends Phase, IdentityDenotTransformer: else super.typedPackageDef(tree) end TypeRefiner - -object RefineTypes: - val name = "refineTypes" end RefineTypes + +class TestRefineTypes extends RefineTypes: + def phaseName: String = "refineTypes" + override def isEnabled(using Context) = ctx.settings.YrefineTypes.value + def newRefiner() = TypeRefiner() + + + From b79931efb6f3daa34ee91ce5ee172fb02301f6d4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Jun 2021 14:33:24 +0200 Subject: [PATCH 38/87] Normalize refs in CapturingTypes and CaptureSets Comparison is via `eq`, so we have to make sure equal elements are identical. --- .../dotty/tools/dotc/core/CaptureSet.scala | 6 +++--- .../src/dotty/tools/dotc/core/Types.scala | 21 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala index 36e7d781af3c..89f01ae13450 100644 --- a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala @@ -11,7 +11,7 @@ import reporting.trace import printing.{Showable, Printer} import printing.Texts.* -case class CaptureSet(elems: CaptureSet.Refs) extends Showable: +case class CaptureSet private (elems: CaptureSet.Refs) extends Showable: import CaptureSet.* def isEmpty: Boolean = elems.isEmpty @@ -55,9 +55,9 @@ object CaptureSet: /** Used as a recursion brake */ @sharable private[core] val Pending = CaptureSet(SimpleIdentitySet.empty) - def apply(elems: CaptureRef*): CaptureSet = + def apply(elems: CaptureRef*)(using Context): CaptureSet = if elems.isEmpty then empty - else CaptureSet(SimpleIdentitySet(elems*)) + else CaptureSet(SimpleIdentitySet(elems.map(_.normalizedRef)*)) def ofType(tp: Type)(using Context): CaptureSet = val collect = new TypeAccumulator[Refs]: diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c809845fa2af..df0060d032ec 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2027,11 +2027,17 @@ object Types { trait CaptureRef extends TypeProxy, ValueType: private var myCaptureSet: CaptureSet = _ private var myCaptureSetRunId: Int = NoRunId - private val singletonCaptureSet = CaptureSet(this) + private var mySingletonCaptureSet: CaptureSet = null - def isRootCapability(using Context): Boolean def canBeTracked(using Context): Boolean - def isTracked(using Context): Boolean = canBeTracked && captureSetOfInfo.nonEmpty + final def isTracked(using Context): Boolean = canBeTracked && captureSetOfInfo.nonEmpty + def isRootCapability(using Context): Boolean = false + def normalizedRef(using Context): CaptureRef = this + + def singletonCaptureSet(using Context): CaptureSet = + if mySingletonCaptureSet == null then + mySingletonCaptureSet = CaptureSet(this.normalizedRef) + mySingletonCaptureSet def captureSetOfInfo(using Context): CaptureSet = if ctx.runId == myCaptureSetRunId then myCaptureSet @@ -2387,9 +2393,12 @@ object Types { def canBeTracked(using Context) = (prefix eq NoPrefix) || symbol.is(TypeParam) || isRootCapability - def isRootCapability(using Context): Boolean = + override def isRootCapability(using Context): Boolean = name == tpnme.CAPTURE_ROOT && symbol == defn.captureRootType + override def normalizedRef(using Context): CaptureRef = + if canBeTracked then symbol.namedType else this + private def checkDenot()(using Context) = {} private def checkSymAssign(sym: Symbol)(using Context) = { @@ -2841,7 +2850,6 @@ object Types { } override def canBeTracked(using Context) = cls.owner.isTerm - override def isRootCapability(using Context): Boolean = false override def computeHash(bs: Binders): Int = doHash(bs, tref) @@ -4346,7 +4354,6 @@ object Types { } override def canBeTracked(using Context) = true - override def isRootCapability(using Context): Boolean = false override def computeHash(bs: Binders): Int = doHash(paramNum, binder.identityHash(bs)) @@ -5161,7 +5168,7 @@ object Types { object CapturingType: def apply(parent: Type, ref: CaptureRef)(using Context): CapturingType = - unique(CachedCapturingType(parent, ref)) + unique(CachedCapturingType(parent, ref.normalizedRef)) def checked(parent: Type, ref: Type)(using Context): CapturingType = ref match case ref: CaptureRef => apply(parent, ref) end CapturingType From 91cb1bcc014063637c714a53c823441d42e685c8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Jun 2021 16:14:13 +0200 Subject: [PATCH 39/87] Add missing case in type comparer --- .../dotty/tools/dotc/core/TypeComparer.scala | 20 +++++++++++- .../dotty/tools/dotc/CompilationTests.scala | 2 ++ tests/neg-custom-args/captures/capt1.scala | 32 +++++++++++++++++++ tests/pos-special/captures/capt1.scala | 6 ++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/neg-custom-args/captures/capt1.scala create mode 100644 tests/pos-special/captures/capt1.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 6ac206b091ea..097645ac0521 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -803,7 +803,25 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case _ => false } case _ => false - comparePaths || isSubType(tp1.underlying.widenExpr, tp2, approx.addLow) + comparePaths || { + val tp2n = tp1 match + case tp1: CaptureRef if tp1.isTracked && tp2.captureSet.accountsFor(tp1) => + // New rule dealing with singleton types on the left: + // + // E |- x: S {x} <: cv(T) E |- S <: {*} T + // ------------------------------------------ + // E |- x.type <:< T + // + // Note: This would map to the following (Var) rule in deep capture calculus: + // + // E |- x: S {x} <: cv(T) E |- S <: {*} T + // ------------------------------------------ + // E |- x: {x} T + // + CapturingType(tp2, defn.captureRootType.typeRef) + case _ => tp2 + isSubType(tp1.underlying.widenExpr, tp2n, approx.addLow) + } case tp1: RefinedType => isNewSubType(tp1.parent) case tp1: RecType => diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index d4af8b3639bb..27f5b1385a3e 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -33,6 +33,7 @@ class CompilationTests { compileFilesInDir("tests/pos-special/sourcepath/outer", defaultOptions.and("-sourcepath", "tests/pos-special/sourcepath")), compileFile("tests/pos-special/sourcepath/outer/nested/Test4.scala", defaultOptions.and("-sourcepath", "tests/pos-special/sourcepath")), compileFilesInDir("tests/pos-special/fatal-warnings", defaultOptions.and("-Xfatal-warnings", "-deprecation", "-feature")), + compileFilesInDir("tests/pos-special/captures", defaultOptions.and("-Yrefine-types")), compileFile("tests/pos-special/avoid-warn-deprecation.scala", defaultOptions.and("-Xfatal-warnings", "-feature")), compileFilesInDir("tests/pos-special/spec-t5545", defaultOptions), compileFilesInDir("tests/pos-special/strawman-collections", allowDeepSubtypes), @@ -132,6 +133,7 @@ class CompilationTests { compileFilesInDir("tests/neg-custom-args/allow-deep-subtypes", allowDeepSubtypes), compileFilesInDir("tests/neg-custom-args/explicit-nulls", defaultOptions.and("-Yexplicit-nulls")), compileFilesInDir("tests/neg-custom-args/no-experimental", defaultOptions.and("-Yno-experimental")), + compileFilesInDir("tests/neg-custom-args/captures", defaultOptions.and("-Yrefine-types")), compileDir("tests/neg-custom-args/impl-conv", defaultOptions.and("-Xfatal-warnings", "-feature")), compileFile("tests/neg-custom-args/implicit-conversions.scala", defaultOptions.and("-Xfatal-warnings", "-feature")), compileFile("tests/neg-custom-args/implicit-conversions-old.scala", defaultOptions.and("-Xfatal-warnings", "-feature")), diff --git a/tests/neg-custom-args/captures/capt1.scala b/tests/neg-custom-args/captures/capt1.scala new file mode 100644 index 000000000000..a8c626663e5c --- /dev/null +++ b/tests/neg-custom-args/captures/capt1.scala @@ -0,0 +1,32 @@ +class C +def f(x: C holds *, y: C): () => C = + () => if x == null then y else y // error + +def g(x: C holds *, y: C): Any = + () => if x == null then y else y // error + +def h1(x: C holds *, y: C): Any holds x.type = + def f() = if x == null then y else y + () => f() // ok + +def h2(x: C holds *): Any = + def f(y: Int) = if x == null then y else y + f // error + +class A +type Cap = C holds * + +def h3(x: Cap): A = + class F(y: Int) extends A: + def m() = if x == null then y else y + F(22) // error + +def h4(x: Cap, y: Int): A = + new A: + def m() = if x == null then y else y // error + +//def f1(c: Cap): (() => C holds c.type) = () => c // ok +//def f2(c: Cap): (() => C) holds c.type = () => c + +//def h5(x: Cap): () => C = +// f(x) diff --git a/tests/pos-special/captures/capt1.scala b/tests/pos-special/captures/capt1.scala new file mode 100644 index 000000000000..59ad1a9ab798 --- /dev/null +++ b/tests/pos-special/captures/capt1.scala @@ -0,0 +1,6 @@ +class C + +def foo() = + val x: C holds * = ??? + val y: C holds x.type = x + y From bd1da7113819f2017388a1b3ce67ae9425c2acd1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Jun 2021 18:41:28 +0200 Subject: [PATCH 40/87] Typecheck applications --- .../tools/dotc/typer/CheckCaptures.scala | 26 ++++++++++++++++--- tests/neg-custom-args/captures/capt1.scala | 6 ----- tests/neg-custom-args/captures/capt2.scala | 5 ++++ tests/pos-special/captures/capt1.scala | 10 ++++++- 4 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 tests/neg-custom-args/captures/capt2.scala diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index f927fcfb8788..d051c8147ff7 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -72,11 +72,29 @@ class CheckCaptures extends RefineTypes: recur(ctx.owner) myDeps + private def capturedVars(sym: Symbol)(using Context): CaptureSet = + CaptureSet(deps.freeVars(sym).toList.map(_.termRef).filter(_.isTracked)*) + override def typedClosure(tree: untpd.Closure, pt: Type)(using Context): Tree = - val tree1 = super.typedClosure(tree, pt).asInstanceOf[Closure] - val captured = CaptureSet( - deps.freeVars(tree1.meth.symbol).toList.map(_.termRef).filter(_.isTracked)*) - tree1.withType(tree1.tpe.capturing(captured)) + super.typedClosure(tree, pt) match + case tree1: Closure => + tree1.withType(tree1.tpe.capturing(capturedVars(tree1.meth.symbol))) + case tree1 => tree1 + + override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = + super.typedApply(tree, pt) match + case tree1 @ Apply(fn, args) => + val tree2 = fn.tpe.widen match + case mt: MethodType if mt.isCaptureDependent => + tree1.withType(tree1.tpe.substParams(mt, args.tpes)) + case _ => + tree1 + if tree.fun.symbol.isConstructor then + //println(i"typing $tree1, ${capturedVars(tree1.tpe.classSymbol)}") + tree2.withType(tree2.tpe.capturing(capturedVars(tree1.tpe.classSymbol))) + else + tree2 + case tree1 => tree1 end CaptureChecker end CheckCaptures diff --git a/tests/neg-custom-args/captures/capt1.scala b/tests/neg-custom-args/captures/capt1.scala index a8c626663e5c..fb898a6f59fc 100644 --- a/tests/neg-custom-args/captures/capt1.scala +++ b/tests/neg-custom-args/captures/capt1.scala @@ -24,9 +24,3 @@ def h3(x: Cap): A = def h4(x: Cap, y: Int): A = new A: def m() = if x == null then y else y // error - -//def f1(c: Cap): (() => C holds c.type) = () => c // ok -//def f2(c: Cap): (() => C) holds c.type = () => c - -//def h5(x: Cap): () => C = -// f(x) diff --git a/tests/neg-custom-args/captures/capt2.scala b/tests/neg-custom-args/captures/capt2.scala new file mode 100644 index 000000000000..045e2e372211 --- /dev/null +++ b/tests/neg-custom-args/captures/capt2.scala @@ -0,0 +1,5 @@ +def f1(c: Cap): (() => C holds c.type) = () => c // ok +def f2(c: Cap): (() => C) holds c.type = () => c // error + +def h5(x: Cap): () => C = + f1(x) // error diff --git a/tests/pos-special/captures/capt1.scala b/tests/pos-special/captures/capt1.scala index 59ad1a9ab798..f0f82c8c6e18 100644 --- a/tests/pos-special/captures/capt1.scala +++ b/tests/pos-special/captures/capt1.scala @@ -1,6 +1,14 @@ class C +type Cap = C holds * + +def f1(c: Cap): () => C holds c.type = () => c // ok def foo() = val x: C holds * = ??? val y: C holds x.type = x - y + val x2: (() => C) holds x.type = ??? + val y2: () => C holds x.type = x2 + + val z: () => Cap = f1(x) + + From 2905795a26ba020c9bc6e4ca815fb355ffacc59b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Jun 2021 18:48:32 +0200 Subject: [PATCH 41/87] Ignore `holds` unless `-Yrefine-types` is set --- compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 3 ++- compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 94c471451428..f7a0c9683aac 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -361,7 +361,8 @@ class TreeUnpickler(reader: TastyReader, val args = until(end)(readType()) tycon match case tycon: TypeRef if tycon.symbol == defn.Predef_holdsType => - CapturingType.checked(args(0), args(1)) + if ctx.settings.YrefineTypes.value then CapturingType.checked(args(0), args(1)) + else args(0) case _ => tycon.appliedTo(args) case TYPEBOUNDS => diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index daf34adbf204..712041ad64a0 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -208,7 +208,9 @@ trait TypeAssigner { val constr = tycon.typeSymbol if constr == defn.andType then AndType(args(0), args(1)) else if constr == defn.orType then OrType(args(0), args(1), soft = false) - else if constr == defn.Predef_holdsType then captType(args(0), args(1)) + else if constr == defn.Predef_holdsType then + if ctx.settings.YrefineTypes.value then captType(args(0), args(1)) + else args(0) else tp case _ => tp end processAppliedType From 6cee8c06be07fbaddafc4fede5bfc739198ca1a9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Jun 2021 19:04:01 +0200 Subject: [PATCH 42/87] Introduce -Ycc setting --- compiler/src/dotty/tools/dotc/Compiler.scala | 3 ++- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 1 + compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala | 2 +- compiler/src/dotty/tools/dotc/typer/PreRefine.scala | 3 ++- compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- compiler/test/dotty/tools/dotc/CompilationTests.scala | 4 ++-- 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 2c8bcbb2c9c9..a5404e425fd3 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -3,7 +3,7 @@ package dotc import core._ import Contexts._ -import typer.{FrontEnd, RefChecks, PreRefine, CheckCaptures} +import typer.{FrontEnd, RefChecks, PreRefine, CheckCaptures, TestRefineTypes} import Phases.Phase import transform._ import dotty.tools.backend.jvm.{CollectSuperCalls, GenBCode} @@ -39,6 +39,7 @@ class Compiler { List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer List(new PreRefine) :: List(new CheckCaptures) :: + List(new TestRefineTypes) :: List(new YCheckPositions) :: // YCheck positions List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 7ed417fc9cf9..1392baa80a75 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -202,6 +202,7 @@ trait AllScalaSettings extends CommonScalaSettings { self: Settings.SettingGroup val YcheckInit: Setting[Boolean] = BooleanSetting("-Ysafe-init", "Ensure safe initialization of objects") val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation") val YrefineTypes: Setting[Boolean] = BooleanSetting("-Yrefine-types", "Run experimental type refiner (test only)") + val Ycc: Setting[Boolean] = BooleanSetting("-Ycc", "Check captured references") /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index f7a0c9683aac..4b07e82beb5c 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -361,7 +361,7 @@ class TreeUnpickler(reader: TastyReader, val args = until(end)(readType()) tycon match case tycon: TypeRef if tycon.symbol == defn.Predef_holdsType => - if ctx.settings.YrefineTypes.value then CapturingType.checked(args(0), args(1)) + if ctx.settings.Ycc.value then CapturingType.checked(args(0), args(1)) else args(0) case _ => tycon.appliedTo(args) diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index d051c8147ff7..d9b0b03bb4ed 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -51,7 +51,7 @@ class CheckCaptures extends RefineTypes: import ast.tpd.* def phaseName: String = "checkCaptures" - override def isEnabled(using Context) = ctx.settings.YrefineTypes.value + override def isEnabled(using Context) = ctx.settings.Ycc.value def newRefiner() = CaptureChecker() diff --git a/compiler/src/dotty/tools/dotc/typer/PreRefine.scala b/compiler/src/dotty/tools/dotc/typer/PreRefine.scala index f7b73890d6aa..0d331b6556b5 100644 --- a/compiler/src/dotty/tools/dotc/typer/PreRefine.scala +++ b/compiler/src/dotty/tools/dotc/typer/PreRefine.scala @@ -12,7 +12,8 @@ class PreRefine extends Phase, IdentityDenotTransformer: def phaseName: String = "preRefine" - override def isEnabled(using Context) = ctx.settings.YrefineTypes.value + override def isEnabled(using Context) = + ctx.settings.YrefineTypes.value || ctx.settings.Ycc.value override def changesBaseTypes: Boolean = true diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 712041ad64a0..fe26390d8777 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -209,7 +209,7 @@ trait TypeAssigner { if constr == defn.andType then AndType(args(0), args(1)) else if constr == defn.orType then OrType(args(0), args(1), soft = false) else if constr == defn.Predef_holdsType then - if ctx.settings.YrefineTypes.value then captType(args(0), args(1)) + if ctx.settings.Ycc.value then captType(args(0), args(1)) else args(0) else tp case _ => tp diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 27f5b1385a3e..6b0c77d77b32 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -33,7 +33,7 @@ class CompilationTests { compileFilesInDir("tests/pos-special/sourcepath/outer", defaultOptions.and("-sourcepath", "tests/pos-special/sourcepath")), compileFile("tests/pos-special/sourcepath/outer/nested/Test4.scala", defaultOptions.and("-sourcepath", "tests/pos-special/sourcepath")), compileFilesInDir("tests/pos-special/fatal-warnings", defaultOptions.and("-Xfatal-warnings", "-deprecation", "-feature")), - compileFilesInDir("tests/pos-special/captures", defaultOptions.and("-Yrefine-types")), + compileFilesInDir("tests/pos-special/captures", defaultOptions.and("-Ycc")), compileFile("tests/pos-special/avoid-warn-deprecation.scala", defaultOptions.and("-Xfatal-warnings", "-feature")), compileFilesInDir("tests/pos-special/spec-t5545", defaultOptions), compileFilesInDir("tests/pos-special/strawman-collections", allowDeepSubtypes), @@ -133,7 +133,7 @@ class CompilationTests { compileFilesInDir("tests/neg-custom-args/allow-deep-subtypes", allowDeepSubtypes), compileFilesInDir("tests/neg-custom-args/explicit-nulls", defaultOptions.and("-Yexplicit-nulls")), compileFilesInDir("tests/neg-custom-args/no-experimental", defaultOptions.and("-Yno-experimental")), - compileFilesInDir("tests/neg-custom-args/captures", defaultOptions.and("-Yrefine-types")), + compileFilesInDir("tests/neg-custom-args/captures", defaultOptions.and("-Ycc")), compileDir("tests/neg-custom-args/impl-conv", defaultOptions.and("-Xfatal-warnings", "-feature")), compileFile("tests/neg-custom-args/implicit-conversions.scala", defaultOptions.and("-Xfatal-warnings", "-feature")), compileFile("tests/neg-custom-args/implicit-conversions-old.scala", defaultOptions.and("-Xfatal-warnings", "-feature")), From 2f085ac489ff958c4fc6152abbeb9c5f1a27f89a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Jun 2021 19:05:07 +0200 Subject: [PATCH 43/87] Fix tests --- tests/neg-custom-args/captures/capt2.scala | 3 +++ tests/{neg => neg-custom-args/captures}/cc1.scala | 0 2 files changed, 3 insertions(+) rename tests/{neg => neg-custom-args/captures}/cc1.scala (100%) diff --git a/tests/neg-custom-args/captures/capt2.scala b/tests/neg-custom-args/captures/capt2.scala index 045e2e372211..12157c06f9a6 100644 --- a/tests/neg-custom-args/captures/capt2.scala +++ b/tests/neg-custom-args/captures/capt2.scala @@ -1,3 +1,6 @@ +class C +type Cap = C holds * + def f1(c: Cap): (() => C holds c.type) = () => c // ok def f2(c: Cap): (() => C) holds c.type = () => c // error diff --git a/tests/neg/cc1.scala b/tests/neg-custom-args/captures/cc1.scala similarity index 100% rename from tests/neg/cc1.scala rename to tests/neg-custom-args/captures/cc1.scala From c6fc5f3ee190d0f8b221b93ca9b469321aa695e6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Jun 2021 19:30:42 +0200 Subject: [PATCH 44/87] Move capture parameter substitution into normal typer Otherwise we get orphan type parameters at Ycheck --- compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala | 9 ++------- compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala | 8 ++++++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index d9b0b03bb4ed..a7c0014dd57b 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -84,16 +84,11 @@ class CheckCaptures extends RefineTypes: override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = super.typedApply(tree, pt) match case tree1 @ Apply(fn, args) => - val tree2 = fn.tpe.widen match - case mt: MethodType if mt.isCaptureDependent => - tree1.withType(tree1.tpe.substParams(mt, args.tpes)) - case _ => - tree1 if tree.fun.symbol.isConstructor then //println(i"typing $tree1, ${capturedVars(tree1.tpe.classSymbol)}") - tree2.withType(tree2.tpe.capturing(capturedVars(tree1.tpe.classSymbol))) + tree1.withType(tree1.tpe.capturing(capturedVars(tree1.tpe.classSymbol))) else - tree2 + tree1 case tree1 => tree1 end CaptureChecker end CheckCaptures diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index fe26390d8777..b237d9639646 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -311,8 +311,12 @@ trait TypeAssigner { val ownType = fn.tpe.widen match { case fntpe: MethodType => if (sameLength(fntpe.paramInfos, args) || ctx.phase.prev.relaxedTyping) - if (fntpe.isResultDependent) safeSubstParams(fntpe.resultType, fntpe.paramRefs, args.tpes) - else fntpe.resultType + if fntpe.isResultDependent then + safeSubstParams(fntpe.resultType, fntpe.paramRefs, args.tpes) + else if fntpe.isCaptureDependent then + fntpe.resultType.substParams(fntpe, args.tpes) + else + fntpe.resultType else errorType(i"wrong number of arguments at ${ctx.phase.prev} for $fntpe: ${fn.tpe}, expected: ${fntpe.paramInfos.length}, found: ${args.length}", tree.srcPos) case t => From ea7012a42618ef5ca0328301fe1e9c5d89882289 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Jun 2021 11:55:44 +0200 Subject: [PATCH 45/87] More fixes and tests --- .../dotty/tools/dotc/core/Definitions.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 5 +- .../tools/dotc/typer/CheckCaptures.scala | 282 +----------------- .../dotty/tools/dotc/typer/RefineTypes.scala | 3 + tests/neg-custom-args/captures/capt1.scala | 8 + tests/neg-custom-args/captures/try.scala | 39 +++ tests/pos-special/captures/capt1.scala | 7 +- tests/pos-special/captures/try.scala | 26 ++ 8 files changed, 100 insertions(+), 272 deletions(-) create mode 100644 tests/neg-custom-args/captures/try.scala create mode 100644 tests/pos-special/captures/try.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index a46dfd937927..e5ff1824da0b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -443,7 +443,7 @@ class Definitions { @tu lazy val andType: TypeSymbol = enterBinaryAlias(tpnme.AND, AndType(_, _)) @tu lazy val orType: TypeSymbol = enterBinaryAlias(tpnme.OR, OrType(_, _, soft = false)) - @tu lazy val captureRootType: TypeSymbol = enterType(tpnme.CAPTURE_ROOT, TypeBounds.empty) + @tu lazy val captureRootType: TypeSymbol = enterType(tpnme.CAPTURE_ROOT, TypeBounds.empty, Deferred) /** Marker method to indicate an argument to a call-by-name parameter. * Created by byNameClosures and elimByName, eliminated by Erasure, diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index df0060d032ec..37bbff7c1758 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2384,14 +2384,15 @@ object Types { } /** A reference can be tracked if it is - * (1) local, + * (1) a local term ref * (2) a type parameter, * (3) a method term parameter * References to term parameters of classes cannot be tracked individually. * They are subsumed in the capture sets of the enclosing class. */ def canBeTracked(using Context) = - (prefix eq NoPrefix) || symbol.is(TypeParam) || isRootCapability + if isTerm then prefix eq NoPrefix + else symbol.is(TypeParam) || isRootCapability override def isRootCapability(using Context): Boolean = name == tpnme.CAPTURE_ROOT && symbol == defn.captureRootType diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index a7c0014dd57b..d079c9f5debc 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -90,275 +90,23 @@ class CheckCaptures extends RefineTypes: else tree1 case tree1 => tree1 - end CaptureChecker -end CheckCaptures - -/* - def run(using Context): Unit = - refinr.println(i"refine types of ${ctx.compilationUnit}") - val refiner = newRefiner() - val unit = ctx.compilationUnit - val refineCtx = ctx - .fresh - .setMode(Mode.ImplicitsEnabled) - .setTyper(refiner) - val refinedTree = refiner.typedExpr(unit.tpdTree)(using refineCtx) - if ctx.settings.Xprint.value.containsPhase(this) then - report.echo(i"discarded result of $unit after refineTypes:\n\n$refinedTree") - - def preRefinePhase = this.prev.asInstanceOf[PreRefine] - def thisPhase = this - - def newRefiner(): TypeRefiner = TypeRefiner() - - class TypeRefiner extends ReTyper: - import ast.tpd.* - - override def newLikeThis: Typer = new TypeRefiner - - /* override def typedAhead(tree: untpd.Tree, typed: untpd.Tree => Tree)(using Context): Tree = - tree.getAttachment(TypedAhead) match - case Some(ttree) => ttree - case none => - val ttree = typed(tree) - tree.putAttachment(TypedAhead, ttree) - ttree*/ - - /** Update the symbol's info to `newInfo` for the current phase, and - * to the symbol's orginal info for the phase afterwards. - */ - def updateInfo(sym: Symbol, newInfo: Type)(using Context): Unit = - sym.copySymDenotation().installAfter(thisPhase) // reset - sym.copySymDenotation( - info = newInfo, - initFlags = - if newInfo.isInstanceOf[LazyType] then sym.flags &~ Touched - else sym.flags - ).installAfter(preRefinePhase) - - /** A completer for local and provate vals, vars, and defs. Re-infers - * the type from the type of the right-hand side expression. - */ - class RefineCompleter(val original: ValOrDefDef)(using Context) extends LazyType: - def completeInCreationContext(symd: SymDenotation): Unit = - val (paramss, paramFn) = original match - case ddef: DefDef => - val paramss = ddef.paramss.nestedMap(_.symbol) - (paramss, wrapMethodType(_: Type, paramss, isJava = false)) - case _: ValDef => - (Nil, (x: Type) => x) - inContext(ctx.fresh.setOwner(symd.symbol).setTree(original)) { - val rhsType = inferredResultType(original, symd.symbol, paramss, paramFn, WildcardType) - typedAheadType(original.tpt, rhsType) - symd.info = paramFn(rhsType) - } - def complete(symd: SymDenotation)(using Context): Unit = completeInCreationContext(symd) - end RefineCompleter - - /** Update the infos of all symbols defined `trees` that have (result) types - * that need to be reinferred. This is the case if - * 1. the type was inferred originally, and - * 2. the definition is private or local, - * 3. the definition is not a parameter or Java defined - * 4. the definition is not an inline value - * 5. the definition is not a default getter - */ - override def index(trees: List[untpd.Tree])(using Context): Context = - for case tree: ValOrDefDef <- trees.asInstanceOf[List[Tree]] do - val sym = tree.symbol - if tree.tpt.isInstanceOf[untpd.InferredTypeTree] - && (sym.is(Private) || sym.owner.isTerm) - && !sym.isOneOf(Param | JavaDefined) - && !sym.isOneOf(FinalOrInline, butNot = Method | Mutable) - && !sym.name.is(DefaultGetterName) - then - updateInfo(sym, RefineCompleter(tree)) - ctx - - /** Keep the types of all source-written type trees; re-typecheck the rest */ - override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = - trace(i"typed $tree, $pt", refinr, show = true) { - tree.removeAttachment(TypedAhead) match - case Some(ttree) => ttree - case none => - tree match - case _: untpd.TypedSplice - | _: untpd.Thicket - | _: EmptyValDef[?] - | _: untpd.TypeTree => - super.typedUnadapted(tree, pt, locked) - case _ if tree.isType => - promote(tree) - case _ => - super.typedUnadapted(tree, pt, locked) - } - - /** Keep the symbol of the `select` but re-infer its type */ - override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = - val Select(qual, name) = tree - if name.is(OuterSelectName) then promote(tree) - else - val qual1 = withoutMode(Mode.Pattern)(typed(qual, AnySelectionProto)) - val qualType = qual1.tpe.widenIfUnstable - val pre = maybeSkolemizePrefix(qualType, name) - val mbr = qualType.findMember(name, pre, - excluded = if tree.symbol.is(Private) then EmptyFlags else Private) - .suchThat(tree.symbol ==) - val ownType = qualType.select(name, mbr) - untpd.cpy.Select(tree)(qual1, name).withType(ownType) - - /** Set the type of inferred TypeTrees to the expected type. Keep the others unchanged. */ - override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): TypeTree = - if tree.isInstanceOf[untpd.InferredTypeTree] && isFullyDefined(pt, ForceDegree.flipBottom) then - tree.withType(pt) - else - promote(tree) - - /** Redo core steps of type checking from Typer (they were overridden in ReTyper). - * Compare with `typedTyped` in TreeChecker that does essentially the same thing - */ - override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree = - val tpt1 = checkSimpleKinded(typedType(tree.tpt)) - val expr1 = tree.expr match - case id: untpd.Ident if (ctx.mode is Mode.Pattern) && untpd.isVarPattern(id) && (id.name == nme.WILDCARD || id.name == nme.WILDCARD_STAR) => - tree.expr.withType(tpt1.tpe) - case _ => - var pt1 = tpt1.tpe - if pt1.isRepeatedParam then - pt1 = pt1.translateFromRepeated(toArray = tree.expr.typeOpt.derivesFrom(defn.ArrayClass)) - typed(tree.expr, pt1) - untpd.cpy.Typed(tree)(expr1, tpt1).withType(tree.typeOpt) - - /** Replace all type variables in a (possibly embedded) type application - * by fresh, uninstantiated type variables that are pairwise linked with - * the old ones. The type application can either be the toplevel tree `tree` - * or wrapped in one or more closures. - * Replacement insde closures is necessary since sometimes type variables - * bound in a callee are leaked in the parameter types of an enclosing closure - * that infers its parameter types from the callee. - * @return The changed tree with the new type variables, - * and a map from old type variables to corresponding freshly created type variables - */ - private def resetTypeVars[T <: tpd.Tree](tree: T)(using Context): (T, Map[TypeVar, TypeVar]) = tree match - case tree: TypeApply => - val tvars = for arg <- tree.args; case tvar: TypeVar <- arg.tpe :: Nil yield tvar - if tvars.nonEmpty && tvars.length == tree.args.length then - val (args1, tvars1) = - if tvars.head.isInstantiated then - // we are seeing type variables that are not yet copied by a previous resetTypeVars - val args1 = constrained(tree.fun.tpe.widen.asInstanceOf[TypeLambda], tree)._2 - val tvars1 = args1.tpes.asInstanceOf[List[TypeVar]] - for (tvar, tvar1) <- tvars.lazyZip(tvars1) do tvar1.link(tvar) - (args1, tvars1) - else - (tree.args, tvars) - (cpy.TypeApply(tree)(tree.fun, args1).asInstanceOf[T], - tvars1.map(tvar => (tvar.linkedOriginal, tvar)).toMap) - else - (tree, Map.empty) - case tree @ Apply(fn, args) => - val (fn1, map1) = resetTypeVars(fn) - (cpy.Apply(tree)(fn, args).asInstanceOf[T], map1) - case Block(stats, closure: Closure) => - var tvmap: Map[TypeVar, TypeVar] = Map.empty - val stats1 = stats.mapConserve { - case stat: DefDef if stat.symbol == closure.meth.symbol => - val (rhs1, map1) = resetTypeVars(stat.rhs) - tvmap = map1 - cpy.DefDef(stat)(rhs = rhs1) - case stat => stat - } - (cpy.Block(tree)(stats1, closure).asInstanceOf[T], tvmap) - case Block(Nil, expr) => - val (rhs1, map1) = resetTypeVars(expr) - (cpy.Block(tree)(Nil, rhs1).asInstanceOf[T], map1) - case _ => - (tree, Map.empty) - end resetTypeVars - - /** The application with all inferred type arguments reset to fresh type variab;es - * classOf[...] applications are left alone. - */ - override def typedTypeApply(app: untpd.TypeApply, pt: Type)(using Context): Tree = - val app0 = promote(app) - if app0.symbol == defn.Predef_classOf then app0 - else super.typedTypeApply(resetTypeVars(app0)._1, pt) + end CaptureChecker - /** If block is defines closure, replace all parameters that were inferred - * from the expected type by corresponding parts of the new expected type. - * Update infos of parameter symbols and the anonymous function accordingly. - */ - override def typedBlock(blk: untpd.Block, pt: Type)(using Context): Tree = - val blk0 = promote(blk) - val blk1 = blk0.expr match - case closure: Closure => - val stats1 = blk0.stats.mapConserve { - case stat: DefDef if stat.symbol == closure.meth.symbol => - stat.paramss match - case ValDefs(params) :: Nil => - val (protoFormals, _) = decomposeProtoFunction(pt, params.length, stat) - val params1 = params.zipWithConserve(protoFormals) { - case (param @ ValDef(_, tpt: InferredTypeTree, _), formal) - if isFullyDefined(formal, ForceDegree.failBottom) => - updateInfo(param.symbol, formal) - cpy.ValDef(param)(tpt = param.tpt.withType(formal)) - case (param, _) => - param - } - if params eq params1 then stat - else - val mt = stat.symbol.info.asInstanceOf[MethodType] - val formals1 = - for i <- mt.paramInfos.indices.toList yield - if params(i) eq params1(i) then mt.paramInfos(i) else protoFormals(i) - updateInfo(stat.symbol, mt.derivedLambdaType(paramInfos = formals1)) - cpy.DefDef(stat)(paramss = params1 :: Nil) - case _ => - stat - } - cpy.Block(blk0)(stats1, closure) + object PostRefinerCheck extends TreeTraverser: + def traverse(tree: Tree)(using Context) = + tree match + case tree1 @ TypeApply(fn, args) => + for arg <- args do + //println(i"checking $arg in $tree: ${arg.tpe.captureSet}") + if arg.tpe.captureSet.accountsFor(defn.captureRootType.typeRef) then + val argStr = arg match + case arg: InferredTypeTree => i"inferred type argument ${arg.tpe}" + case _ => "type argument" + report.error(s"$argStr is not allowed to capture the root capability *", arg.srcPos) case _ => - blk - super.typedBlock(blk1, pt) + traverseChildren(tree) - /** If tree defines an anonymous function, make sure that any type variables - * defined in the callee rhs are replaced in the function itself. - */ - override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = - sym.ensureCompleted() - if sym.isAnonymousFunction then - val ddef0 = promote(ddef) - val (rhs1, tvmap) = resetTypeVars(ddef0.rhs) - if tvmap.nonEmpty then - val tmap = new TypeMap: - def apply(t: Type) = mapOver { - t match - case t: TypeVar => tvmap.getOrElse(t, t) - case _ => t - } - val ValDefs(params) :: Nil = ddef0.paramss - val params1 = params.mapConserve { param => - updateInfo(param.symbol, tmap(param.symbol.info)) - cpy.ValDef(param)(tpt = param.tpt.withType(tmap(param.tpt.tpe))) - } - val mt = sym.info.asInstanceOf[MethodType] - updateInfo(sym, mt.derivedLambdaType(paramInfos = mt.paramInfos.mapConserve(tmap))) - val ddef1 = cpy.DefDef(ddef0)(paramss = params1 :: Nil, rhs = rhs1) - val nestedCtx = ctx.fresh.setNewTyperState() // needed so that leaked type variables properly nest in their owning typerstate - try inContext(nestedCtx) { super.typedDefDef(ddef1, sym) } - finally nestedCtx.typerState.commit() - else super.typedDefDef(ddef, sym) - else super.typedDefDef(ddef, sym) + def postRefinerCheck(tree: tpd.Tree)(using Context): Unit = PostRefinerCheck.traverse(tree) - override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = - sym.ensureCompleted() - super.typedValDef(vdef, sym) - - override def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree = - if tree.symbol == defn.StdLibPatchesPackage then - promote(tree) // don't check stdlib patches, since their symbols were highjacked by stdlib classes - else - super.typedPackageDef(tree) - end TypeRefiner -*/ +end CheckCaptures diff --git a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala index 4b69c2b8c739..db14d7c859f2 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefineTypes.scala @@ -63,11 +63,13 @@ abstract class RefineTypes extends Phase, IdentityDenotTransformer: val refinedTree = refiner.typedExpr(unit.tpdTree)(using refineCtx) if ctx.settings.Xprint.value.containsPhase(this) then report.echo(i"discarded result of $unit after refineTypes:\n\n$refinedTree") + postRefinerCheck(refinedTree) def preRefinePhase = this.prev.asInstanceOf[PreRefine] def thisPhase = this def newRefiner(): TypeRefiner + def postRefinerCheck(tree: tpd.Tree)(using Context): Unit class TypeRefiner extends ReTyper: import ast.tpd.* @@ -352,6 +354,7 @@ class TestRefineTypes extends RefineTypes: def phaseName: String = "refineTypes" override def isEnabled(using Context) = ctx.settings.YrefineTypes.value def newRefiner() = TypeRefiner() + def postRefinerCheck(tree: tpd.Tree)(using Context): Unit = () diff --git a/tests/neg-custom-args/captures/capt1.scala b/tests/neg-custom-args/captures/capt1.scala index fb898a6f59fc..4b67b1a28045 100644 --- a/tests/neg-custom-args/captures/capt1.scala +++ b/tests/neg-custom-args/captures/capt1.scala @@ -15,6 +15,7 @@ def h2(x: C holds *): Any = class A type Cap = C holds * +type Top = Any holds * def h3(x: Cap): A = class F(y: Int) extends A: @@ -24,3 +25,10 @@ def h3(x: Cap): A = def h4(x: Cap, y: Int): A = new A: def m() = if x == null then y else y // error + +def foo() = + val x: C holds * = ??? + def h[X <:Top](a: X)(b: X) = a + val z2 = h[() => Cap](() => x)(() => C()) // error + val z3 = h(() => x)(() => C()) // error + diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala new file mode 100644 index 000000000000..182f779592af --- /dev/null +++ b/tests/neg-custom-args/captures/try.scala @@ -0,0 +1,39 @@ +import language.experimental.erasedDefinitions + +class CT[E <: Exception] +type CanThrow[E <: Exception] = CT[E] holds * +type Top = Any holds * + +infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R + +class Fail extends Exception + +def raise[E <: Exception](e: E): Nothing throws E = throw e + +def foo(x: Boolean): Int throws Fail = + if x then 1 else raise(Fail()) + +def handle[E <: Exception, R <: Top](op: CanThrow[E] => R)(handler: E => R): R = + val x: CanThrow[E] = ??? + try op(x) + catch case ex: E => handler(ex) + +val _ = handle[Exception, CanThrow[Exception]] { // error + (x: CanThrow[Exception]) => x +}{ + (ex: Exception) => ??? +} + +val _ = handle[Exception, () => Nothing] { + (x: CanThrow[Exception]) => + () => raise(new Exception)(using x) // error +} { + (ex: Exception) => ??? +} + +val _ = handle { + (x: CanThrow[Exception]) => + () => raise(new Exception)(using x) // error +} { + (ex: Exception) => ??? +} diff --git a/tests/pos-special/captures/capt1.scala b/tests/pos-special/captures/capt1.scala index f0f82c8c6e18..77ba34256f01 100644 --- a/tests/pos-special/captures/capt1.scala +++ b/tests/pos-special/captures/capt1.scala @@ -1,6 +1,6 @@ class C type Cap = C holds * - +type Top = Any holds * def f1(c: Cap): () => C holds c.type = () => c // ok def foo() = @@ -9,6 +9,9 @@ def foo() = val x2: (() => C) holds x.type = ??? val y2: () => C holds x.type = x2 - val z: () => Cap = f1(x) + val z1: () => Cap = f1(x) + def h[X <:Top](a: X)(b: X) = a + val z2 = + if x == null then () => x else () => C() diff --git a/tests/pos-special/captures/try.scala b/tests/pos-special/captures/try.scala new file mode 100644 index 000000000000..18d0801267f8 --- /dev/null +++ b/tests/pos-special/captures/try.scala @@ -0,0 +1,26 @@ +import language.experimental.erasedDefinitions + +class CT[E <: Exception] +type CanThrow[E <: Exception] = CT[E] holds * + +infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R + +class Fail extends Exception + +def raise[E <: Exception](e: E): Nothing throws E = throw e + +def foo(x: Boolean): Int throws Fail = + if x then 1 else raise(Fail()) + +def handle[E <: Exception, R](op: (erased CanThrow[E]) => R)(handler: E => R): R = + erased val x: CanThrow[E] = ??? + try op(x) + catch case ex: E => handler(ex) + +val _ = handle { (erased x) => + if true then + raise(new Exception)(using x) + 22 + else + 11 + } \ No newline at end of file From ecaa1c1c5bb07ea68743e42129defa239d24894c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Jun 2021 17:26:59 +0200 Subject: [PATCH 46/87] Fix TypeMap for CapturingTypes --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 37bbff7c1758..875d57c93a1a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5517,7 +5517,7 @@ object Types { else derivedAnnotatedType(tp, underlying1, mapOver(annot)) case tp @ CapturingType(parent, ref) => - derivedCapturing(tp, this(parent), atVariance(0)(this(ref))) + derivedCapturing(tp, this(parent), this(ref)) case _: ThisType | _: BoundType diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index d079c9f5debc..2c9f91f5aca8 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -50,7 +50,7 @@ import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions class CheckCaptures extends RefineTypes: import ast.tpd.* - def phaseName: String = "checkCaptures" + def phaseName: String = "cc" override def isEnabled(using Context) = ctx.settings.Ycc.value def newRefiner() = CaptureChecker() @@ -78,6 +78,7 @@ class CheckCaptures extends RefineTypes: override def typedClosure(tree: untpd.Closure, pt: Type)(using Context): Tree = super.typedClosure(tree, pt) match case tree1: Closure => + println(i"typing closure ${tree1.meth.symbol} with fvs ${capturedVars(tree1.meth.symbol)}") tree1.withType(tree1.tpe.capturing(capturedVars(tree1.meth.symbol))) case tree1 => tree1 From 890aa72071638a3ec30009cd642244362d8d5a0f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 8 Jun 2021 18:09:23 +0200 Subject: [PATCH 47/87] Improve error message --- .../tools/dotc/typer/CheckCaptures.scala | 14 ++++-- tests/neg-custom-args/captures/try.check | 31 ++++++++++++ tests/neg-custom-args/captures/try.scala | 48 ++++++++++++------- 3 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 tests/neg-custom-args/captures/try.check diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index 2c9f91f5aca8..787f2fd7aa49 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -78,7 +78,7 @@ class CheckCaptures extends RefineTypes: override def typedClosure(tree: untpd.Closure, pt: Type)(using Context): Tree = super.typedClosure(tree, pt) match case tree1: Closure => - println(i"typing closure ${tree1.meth.symbol} with fvs ${capturedVars(tree1.meth.symbol)}") + refinr.println(i"typing closure ${tree1.meth.symbol} with fvs ${capturedVars(tree1.meth.symbol)}") tree1.withType(tree1.tpe.capturing(capturedVars(tree1.meth.symbol))) case tree1 => tree1 @@ -101,10 +101,14 @@ class CheckCaptures extends RefineTypes: for arg <- args do //println(i"checking $arg in $tree: ${arg.tpe.captureSet}") if arg.tpe.captureSet.accountsFor(defn.captureRootType.typeRef) then - val argStr = arg match - case arg: InferredTypeTree => i"inferred type argument ${arg.tpe}" - case _ => "type argument" - report.error(s"$argStr is not allowed to capture the root capability *", arg.srcPos) + val notAllowed = " is not allowed to capture the root capability *" + def msg = arg match + case arg: InferredTypeTree => + i"""inferred type argument ${arg.tpe}$notAllowed + | + |The inferred arguments are: [$args%, %]""" + case _ => s"type argument$notAllowed" + report.error(msg, arg.srcPos) case _ => traverseChildren(tree) diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check new file mode 100644 index 000000000000..a209868a8235 --- /dev/null +++ b/tests/neg-custom-args/captures/try.check @@ -0,0 +1,31 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:29:32 ------------------------------------------ +29 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (() => Nothing) holds x + | Required: () => Nothing + +longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:49:2 ------------------------------------------- +44 |val global = handle { +45 | (x: CanThrow[Exception]) => +46 | () => +47 | raise(new Exception)(using x) +48 | 22 +49 |} { // error + | ^ + | Found: (() => Int) holds * + | Required: () => Int +50 | (ex: Exception) => () => 22 +51 |} + +longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/try.scala:22:28 --------------------------------------------------------------- +22 | val a = handle[Exception, CanThrow[Exception]] { // error + | ^^^^^^^^^^^^^^^^^^^ + | type argument is not allowed to capture the root capability * +-- Error: tests/neg-custom-args/captures/try.scala:34:11 --------------------------------------------------------------- +34 | val xx = handle { // error + | ^^^^^^ + | inferred type argument ((() => Int) holds *) is not allowed to capture the root capability * + | + | The inferred arguments are: [Exception, ((() => Int) holds *)] diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index 182f779592af..116a794d5292 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -18,22 +18,34 @@ def handle[E <: Exception, R <: Top](op: CanThrow[E] => R)(handler: E => R): R = try op(x) catch case ex: E => handler(ex) -val _ = handle[Exception, CanThrow[Exception]] { // error - (x: CanThrow[Exception]) => x -}{ - (ex: Exception) => ??? -} - -val _ = handle[Exception, () => Nothing] { - (x: CanThrow[Exception]) => - () => raise(new Exception)(using x) // error -} { - (ex: Exception) => ??? -} - -val _ = handle { +def test = + val a = handle[Exception, CanThrow[Exception]] { // error + (x: CanThrow[Exception]) => x + }{ + (ex: Exception) => ??? + } + + val b = handle[Exception, () => Nothing] { + (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error + } { + (ex: Exception) => ??? + } + + val xx = handle { // error + (x: CanThrow[Exception]) => + () => + raise(new Exception)(using x) + 22 + } { + (ex: Exception) => () => 22 + } + xx + +val global = handle { (x: CanThrow[Exception]) => - () => raise(new Exception)(using x) // error -} { - (ex: Exception) => ??? -} + () => + raise(new Exception)(using x) + 22 +} { // error + (ex: Exception) => () => 22 +} \ No newline at end of file From 662e76b73bd764947adbe74b79af6658fea0e647 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jun 2021 13:41:03 +0200 Subject: [PATCH 48/87] Make "capture `*`" check configurable --- compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index 787f2fd7aa49..5ba700334794 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -94,6 +94,8 @@ class CheckCaptures extends RefineTypes: end CaptureChecker + inline val disallowUniversal = true + object PostRefinerCheck extends TreeTraverser: def traverse(tree: Tree)(using Context) = tree match @@ -112,6 +114,7 @@ class CheckCaptures extends RefineTypes: case _ => traverseChildren(tree) - def postRefinerCheck(tree: tpd.Tree)(using Context): Unit = PostRefinerCheck.traverse(tree) + def postRefinerCheck(tree: tpd.Tree)(using Context): Unit = + if disallowUniversal then PostRefinerCheck.traverse(tree) end CheckCaptures From e273bfabfd49d2e7ac8f1e1927a8472ff35908b5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jun 2021 13:52:46 +0200 Subject: [PATCH 49/87] Update tests --- tests/neg-custom-args/captures/try.check | 27 +++++++++++++++--------- tests/neg-custom-args/captures/try.scala | 7 +++--- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index a209868a8235..82cf3aa74c48 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -5,18 +5,25 @@ | Required: () => Nothing longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:49:2 ------------------------------------------- -44 |val global = handle { -45 | (x: CanThrow[Exception]) => -46 | () => -47 | raise(new Exception)(using x) -48 | 22 -49 |} { // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:43:2 ------------------------------------------- +43 | yy // error + | ^^ + | Found: (yy : List[(xx : (() => Int) holds *)]) + | Required: List[() => Int] + +longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:50:2 ------------------------------------------- +45 |val global = handle { +46 | (x: CanThrow[Exception]) => +47 | () => +48 | raise(new Exception)(using x) +49 | 22 +50 |} { // error | ^ | Found: (() => Int) holds * | Required: () => Int -50 | (ex: Exception) => () => 22 -51 |} +51 | (ex: Exception) => () => 22 +52 |} longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/try.scala:22:28 --------------------------------------------------------------- @@ -24,7 +31,7 @@ longer explanation available when compiling with `-explain` | ^^^^^^^^^^^^^^^^^^^ | type argument is not allowed to capture the root capability * -- Error: tests/neg-custom-args/captures/try.scala:34:11 --------------------------------------------------------------- -34 | val xx = handle { // error +34 | val xx = handle { // error | ^^^^^^ | inferred type argument ((() => Int) holds *) is not allowed to capture the root capability * | diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index 116a794d5292..de058d3ec40c 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -18,7 +18,7 @@ def handle[E <: Exception, R <: Top](op: CanThrow[E] => R)(handler: E => R): R = try op(x) catch case ex: E => handler(ex) -def test = +def test: List[() => Int] = val a = handle[Exception, CanThrow[Exception]] { // error (x: CanThrow[Exception]) => x }{ @@ -31,7 +31,7 @@ def test = (ex: Exception) => ??? } - val xx = handle { // error + val xx = handle { // error (x: CanThrow[Exception]) => () => raise(new Exception)(using x) @@ -39,7 +39,8 @@ def test = } { (ex: Exception) => () => 22 } - xx + val yy = xx :: Nil + yy // error val global = handle { (x: CanThrow[Exception]) => From 3206e9a5206f6fbf7eec4e77bbc2acc8f59f14d4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jun 2021 12:12:00 +0200 Subject: [PATCH 50/87] Fix argForParam --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 875d57c93a1a..41663f926d0b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2497,7 +2497,7 @@ object Types { val tparam = symbol val cls = tparam.owner val base = pre.baseType(cls) - base match { + base.stripped match { case AppliedType(_, allArgs) => var tparams = cls.typeParams var args = allArgs From 3e8b0f6aa9f35ef37f74cab2618508da3d515bdd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jun 2021 12:12:56 +0200 Subject: [PATCH 51/87] Fix printing of function type trees --- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index f83e65499900..54de6a7e13fc 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -510,6 +510,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { changePrec(OrTypePrec) { toText(args(0)) ~ " | " ~ atPrec(OrTypePrec + 1) { toText(args(1)) } } else if (tpt.symbol == defn.andType && args.length == 2) changePrec(AndTypePrec) { toText(args(0)) ~ " & " ~ atPrec(AndTypePrec + 1) { toText(args(1)) } } + else if defn.isFunctionClass(tpt.symbol) + && tpt.isInstanceOf[TypeTree] && tree.hasType && !printDebug + then changePrec(GlobalPrec) { toText(tree.typeOpt) } else args match case arg :: _ if arg.isTerm => toTextLocal(tpt) ~ "(" ~ Text(args.map(argText), ", ") ~ ")" From a537a4014f9074886251dfd60bc01253894f79d6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jun 2021 12:15:31 +0200 Subject: [PATCH 52/87] Declare subcategories of abilities with @ability --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 1 + compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- library/src-bootstrapped/scala/annotation/ability.scala | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 library/src-bootstrapped/scala/annotation/ability.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e5ff1824da0b..cf041c4f4ceb 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -931,6 +931,7 @@ class Definitions { @tu lazy val FunctionalInterfaceAnnot: ClassSymbol = requiredClass("java.lang.FunctionalInterface") @tu lazy val TargetNameAnnot: ClassSymbol = requiredClass("scala.annotation.targetName") @tu lazy val VarargsAnnot: ClassSymbol = requiredClass("scala.annotation.varargs") + @tu lazy val AbilityAnnot: ClassSymbol = requiredClass("scala.annotation.ability") @tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable") diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 41663f926d0b..aa913f53cc98 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2391,7 +2391,7 @@ object Types { * They are subsumed in the capture sets of the enclosing class. */ def canBeTracked(using Context) = - if isTerm then prefix eq NoPrefix + if isTerm then (prefix eq NoPrefix) || symbol.hasAnnotation(defn.AbilityAnnot) else symbol.is(TypeParam) || isRootCapability override def isRootCapability(using Context): Boolean = diff --git a/library/src-bootstrapped/scala/annotation/ability.scala b/library/src-bootstrapped/scala/annotation/ability.scala new file mode 100644 index 000000000000..150a62ee00c7 --- /dev/null +++ b/library/src-bootstrapped/scala/annotation/ability.scala @@ -0,0 +1,8 @@ +package scala.annotation + +/** An annotation inidcating that a val should be tracked as its own ability. + * Example: + * + * @ability erased val canThrow: * = ??? + */ +class ability extends StaticAnnotation \ No newline at end of file From 6c5a1cb02a65a2c0f9aceecaeaf8967c18189a95 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jun 2021 14:03:54 +0200 Subject: [PATCH 53/87] Rename holds -> retains --- .../dotty/tools/dotc/core/Definitions.scala | 2 +- .../src/dotty/tools/dotc/core/StdNames.scala | 2 +- .../tools/dotc/core/tasty/TreePickler.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- .../tools/dotc/printing/PlainPrinter.scala | 2 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- .../scala/runtime/stdLibPatches/Predef.scala | 3 ++- tests/neg-custom-args/captures/capt1.scala | 14 +++++------ tests/neg-custom-args/captures/capt2.scala | 6 ++--- tests/neg-custom-args/captures/cc1.scala | 2 +- tests/neg-custom-args/captures/try.check | 10 ++++---- tests/neg-custom-args/captures/try.scala | 4 +-- tests/pos-special/captures/capt1.scala | 25 +++++++++++++------ tests/pos-special/captures/try.scala | 2 +- tests/pos/capturing.scala | 8 +++--- 15 files changed, 49 insertions(+), 37 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index cf041c4f4ceb..2cd2dbd9c5ef 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -474,7 +474,7 @@ class Definitions { @tu lazy val Predef_classOf : Symbol = ScalaPredefModule.requiredMethod(nme.classOf) @tu lazy val Predef_identity : Symbol = ScalaPredefModule.requiredMethod(nme.identity) @tu lazy val Predef_undefined: Symbol = ScalaPredefModule.requiredMethod(nme.???) - @tu lazy val Predef_holdsType: Symbol = ScalaPredefModule.requiredType(tpnme.holds) + @tu lazy val Predef_retainsType: Symbol = ScalaPredefModule.requiredType(tpnme.retains) @tu lazy val ScalaPredefModuleClass: ClassSymbol = ScalaPredefModule.moduleClass.asClass @tu lazy val SubTypeClass: ClassSymbol = requiredClass("scala.<:<") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 96fc1e789944..bd8a9ffa554e 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -497,7 +497,6 @@ object StdNames { val hash_ : N = "hash" val head: N = "head" val higherKinds: N = "higherKinds" - val holds: N = "holds" val identity: N = "identity" val implicitConversions: N = "implicitConversions" val implicitly: N = "implicitly" @@ -570,6 +569,7 @@ object StdNames { val reflectiveSelectable: N = "reflectiveSelectable" val reify : N = "reify" val releaseFence : N = "releaseFence" + val retains: N = "retains" val rootMirror : N = "rootMirror" val run: N = "run" val runOrElse: N = "runOrElse" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 9596ba1d25e8..d3dab429d121 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -291,7 +291,7 @@ class TreePickler(pickler: TastyPickler) { case tp: CapturingType => writeByte(APPLIEDtype) withLength { - pickleType(defn.Predef_holdsType.typeRef) + pickleType(defn.Predef_retainsType.typeRef) pickleType(tp.parent) pickleType(tp.ref) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 4b07e82beb5c..dbc7e9644954 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -360,7 +360,7 @@ class TreeUnpickler(reader: TastyReader, val tycon = readType() val args = until(end)(readType()) tycon match - case tycon: TypeRef if tycon.symbol == defn.Predef_holdsType => + case tycon: TypeRef if tycon.symbol == defn.Predef_retainsType => if ctx.settings.Ycc.value then CapturingType.checked(args(0), args(1)) else args(0) case _ => diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 9b8510539f0f..120c954cd68a 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -188,7 +188,7 @@ class PlainPrinter(_ctx: Context) extends Printer { (" <: " ~ toText(bound) provided !bound.isAny) }.close case CapturingType(parent, ref) => - changePrec(InfixPrec)(toText(parent) ~ " holds " ~ toTextCaptureRef(ref)) + changePrec(InfixPrec)(toText(parent) ~ " retains " ~ toTextCaptureRef(ref)) case tp: PreviousErrorType if ctx.settings.XprintTypes.value => "" // do not print previously reported error message because they may try to print this error type again recuresevely case tp: ErrorType => diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index b237d9639646..e568eecea159 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -208,7 +208,7 @@ trait TypeAssigner { val constr = tycon.typeSymbol if constr == defn.andType then AndType(args(0), args(1)) else if constr == defn.orType then OrType(args(0), args(1), soft = false) - else if constr == defn.Predef_holdsType then + else if constr == defn.Predef_retainsType then if ctx.settings.Ycc.value then captType(args(0), args(1)) else args(0) else tp diff --git a/library/src/scala/runtime/stdLibPatches/Predef.scala b/library/src/scala/runtime/stdLibPatches/Predef.scala index ecabcf9960ba..1e9dc2303155 100644 --- a/library/src/scala/runtime/stdLibPatches/Predef.scala +++ b/library/src/scala/runtime/stdLibPatches/Predef.scala @@ -49,5 +49,6 @@ object Predef: scala.runtime.Scala3RunTime.nn(x) /** type `A` with capture set `B` */ - infix type holds[A, B] + infix type retains[A, B] + end Predef diff --git a/tests/neg-custom-args/captures/capt1.scala b/tests/neg-custom-args/captures/capt1.scala index 4b67b1a28045..a7f2b0925dca 100644 --- a/tests/neg-custom-args/captures/capt1.scala +++ b/tests/neg-custom-args/captures/capt1.scala @@ -1,21 +1,21 @@ class C -def f(x: C holds *, y: C): () => C = +def f(x: C retains *, y: C): () => C = () => if x == null then y else y // error -def g(x: C holds *, y: C): Any = +def g(x: C retains *, y: C): Any = () => if x == null then y else y // error -def h1(x: C holds *, y: C): Any holds x.type = +def h1(x: C retains *, y: C): Any retains x.type = def f() = if x == null then y else y () => f() // ok -def h2(x: C holds *): Any = +def h2(x: C retains *): Any = def f(y: Int) = if x == null then y else y f // error class A -type Cap = C holds * -type Top = Any holds * +type Cap = C retains * +type Top = Any retains * def h3(x: Cap): A = class F(y: Int) extends A: @@ -27,7 +27,7 @@ def h4(x: Cap, y: Int): A = def m() = if x == null then y else y // error def foo() = - val x: C holds * = ??? + val x: C retains * = ??? def h[X <:Top](a: X)(b: X) = a val z2 = h[() => Cap](() => x)(() => C()) // error val z3 = h(() => x)(() => C()) // error diff --git a/tests/neg-custom-args/captures/capt2.scala b/tests/neg-custom-args/captures/capt2.scala index 12157c06f9a6..31c549828ad0 100644 --- a/tests/neg-custom-args/captures/capt2.scala +++ b/tests/neg-custom-args/captures/capt2.scala @@ -1,8 +1,8 @@ class C -type Cap = C holds * +type Cap = C retains * -def f1(c: Cap): (() => C holds c.type) = () => c // ok -def f2(c: Cap): (() => C) holds c.type = () => c // error +def f1(c: Cap): (() => C retains c.type) = () => c // ok +def f2(c: Cap): (() => C) retains c.type = () => c // error def h5(x: Cap): () => C = f1(x) // error diff --git a/tests/neg-custom-args/captures/cc1.scala b/tests/neg-custom-args/captures/cc1.scala index e116c33bab30..41098a9a3ab6 100644 --- a/tests/neg-custom-args/captures/cc1.scala +++ b/tests/neg-custom-args/captures/cc1.scala @@ -1,4 +1,4 @@ object Test: - def f[A <: Any holds *](x: A): Any = x // error + def f[A <: Any retains *](x: A): Any = x // error diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 82cf3aa74c48..6e07f063f50d 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,14 +1,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:29:32 ------------------------------------------ 29 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (() => Nothing) holds x + | Found: (() => Nothing) retains x | Required: () => Nothing longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:43:2 ------------------------------------------- 43 | yy // error | ^^ - | Found: (yy : List[(xx : (() => Int) holds *)]) + | Found: (yy : List[(xx : (() => Int) retains *)]) | Required: List[() => Int] longer explanation available when compiling with `-explain` @@ -20,7 +20,7 @@ longer explanation available when compiling with `-explain` 49 | 22 50 |} { // error | ^ - | Found: (() => Int) holds * + | Found: (() => Int) retains * | Required: () => Int 51 | (ex: Exception) => () => 22 52 |} @@ -33,6 +33,6 @@ longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/try.scala:34:11 --------------------------------------------------------------- 34 | val xx = handle { // error | ^^^^^^ - | inferred type argument ((() => Int) holds *) is not allowed to capture the root capability * + | inferred type argument ((() => Int) retains *) is not allowed to capture the root capability * | - | The inferred arguments are: [Exception, ((() => Int) holds *)] + | The inferred arguments are: [Exception, ((() => Int) retains *)] diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index de058d3ec40c..4784d055fccc 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -1,8 +1,8 @@ import language.experimental.erasedDefinitions class CT[E <: Exception] -type CanThrow[E <: Exception] = CT[E] holds * -type Top = Any holds * +type CanThrow[E <: Exception] = CT[E] retains * +type Top = Any retains * infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R diff --git a/tests/pos-special/captures/capt1.scala b/tests/pos-special/captures/capt1.scala index 77ba34256f01..cada7aa41de5 100644 --- a/tests/pos-special/captures/capt1.scala +++ b/tests/pos-special/captures/capt1.scala @@ -1,13 +1,24 @@ class C -type Cap = C holds * -type Top = Any holds * -def f1(c: Cap): () => C holds c.type = () => c // ok +type Cap = C retains * +type Top = Any retains * +def f1(c: Cap): () => C retains c.type = () => c // ok + +def f2: Int = + val g: (Boolean => Int) retains * = ??? + val x = g(true) + x + +def f3: Int = + def g: (Boolean => Int) retains * = ??? + def h = g + val x = g.apply(true) + x def foo() = - val x: C holds * = ??? - val y: C holds x.type = x - val x2: (() => C) holds x.type = ??? - val y2: () => C holds x.type = x2 + val x: C retains * = ??? + val y: C retains x.type = x + val x2: (() => C) retains x.type = ??? + val y2: () => C retains x.type = x2 val z1: () => Cap = f1(x) def h[X <:Top](a: X)(b: X) = a diff --git a/tests/pos-special/captures/try.scala b/tests/pos-special/captures/try.scala index 18d0801267f8..e251ed72e48d 100644 --- a/tests/pos-special/captures/try.scala +++ b/tests/pos-special/captures/try.scala @@ -1,7 +1,7 @@ import language.experimental.erasedDefinitions class CT[E <: Exception] -type CanThrow[E <: Exception] = CT[E] holds * +type CanThrow[E <: Exception] = CT[E] retains * infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R diff --git a/tests/pos/capturing.scala b/tests/pos/capturing.scala index 693e51952e38..edadde2758df 100644 --- a/tests/pos/capturing.scala +++ b/tests/pos/capturing.scala @@ -1,8 +1,8 @@ object Test: - extension [A <: Any holds *] (xs: LazyList[A]) - def lazyMap[B <: Any holds *] (f: A => B holds *): LazyList[B] holds f.type | A | B = - val x: Int holds f.type | A = ??? + extension [A <: Any retains *] (xs: LazyList[A]) + def lazyMap[B <: Any retains *] (f: A => B retains *): LazyList[B] retains f.type | A | B = + val x: Int retains f.type | A = ??? val y = x - val z: Int holds A holds f.type = y + val z: Int retains A retains f.type = y ??? From 2b609946c192c82cf8c470fa13ca9bc3dc3a45d9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jun 2021 13:33:29 +0200 Subject: [PATCH 54/87] Define capture sets of classes --- .../dotty/tools/dotc/core/CaptureSet.scala | 24 +++++++++ .../dotty/tools/dotc/core/Definitions.scala | 2 + .../src/dotty/tools/dotc/core/Types.scala | 4 ++ library/src-bootstrapped/scala/Retains.scala | 7 +++ tests/neg-custom-args/captures/try2.check | 28 ++++++++++ tests/neg-custom-args/captures/try2.scala | 54 +++++++++++++++++++ 6 files changed, 119 insertions(+) create mode 100644 library/src-bootstrapped/scala/Retains.scala create mode 100644 tests/neg-custom-args/captures/try2.check create mode 100644 tests/neg-custom-args/captures/try2.scala diff --git a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala index 89f01ae13450..305fac856b7e 100644 --- a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala @@ -34,6 +34,9 @@ case class CaptureSet private (elems: CaptureSet.Refs) extends Showable: myClosure = cl myClosure + def ++ (that: CaptureSet): CaptureSet = + CaptureSet(elems ++ that.elems) + /** {x} <:< this where <:< is subcapturing */ def accountsFor(x: CaptureRef)(using Context) = elems.contains(x) || !x.isRootCapability && x.captureSetOfInfo <:< this @@ -59,6 +62,25 @@ object CaptureSet: if elems.isEmpty then empty else CaptureSet(SimpleIdentitySet(elems.map(_.normalizedRef)*)) + def ofClass(cinfo: ClassInfo, argTypes: List[Type])(using Context): CaptureSet = + def captureSetOf(tp: Type): CaptureSet = tp match + case tp: TypeRef if tp.symbol.is(ParamAccessor) => + def mapArg(accs: List[Symbol], tps: List[Type]): CaptureSet = accs match + case acc :: accs1 if tps.nonEmpty => + if acc == tp.symbol then tps.head.captureSet + else mapArg(accs1, tps.tail) + case _ => + empty + mapArg(cinfo.cls.paramAccessors, argTypes) + case _ => + tp.captureSet + val css = + for + parent <- cinfo.parents if parent.classSymbol == defn.RetainsClass + arg <- parent.argInfos + yield captureSetOf(arg) + css.foldLeft(empty)(_ ++ _) + def ofType(tp: Type)(using Context): CaptureSet = val collect = new TypeAccumulator[Refs]: var localBinders: SimpleIdentitySet[BindingType] = SimpleIdentitySet.empty @@ -84,6 +106,8 @@ object CaptureSet: if variance >= 0 then elems1 + ref else elems1 case TypeBounds(_, hi) => apply(elems, hi) + case tp: ClassInfo => + elems ++ ofClass(tp, Nil).elems case tp: LazyRef => if seenLazyRefs.contains(tp) || tp.evaluating // shapeless gets an assertion error without this test diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2cd2dbd9c5ef..25dc0602ae76 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -879,6 +879,8 @@ class Definitions { lazy val RuntimeTuples_isInstanceOfEmptyTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfEmptyTuple") lazy val RuntimeTuples_isInstanceOfNonEmptyTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfNonEmptyTuple") + @tu lazy val RetainsClass: ClassSymbol = requiredClass("scala.Retains") + // Annotation base classes @tu lazy val AnnotationClass: ClassSymbol = requiredClass("scala.annotation.Annotation") @tu lazy val ClassfileAnnotationClass: ClassSymbol = requiredClass("scala.annotation.ClassfileAnnotation") diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index aa913f53cc98..b9f7c8fe541b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2852,6 +2852,10 @@ object Types { override def canBeTracked(using Context) = cls.owner.isTerm + override def captureSetOfInfo(using Context): CaptureSet = + super.captureSetOfInfo + ++ CaptureSet.ofClass(cls.classInfo, cls.paramAccessors.map(_.info)) + override def computeHash(bs: Binders): Int = doHash(bs, tref) override def eql(that: Type): Boolean = that match { diff --git a/library/src-bootstrapped/scala/Retains.scala b/library/src-bootstrapped/scala/Retains.scala new file mode 100644 index 000000000000..ebb825ecd12b --- /dev/null +++ b/library/src-bootstrapped/scala/Retains.scala @@ -0,0 +1,7 @@ +package scala + +/** Parent trait that indicates capturing. Example usage: + * + * class Foo(using ctx: Context) extends Holds[ctx | CanThrow[Exception]] + */ +trait Retains[T] diff --git a/tests/neg-custom-args/captures/try2.check b/tests/neg-custom-args/captures/try2.check new file mode 100644 index 000000000000..1544b62f24db --- /dev/null +++ b/tests/neg-custom-args/captures/try2.check @@ -0,0 +1,28 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try2.scala:31:32 ----------------------------------------- +31 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (() => Nothing) retains x + | Required: () => Nothing + +longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try2.scala:45:2 ------------------------------------------ +45 | yy // error + | ^^ + | Found: (yy : List[(xx : (() => Int) retains canThrow)]) + | Required: List[() => Int] + +longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try2.scala:52:2 ------------------------------------------ +47 |val global = handle { +48 | (x: CanThrow[Exception]) => +49 | () => +50 | raise(new Exception)(using x) +51 | 22 +52 |} { // error + | ^ + | Found: (() => Int) retains canThrow + | Required: () => Int +53 | (ex: Exception) => () => 22 +54 |} + +longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/try2.scala b/tests/neg-custom-args/captures/try2.scala new file mode 100644 index 000000000000..fde0c5a218a7 --- /dev/null +++ b/tests/neg-custom-args/captures/try2.scala @@ -0,0 +1,54 @@ +import language.experimental.erasedDefinitions +import annotation.ability + +@ability erased val canThrow: List[*] = ??? + +class CanThrow[E <: Exception] extends Retains[canThrow.type] +type Top = Any retains * + +infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R + +class Fail extends Exception + +def raise[E <: Exception](e: E): Nothing throws E = throw e + +def foo(x: Boolean): Int throws Fail = + if x then 1 else raise(Fail()) + +def handle[E <: Exception, R <: Top](op: CanThrow[E] => R)(handler: E => R): R = + val x: CanThrow[E] = ??? + try op(x) + catch case ex: E => handler(ex) + +def test: List[() => Int] = + val a = handle[Exception, CanThrow[Exception]] { // ok + (x: CanThrow[Exception]) => x + }{ + (ex: Exception) => ??? + } + + val b = handle[Exception, () => Nothing] { + (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error + } { + (ex: Exception) => ??? + } + + val xx = handle { + (x: CanThrow[Exception]) => + () => + raise(new Exception)(using x) + 22 + } { + (ex: Exception) => () => 22 + } + val yy = xx :: Nil + yy // error + +val global = handle { + (x: CanThrow[Exception]) => + () => + raise(new Exception)(using x) + 22 +} { // error + (ex: Exception) => () => 22 +} From 64d68756976643ae8d55dc0256f678b46f0ff546 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jun 2021 14:09:04 +0200 Subject: [PATCH 55/87] Test curried tries --- tests/neg-custom-args/captures/try3.scala | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/neg-custom-args/captures/try3.scala diff --git a/tests/neg-custom-args/captures/try3.scala b/tests/neg-custom-args/captures/try3.scala new file mode 100644 index 000000000000..96480757413d --- /dev/null +++ b/tests/neg-custom-args/captures/try3.scala @@ -0,0 +1,26 @@ +import java.io.IOException + +class CanThrow[E] extends Retains[*] +type Top = Any retains * + +def handle[E <: Exception, T <: Top](op: CanThrow[E] ?=> T)(handler: E => T): T = + val x: CanThrow[E] = ??? + try op(using x) + catch case ex: E => handler(ex) + +def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = + throw ex + +@main def Test: Int = + def f(a: Boolean) = // error + handle { + if !a then raise(IOException()) + (b: Boolean) => + if !b then raise(IOException()) + 0 + } { + ex => (b: Boolean) => -1 + } + val g = f(true) + g(false) // would raise an uncaught exception + f(true)(false) // would raise an uncaught exception From efeec2d0a3f9ef841e005b3e69b2a0d6a7973971 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jun 2021 16:06:18 +0200 Subject: [PATCH 56/87] Simplify singleton subtype rule As discussed in meeting today, it looks like we can drop a precondition. --- .../src/dotty/tools/dotc/core/TypeComparer.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 097645ac0521..e439e4cb4989 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -805,18 +805,18 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case _ => false comparePaths || { val tp2n = tp1 match - case tp1: CaptureRef if tp1.isTracked && tp2.captureSet.accountsFor(tp1) => + case tp1: CaptureRef if tp1.isTracked => // New rule dealing with singleton types on the left: // - // E |- x: S {x} <: cv(T) E |- S <: {*} T - // ------------------------------------------ - // E |- x.type <:< T + // E |- x: S E |- S <: {*} T + // --------------------------- + // E |- x.type <:< T // // Note: This would map to the following (Var) rule in deep capture calculus: // - // E |- x: S {x} <: cv(T) E |- S <: {*} T - // ------------------------------------------ - // E |- x: {x} T + // E |- x: S E |- S <: {*} T + // --------------------------- + // E |- x: {x} T // CapturingType(tp2, defn.captureRootType.typeRef) case _ => tp2 From 17501391ebf176e6435885a66d2e4fd4691ed05d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jun 2021 16:07:29 +0200 Subject: [PATCH 57/87] Refuse to instantiate type variables with other global capabilities --- .github/workflows/ci.yaml | 6 +-- .../tools/dotc/typer/CheckCaptures.scala | 27 ++++++---- tests/neg-custom-args/captures/try.check | 4 +- tests/neg-custom-args/captures/try2.check | 10 ++++ tests/neg-custom-args/captures/try2.scala | 4 +- tests/neg-custom-args/captures/try3.scala | 4 +- tests/pos-special/captures/try3.scala | 51 +++++++++++++++++++ 7 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 tests/pos-special/captures/try3.scala diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9ecdb9634a72..9ceda5f9715c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -114,9 +114,9 @@ jobs: ./project/scripts/sbt ";scala3-bootstrapped/compile ;scala3-bootstrapped/test;sjsSandbox/run;sjsSandbox/test;sjsJUnitTests/test;sjsCompilerTests/test ;sbt-test/scripted scala2-compat/* ;configureIDE ;stdlib-bootstrapped/test:run ;stdlib-bootstrapped-tasty-tests/test" ./project/scripts/bootstrapCmdTests - - name: MiMa - run: | - ./project/scripts/sbt ";scala3-interfaces/mimaReportBinaryIssues ;scala3-library-bootstrapped/mimaReportBinaryIssues ;scala3-library-bootstrappedJS/mimaReportBinaryIssues" + #- name: MiMa + # run: | + # ./project/scripts/sbt ";scala3-interfaces/mimaReportBinaryIssues ;scala3-library-bootstrapped/mimaReportBinaryIssues ;scala3-library-bootstrappedJS/mimaReportBinaryIssues" test_windows_fast: runs-on: [self-hosted, Windows] diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index 5ba700334794..a93e702ec3ac 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -94,7 +94,7 @@ class CheckCaptures extends RefineTypes: end CaptureChecker - inline val disallowUniversal = true + inline val disallowGlobal = true object PostRefinerCheck extends TreeTraverser: def traverse(tree: Tree)(using Context) = @@ -102,19 +102,24 @@ class CheckCaptures extends RefineTypes: case tree1 @ TypeApply(fn, args) => for arg <- args do //println(i"checking $arg in $tree: ${arg.tpe.captureSet}") - if arg.tpe.captureSet.accountsFor(defn.captureRootType.typeRef) then - val notAllowed = " is not allowed to capture the root capability *" - def msg = arg match - case arg: InferredTypeTree => - i"""inferred type argument ${arg.tpe}$notAllowed - | - |The inferred arguments are: [$args%, %]""" - case _ => s"type argument$notAllowed" - report.error(msg, arg.srcPos) + for ref <- arg.tpe.captureSet.elems do + val isGlobal = ref match + case ref: TypeRef => ref.isRootCapability + case ref: TermRef => ref.prefix != NoPrefix && ref.symbol.hasAnnotation(defn.AbilityAnnot) + val what = if ref.isRootCapability then "universal" else "global" + if isGlobal then + val notAllowed = i" is not allowed to capture the $what capability $ref" + def msg = arg match + case arg: InferredTypeTree => + i"""inferred type argument ${arg.tpe}$notAllowed + | + |The inferred arguments are: [$args%, %]""" + case _ => s"type argument$notAllowed" + report.error(msg, arg.srcPos) case _ => traverseChildren(tree) def postRefinerCheck(tree: tpd.Tree)(using Context): Unit = - if disallowUniversal then PostRefinerCheck.traverse(tree) + if disallowGlobal then PostRefinerCheck.traverse(tree) end CheckCaptures diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 6e07f063f50d..2a28933971c7 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -29,10 +29,10 @@ longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/try.scala:22:28 --------------------------------------------------------------- 22 | val a = handle[Exception, CanThrow[Exception]] { // error | ^^^^^^^^^^^^^^^^^^^ - | type argument is not allowed to capture the root capability * + | type argument is not allowed to capture the universal capability * -- Error: tests/neg-custom-args/captures/try.scala:34:11 --------------------------------------------------------------- 34 | val xx = handle { // error | ^^^^^^ - | inferred type argument ((() => Int) retains *) is not allowed to capture the root capability * + | inferred type argument ((() => Int) retains *) is not allowed to capture the universal capability * | | The inferred arguments are: [Exception, ((() => Int) retains *)] diff --git a/tests/neg-custom-args/captures/try2.check b/tests/neg-custom-args/captures/try2.check index 1544b62f24db..55af935635e7 100644 --- a/tests/neg-custom-args/captures/try2.check +++ b/tests/neg-custom-args/captures/try2.check @@ -26,3 +26,13 @@ longer explanation available when compiling with `-explain` 54 |} longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/try2.scala:24:28 -------------------------------------------------------------- +24 | val a = handle[Exception, CanThrow[Exception]] { // error + | ^^^^^^^^^^^^^^^^^^^ + | type argument is not allowed to capture the global capability (canThrow : List[*]) +-- Error: tests/neg-custom-args/captures/try2.scala:36:11 -------------------------------------------------------------- +36 | val xx = handle { // error + | ^^^^^^ + |inferred type argument ((() => Int) retains canThrow) is not allowed to capture the global capability (canThrow : List[*]) + | + |The inferred arguments are: [Exception, ((() => Int) retains canThrow)] diff --git a/tests/neg-custom-args/captures/try2.scala b/tests/neg-custom-args/captures/try2.scala index fde0c5a218a7..3b7bee8224a9 100644 --- a/tests/neg-custom-args/captures/try2.scala +++ b/tests/neg-custom-args/captures/try2.scala @@ -21,7 +21,7 @@ def handle[E <: Exception, R <: Top](op: CanThrow[E] => R)(handler: E => R): R = catch case ex: E => handler(ex) def test: List[() => Int] = - val a = handle[Exception, CanThrow[Exception]] { // ok + val a = handle[Exception, CanThrow[Exception]] { // error (x: CanThrow[Exception]) => x }{ (ex: Exception) => ??? @@ -33,7 +33,7 @@ def test: List[() => Int] = (ex: Exception) => ??? } - val xx = handle { + val xx = handle { // error (x: CanThrow[Exception]) => () => raise(new Exception)(using x) diff --git a/tests/neg-custom-args/captures/try3.scala b/tests/neg-custom-args/captures/try3.scala index 96480757413d..f90ad6aff0aa 100644 --- a/tests/neg-custom-args/captures/try3.scala +++ b/tests/neg-custom-args/captures/try3.scala @@ -12,8 +12,8 @@ def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = throw ex @main def Test: Int = - def f(a: Boolean) = // error - handle { + def f(a: Boolean) = + handle { // error if !a then raise(IOException()) (b: Boolean) => if !b then raise(IOException()) diff --git a/tests/pos-special/captures/try3.scala b/tests/pos-special/captures/try3.scala new file mode 100644 index 000000000000..1d1e9925da33 --- /dev/null +++ b/tests/pos-special/captures/try3.scala @@ -0,0 +1,51 @@ +import language.experimental.erasedDefinitions +import annotation.ability +import java.io.IOException + +class CanThrow[E] extends Retains[*] +type Top = Any retains * + +def handle[E <: Exception, T <: Top](op: CanThrow[E] ?=> T)(handler: E => T): T = + val x: CanThrow[E] = ??? + try op(using x) + catch case ex: E => handler(ex) + +def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = + throw ex + +def test1: Int = + def f(a: Boolean): Boolean => CanThrow[IOException] ?=> Int = + handle { // error + if !a then raise(IOException()) + (b: Boolean) => (_: CanThrow[IOException]) ?=> + if !b then raise(IOException()) + 0 + } { + ex => (b: Boolean) => (_: CanThrow[IOException]) ?=> -1 + } + handle { + val g = f(true) + g(false) // would raise an uncaught exception + f(true)(false) // would raise an uncaught exception + } { + ex => -1 + } +/* +def test2: Int = + def f(a: Boolean): Boolean => CanThrow[IOException] ?=> Int = + handle { // error + if !a then raise(IOException()) + (b: Boolean) => + if !b then raise(IOException()) + 0 + } { + ex => (b: Boolean) => -1 + } + handle { + val g = f(true) + g(false) // would raise an uncaught exception + f(true)(false) // would raise an uncaught exception + } { + ex => -1 + } +*/ \ No newline at end of file From f5611e398da8ea393a5bb8d868ae42a013b0eaff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 11 Jun 2021 23:02:53 +0200 Subject: [PATCH 58/87] Fixes and well-formedness check for capture-dependent types --- .../src/dotty/tools/dotc/core/Types.scala | 12 +++++----- .../tools/dotc/typer/CheckCaptures.scala | 24 ++++++++++++++++++- .../dotty/tools/dotc/typer/TypeAssigner.scala | 6 ++--- tests/neg-custom-args/captures/capt-wf.scala | 7 ++++++ tests/pos-special/captures/capt-depfun.scala | 11 +++++++++ 5 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 tests/neg-custom-args/captures/capt-wf.scala create mode 100644 tests/pos-special/captures/capt-depfun.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b9f7c8fe541b..b8dd2434a071 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3661,7 +3661,8 @@ object Types { /** Does result type contain references to parameters of this method type, * which cannot be eliminated by de-aliasing? */ - def isResultDependent(using Context): Boolean = dependencyStatus == TrueDeps + def isResultDependent(using Context): Boolean = + dependencyStatus == TrueDeps || dependencyStatus == CaptureDeps /** Does one of the parameter types contain references to earlier parameters * of this method type which cannot be eliminated by de-aliasing? @@ -3671,13 +3672,13 @@ object Types { /** Is there either a true or false type dependency, or does the result * type capture a parameter? */ - def isCaptureDependent(using Context) = dependencyStatus >= CaptureDeps + def isCaptureDependent(using Context) = dependencyStatus == CaptureDeps def newParamRef(n: Int): TermParamRef = new TermParamRefImpl(this, n) /** The least supertype of `resultType` that does not contain parameter dependencies */ def nonDependentResultApprox(using Context): Type = - if (isResultDependent) { + if isResultDependent then val dropDependencies = new ApproximatingTypeMap { def apply(tp: Type) = tp match { case tp @ TermParamRef(thisLambdaType, _) => @@ -3686,7 +3687,6 @@ object Types { } } dropDependencies(resultType) - } else resultType } @@ -4054,8 +4054,8 @@ object Types { type DependencyStatus = Byte final val Unknown: DependencyStatus = 0 // not yet computed final val NoDeps: DependencyStatus = 1 // no dependent parameters found - final val CaptureDeps: DependencyStatus = 2 - final val FalseDeps: DependencyStatus = 3 // all dependent parameters are prefixes of non-depended alias types + final val FalseDeps: DependencyStatus = 2 // all dependent parameters are prefixes of non-depended alias types + final val CaptureDeps: DependencyStatus = 3 final val TrueDeps: DependencyStatus = 4 // some truly dependent parameters exist final val StatusMask: DependencyStatus = 7 // the bits indicating actual dependency status final val Provisional: DependencyStatus = 8 // set if dependency status can still change due to type variable instantiations diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index a93e702ec3ac..adda5f16c57f 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -18,7 +18,7 @@ import Trees._ import scala.util.control.NonFatal import typer.ErrorReporting._ import util.Spans.Span -import util.SimpleIdentitySet +import util.{SimpleIdentitySet, SrcPos} import util.Chars.* import Nullables._ import transform.* @@ -96,6 +96,24 @@ class CheckCaptures extends RefineTypes: inline val disallowGlobal = true + def checkWellFormed(whole: Type, pos: SrcPos)(using Context): Unit = + def checkRelativeVariance(mt: MethodType) = new TypeTraverser: + def traverse(tp: Type): Unit = tp match + case CapturingType(parent, ref @ TermParamRef(`mt`, _)) => + if variance <= 0 then + val direction = if variance < 0 then "contra" else "in" + report.error(em"captured reference $ref appears ${direction}variantly in type $whole", pos) + traverse(parent) + case _ => + traverseChildren(tp) + val checkVariance = new TypeTraverser: + def traverse(tp: Type): Unit = tp match + case mt: MethodType if mt.isResultDependent => + checkRelativeVariance(mt).traverse(mt) + case _ => + traverseChildren(tp) + checkVariance.traverse(whole) + object PostRefinerCheck extends TreeTraverser: def traverse(tree: Tree)(using Context) = tree match @@ -116,6 +134,10 @@ class CheckCaptures extends RefineTypes: |The inferred arguments are: [$args%, %]""" case _ => s"type argument$notAllowed" report.error(msg, arg.srcPos) + case tree: TypeTree => + // it's inferred, no need to check + case tree: TypTree => + checkWellFormed(tree.tpe, tree.srcPos) case _ => traverseChildren(tree) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index e568eecea159..140bff353a10 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -311,10 +311,10 @@ trait TypeAssigner { val ownType = fn.tpe.widen match { case fntpe: MethodType => if (sameLength(fntpe.paramInfos, args) || ctx.phase.prev.relaxedTyping) - if fntpe.isResultDependent then - safeSubstParams(fntpe.resultType, fntpe.paramRefs, args.tpes) - else if fntpe.isCaptureDependent then + if fntpe.isCaptureDependent then fntpe.resultType.substParams(fntpe, args.tpes) + else if fntpe.isResultDependent then + safeSubstParams(fntpe.resultType, fntpe.paramRefs, args.tpes) else fntpe.resultType else diff --git a/tests/neg-custom-args/captures/capt-wf.scala b/tests/neg-custom-args/captures/capt-wf.scala new file mode 100644 index 000000000000..55c15c7fd056 --- /dev/null +++ b/tests/neg-custom-args/captures/capt-wf.scala @@ -0,0 +1,7 @@ +class C +type Cap = C retains * +type Top = Any retains * + +type T = (x: Cap) => List[String retains x.type] => Unit // error +val x: (x: Cap) => Array[String retains x.type] = ??? // error +val y = x diff --git a/tests/pos-special/captures/capt-depfun.scala b/tests/pos-special/captures/capt-depfun.scala new file mode 100644 index 000000000000..ac69d72c1af2 --- /dev/null +++ b/tests/pos-special/captures/capt-depfun.scala @@ -0,0 +1,11 @@ +class C +type Cap = C retains * +type Top = Any retains * + +type T = (x: Cap) => String retains x.type + +def f(y: Cap): String retains * = + val a: T = (x: Cap) => "" + val b = a(y) + val c: String retains y.type = b + c From 0341f7fd8f381354970a930605278493a02e55a1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 12 Jun 2021 09:46:17 +0200 Subject: [PATCH 59/87] Also check well-formedness of method signatures --- .../tools/dotc/typer/CheckCaptures.scala | 31 +++++++++++++------ tests/neg-custom-args/captures/capt-wf.scala | 6 ++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index adda5f16c57f..6b8d05e80431 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -96,20 +96,21 @@ class CheckCaptures extends RefineTypes: inline val disallowGlobal = true + def checkRelativeVariance(mt: MethodType, whole: Type, pos: SrcPos)(using Context) = new TypeTraverser: + def traverse(tp: Type): Unit = tp match + case CapturingType(parent, ref @ TermParamRef(`mt`, _)) => + if variance <= 0 then + val direction = if variance < 0 then "contra" else "in" + report.error(em"captured reference $ref appears ${direction}variantly in type $whole", pos) + traverse(parent) + case _ => + traverseChildren(tp) + def checkWellFormed(whole: Type, pos: SrcPos)(using Context): Unit = - def checkRelativeVariance(mt: MethodType) = new TypeTraverser: - def traverse(tp: Type): Unit = tp match - case CapturingType(parent, ref @ TermParamRef(`mt`, _)) => - if variance <= 0 then - val direction = if variance < 0 then "contra" else "in" - report.error(em"captured reference $ref appears ${direction}variantly in type $whole", pos) - traverse(parent) - case _ => - traverseChildren(tp) val checkVariance = new TypeTraverser: def traverse(tp: Type): Unit = tp match case mt: MethodType if mt.isResultDependent => - checkRelativeVariance(mt).traverse(mt) + checkRelativeVariance(mt, whole, pos).traverse(mt) case _ => traverseChildren(tp) checkVariance.traverse(whole) @@ -138,6 +139,16 @@ class CheckCaptures extends RefineTypes: // it's inferred, no need to check case tree: TypTree => checkWellFormed(tree.tpe, tree.srcPos) + case tree: DefDef => + def check(tp: Type): Unit = tp match + case tp: PolyType => + check(tp.resType) + case mt: MethodType => + if mt.isResultDependent then + checkRelativeVariance(mt, tree.symbol.info, ctx.source.atSpan(tree.nameSpan)).traverse(mt) + check(mt.resType) + case _ => + check(tree.symbol.info) case _ => traverseChildren(tree) diff --git a/tests/neg-custom-args/captures/capt-wf.scala b/tests/neg-custom-args/captures/capt-wf.scala index 55c15c7fd056..0998b7d57bb8 100644 --- a/tests/neg-custom-args/captures/capt-wf.scala +++ b/tests/neg-custom-args/captures/capt-wf.scala @@ -5,3 +5,9 @@ type Top = Any retains * type T = (x: Cap) => List[String retains x.type] => Unit // error val x: (x: Cap) => Array[String retains x.type] = ??? // error val y = x + +def test: Unit = + def f(x: Cap) = // error + val g = (xs: List[String retains x.type]) => () + g + () From e15bfc4992dffbe1d994a8f71f245aa39eabcff1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 12 Jun 2021 15:08:24 +0200 Subject: [PATCH 60/87] Add List encoding test --- .../dotty/tools/dotc/core/CaptureSet.scala | 2 +- .../pos-special/captures/list-encoding.scala | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/pos-special/captures/list-encoding.scala diff --git a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala index 305fac856b7e..cf1d1aae21fa 100644 --- a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala @@ -85,7 +85,7 @@ object CaptureSet: val collect = new TypeAccumulator[Refs]: var localBinders: SimpleIdentitySet[BindingType] = SimpleIdentitySet.empty var seenLazyRefs: SimpleIdentitySet[LazyRef] = SimpleIdentitySet.empty - def apply(elems: Refs, tp: Type): Refs = trace(i"capt $elems, $tp", show = true) { + def apply(elems: Refs, tp: Type): Refs = trace(i"capt $elems, $tp", capt, show = true) { tp match case tp: NamedType => if variance < 0 then elems diff --git a/tests/pos-special/captures/list-encoding.scala b/tests/pos-special/captures/list-encoding.scala new file mode 100644 index 000000000000..eda95898de65 --- /dev/null +++ b/tests/pos-special/captures/list-encoding.scala @@ -0,0 +1,22 @@ +type Top = Any retains * +class Cap extends Retains[*] + +type Op[T <: Top, C <: Top] = + ((v: T) => ((s: C) => C) retains *) retains * + +type List[T <: Top] = + [C <: Top] => (op: Op[T, C]) => ((s: C) => C) retains op.type + +def nil[T <: Top]: List[T] = + [C <: Top] => (op: Op[T, C]) => (s: C) => s + +def cons[T <: Top](hd: T, tl: List[T]): List[T] = + [C <: Top] => (op: Op[T, C]) => (s: C) => op(hd)(tl(op)(s)) + +def foo(c: Cap) = + def f(x: String retains c.type, y: String retains c.type) = + cons(x, cons(y, nil)) + def g(x: String retains c.type, y: Any) = + cons(x, cons(y, nil)) + def h(x: String, y: Any retains c.type) = + cons(x, cons(y, nil)) From 4e349ccea2a9826ea8f8257382f36ae0568c3150 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 12 Jun 2021 19:09:36 +0200 Subject: [PATCH 61/87] Fix joins and join approximations of capturing types --- .../dotty/tools/dotc/core/TypeComparer.scala | 4 ++-- .../src/dotty/tools/dotc/core/TypeOps.scala | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e439e4cb4989..a03b1797383f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2090,8 +2090,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if (tp1 eq tp2) tp1 else if (!tp1.exists) tp1 else if (!tp2.exists) tp2 - else if tp1.isAny && !tp2.isLambdaSub || tp1.isAnyKind || isBottom(tp2) then tp1 - else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || isBottom(tp1) then tp2 + else if tp1.isAny && !tp2.isLambdaSub && tp2.noCaptures || tp1.isAnyKind || isBottom(tp2) then tp1 + else if tp2.isAny && !tp1.isLambdaSub && tp1.noCaptures || tp2.isAnyKind || isBottom(tp1) then tp2 else def mergedLub(tp1: Type, tp2: Type): Type = { tp1.atoms match diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 39aacf8aacab..dfdc16a8a6d2 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -266,15 +266,23 @@ object TypeOps: case _ => false } - // Step 1: Get RecTypes and ErrorTypes out of the way, + // Step 1: Get RecTypes and ErrorTypes and CapturingTypes out of the way, tp1 match { - case tp1: RecType => return tp1.rebind(approximateOr(tp1.parent, tp2)) - case err: ErrorType => return err + case tp1: RecType => + return tp1.rebind(approximateOr(tp1.parent, tp2)) + case tp1: CapturingType => + return tp1.derivedCapturingType(approximateOr(tp1.parent, tp2), tp1.ref) + case err: ErrorType => + return err case _ => } tp2 match { - case tp2: RecType => return tp2.rebind(approximateOr(tp1, tp2.parent)) - case err: ErrorType => return err + case tp2: RecType => + return tp2.rebind(approximateOr(tp1, tp2.parent)) + case tp2: CapturingType => + return tp2.derivedCapturingType(approximateOr(tp1, tp2.parent), tp2.ref) + case err: ErrorType => + return err case _ => } From e96f6536eaaa790eed57504eac523c937523ef2e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 15 Jun 2021 11:18:47 +0200 Subject: [PATCH 62/87] Target minor version --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index fc89ad68e251..97d07e11edc9 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -65,7 +65,7 @@ object DottyJSPlugin extends AutoPlugin { object Build { val referenceVersion = "3.0.0" - val baseVersion = "3.0.2-RC1" + val baseVersion = "3.1.0-RC1" // Versions used by the vscode extension to create a new project // This should be the latest published releases. From 8af8dbcf0f5aac153769d5167159db0ddfb0a0fc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 15 Jun 2021 11:37:10 +0200 Subject: [PATCH 63/87] Fix rebase breakage --- .../dotc/transform/DependencyCollector.scala | 266 ------------------ 1 file changed, 266 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala diff --git a/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala b/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala deleted file mode 100644 index 150d248af6ed..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/DependencyCollector.scala +++ /dev/null @@ -1,266 +0,0 @@ -package dotty.tools.dotc -package transform - -import MegaPhase._ -import core.Denotations.NonSymSingleDenotation -import core.DenotTransformers._ -import core.Symbols._ -import core.Contexts._ -import core.Types._ -import core.Flags._ -import core.Decorators._ -import core.StdNames.nme -import core.Names._ -import core.NameOps._ -import core.NameKinds.ExpandPrefixName -import ast.Trees._ -import SymUtils._ -import ExplicitOuter.outer -import util.Store -import collection.mutable -import collection.mutable.{ HashMap, HashSet, LinkedHashMap, TreeSet } - -abstract class DependencyCollector: - import ast.tpd._ - - def enclosure(using Context): Symbol - def isExpr(sym: Symbol)(using Context): Boolean - - protected type SymSet = TreeSet[Symbol] - - /** A map storing free variables of functions and classes */ - val free: mutable.LinkedHashMap[Symbol, SymSet] = new LinkedHashMap - - /** A hashtable storing calls between functions */ - val called = new LinkedHashMap[Symbol, SymSet] - - /** A map from local methods and classes to the owners to which they will be lifted as members. - * For methods and classes that do not have any dependencies this will be the enclosing package. - * symbols with packages as lifted owners will subsequently represented as static - * members of their toplevel class, unless their enclosing class was already static. - * Note: During tree transform (which runs at phase LambdaLift + 1), liftedOwner - * is also used to decide whether a method had a term owner before. - */ - val liftedOwner = new LinkedHashMap[Symbol, Symbol] - - /** A flag to indicate whether new free variables have been found */ - private var changedFreeVars: Boolean = _ - - /** A flag to indicate whether lifted owners have changed */ - private var changedLiftedOwner: Boolean = _ - - private val ord: Ordering[Symbol] = Ordering.by(_.id) - private def newSymSet = TreeSet.empty[Symbol](ord) - - private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet = - f.getOrElseUpdate(sym, newSymSet) - - def freeVars(sym: Symbol): List[Symbol] = free.get(sym) match - case Some(set) => set.toList - case None => Nil - - /** A symbol is local if it is owned by a term or a local trait, - * or if it is a constructor of a local symbol. - * Note: we count members of local traits as local since their free variables - * have to be passed on from their callers. By contrast, class members get their - * free variable proxies from their enclosing class. - */ - def isLocal(sym: Symbol)(using Context): Boolean = - val owner = sym.maybeOwner - owner.isTerm - || owner.is(Trait) && isLocal(owner) - || sym.isConstructor && isLocal(owner) - - /** Set `liftedOwner(sym)` to `owner` if `owner` is more deeply nested - * than the previous value of `liftedowner(sym)`. - */ - def narrowLiftedOwner(sym: Symbol, owner: Symbol)(using Context): Unit = - if sym.maybeOwner.isTerm - && owner.isProperlyContainedIn(liftedOwner(sym)) - && owner != sym - then - report.log(i"narrow lifted $sym to $owner") - changedLiftedOwner = true - liftedOwner(sym) = owner - - private class NoPath extends Exception - - /** Mark symbol `sym` as being free in `enclosure`, unless `sym` is defined - * in `enclosure` or there is an intermediate class properly containing `enclosure` - * in which `sym` is also free. Also, update `liftedOwner` of `enclosure` so - * that `enclosure` can access `sym`, or its proxy in an intermediate class. - * This means: - * - * 1. If there is an intermediate class in which `sym` is free, `enclosure` - * must be contained in that class (in order to access the `sym proxy stored - * in the class). - * - * 2. If there is no intermediate class, `enclosure` must be contained - * in the class enclosing `sym`. - * - * @return If there is a non-trait class between `enclosure` and - * the owner of `sym`, the largest such class. - * Otherwise, if there is a trait between `enclosure` and - * the owner of `sym`, the largest such trait. - * Otherwise, NoSymbol. - * - * @pre sym.owner.isTerm, (enclosure.isMethod || enclosure.isClass) - * - * The idea of `markFree` is illustrated with an example: - * - * def f(x: int) = { - * class C { - * class D { - * val y = x - * } - * } - * } - * - * In this case `x` is free in the primary constructor of class `C`. - * but it is not free in `D`, because after lambda lift the code would be transformed - * as follows: - * - * def f(x$0: int) { - * class C(x$0: int) { - * val x$1 = x$0 - * class D { - * val y = outer.x$1 - * } - * } - * } - */ - private def markFree(sym: Symbol, enclosure: Symbol)(using Context): Symbol = try { - if (!enclosure.exists) throw new NoPath - if (enclosure == sym.enclosure) NoSymbol - else { - def nestedInConstructor(sym: Symbol): Boolean = - sym.isConstructor || - sym.isTerm && nestedInConstructor(sym.enclosure) - report.debuglog(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure") - val intermediate = - if (enclosure.is(PackageClass)) enclosure - else if (enclosure.isConstructor) markFree(sym, enclosure.owner.enclosure) - else markFree(sym, enclosure.enclosure) - if (intermediate.exists) narrowLiftedOwner(enclosure, intermediate) - if !intermediate.isRealClass || nestedInConstructor(enclosure) then - // Constructors and methods nested inside traits get the free variables - // of the enclosing trait or class. - // Conversely, local traits do not get free variables. - // Methods inside constructors also don't have intermediates, - // need to get all their free variables passed directly. - if (!enclosure.is(Trait)) - if (symSet(free, enclosure).add(sym)) { - changedFreeVars = true - report.log(i"$sym is free in $enclosure") - } - if (intermediate.isRealClass) intermediate - else if (enclosure.isRealClass) enclosure - else if (intermediate.isClass) intermediate - else if (enclosure.isClass) enclosure - else NoSymbol - } - } - catch { - case ex: NoPath => - println(i"error lambda lifting ${ctx.compilationUnit}: $sym is not visible from $enclosure") - throw ex - } - - private def markCalled(callee: Symbol, caller: Symbol)(using Context): Unit = { - report.debuglog(i"mark called: $callee of ${callee.owner} is called by $caller in ${caller.owner}") - assert(isLocal(callee)) - symSet(called, caller) += callee - } - - def process(tree: Tree)(using Context) = - val sym = tree.symbol - - def narrowTo(thisClass: ClassSymbol) = - val enclMethod = enclosure - val enclClass = enclMethod.enclosingClass - narrowLiftedOwner(enclMethod, - if enclClass.isContainedIn(thisClass) then thisClass - else enclClass) // unknown this reference, play it safe and assume the narrowest possible owner - - tree match - case tree: Ident => - if isLocal(sym) then - if isExpr(sym) then markCalled(sym, enclosure) - else if sym.isTerm then markFree(sym, enclosure) - def captureImplicitThis(x: Type): Unit = x match - case tr@TermRef(x, _) if !tr.termSymbol.isStatic => captureImplicitThis(x) - case x: ThisType if !x.tref.typeSymbol.isStaticOwner => narrowTo(x.tref.typeSymbol.asClass) - case _ => - captureImplicitThis(tree.tpe) - case tree: Select => - if isExpr(sym) && isLocal(sym) then markCalled(sym, enclosure) - case tree: This => - narrowTo(tree.symbol.asClass) - case tree: DefDef => - if sym.owner.isTerm then - liftedOwner(sym) = sym.enclosingPackageClass - // this will make methods in supercall constructors of top-level classes owned - // by the enclosing package, which means they will be static. - // On the other hand, all other methods will be indirectly owned by their - // top-level class. This avoids possible deadlocks when a static method - // has to access its enclosing object from the outside. - else if sym.isConstructor then - if sym.isPrimaryConstructor && isLocal(sym.owner) && !sym.owner.is(Trait) then - // add a call edge from the constructor of a local non-trait class to - // the class itself. This is done so that the constructor inherits - // the free variables of the class. - symSet(called, sym) += sym.owner - case tree: TypeDef => - if sym.owner.isTerm then liftedOwner(sym) = sym.topLevelClass.owner - case _ => - end process - - private class CollectDependencies extends TreeTraverser: - def traverse(tree: Tree)(using Context) = - try - process(tree) - traverseChildren(tree) - catch case ex: Exception => - println(i"$ex while traversing $tree") - throw ex - - /** Compute final free variables map `fvs by closing over caller dependencies. */ - private def computeFreeVars()(using Context): Unit = - while - changedFreeVars = false - for - caller <- called.keys - callee <- called(caller) - fvs <- free get callee - fv <- fvs - do - markFree(fv, caller) - changedFreeVars - do () - - /** Compute final liftedOwner map by closing over caller dependencies */ - private def computeLiftedOwners()(using Context): Unit = - while - changedLiftedOwner = false - for - caller <- called.keys - callee <- called(caller) - do - val normalizedCallee = callee.skipConstructor - val calleeOwner = normalizedCallee.owner - if calleeOwner.isTerm then narrowLiftedOwner(caller, liftedOwner(normalizedCallee)) - else - assert(calleeOwner.is(Trait)) - // methods nested inside local trait methods cannot be lifted out - // beyond the trait. Note that we can also call a trait method through - // a qualifier; in that case no restriction to lifted owner arises. - if caller.isContainedIn(calleeOwner) then - narrowLiftedOwner(caller, calleeOwner) - changedLiftedOwner - do () - - def collectDependencies()(using Context): Unit = - CollectDependencies().traverse(ctx.compilationUnit.tpdTree) - computeFreeVars() - computeLiftedOwners() -end DependencyCollector From 639361bd37b4f14042a4293dadec9fd71039f574 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jun 2021 15:09:56 +0200 Subject: [PATCH 64/87] Allow curried methods that refer to capture refs contravariantly Delay capture checking until the point where such a method is eta-converted. --- .../tools/dotc/typer/CheckCaptures.scala | 34 ++++++++----------- tests/neg-custom-args/captures/capt-wf.scala | 7 +++- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index 6b8d05e80431..4fceb72046ef 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -96,21 +96,20 @@ class CheckCaptures extends RefineTypes: inline val disallowGlobal = true - def checkRelativeVariance(mt: MethodType, whole: Type, pos: SrcPos)(using Context) = new TypeTraverser: - def traverse(tp: Type): Unit = tp match - case CapturingType(parent, ref @ TermParamRef(`mt`, _)) => - if variance <= 0 then - val direction = if variance < 0 then "contra" else "in" - report.error(em"captured reference $ref appears ${direction}variantly in type $whole", pos) - traverse(parent) - case _ => - traverseChildren(tp) - def checkWellFormed(whole: Type, pos: SrcPos)(using Context): Unit = + def checkRelativeVariance(mt: MethodType) = new TypeTraverser: + def traverse(tp: Type): Unit = tp match + case CapturingType(parent, ref @ TermParamRef(`mt`, _)) => + if variance <= 0 then + val direction = if variance < 0 then "contra" else "in" + report.error(em"captured reference $ref appears ${direction}variantly in type $whole", pos) + traverse(parent) + case _ => + traverseChildren(tp) val checkVariance = new TypeTraverser: def traverse(tp: Type): Unit = tp match case mt: MethodType if mt.isResultDependent => - checkRelativeVariance(mt, whole, pos).traverse(mt) + checkRelativeVariance(mt).traverse(mt) case _ => traverseChildren(tp) checkVariance.traverse(whole) @@ -118,7 +117,7 @@ class CheckCaptures extends RefineTypes: object PostRefinerCheck extends TreeTraverser: def traverse(tree: Tree)(using Context) = tree match - case tree1 @ TypeApply(fn, args) => + case tree1 @ TypeApply(fn, args) if disallowGlobal => for arg <- args do //println(i"checking $arg in $tree: ${arg.tpe.captureSet}") for ref <- arg.tpe.captureSet.elems do @@ -137,22 +136,17 @@ class CheckCaptures extends RefineTypes: report.error(msg, arg.srcPos) case tree: TypeTree => // it's inferred, no need to check - case tree: TypTree => + case _: TypTree | _: Closure => checkWellFormed(tree.tpe, tree.srcPos) case tree: DefDef => def check(tp: Type): Unit = tp match - case tp: PolyType => - check(tp.resType) - case mt: MethodType => - if mt.isResultDependent then - checkRelativeVariance(mt, tree.symbol.info, ctx.source.atSpan(tree.nameSpan)).traverse(mt) - check(mt.resType) + case tp: MethodOrPoly => check(tp.resType) case _ => check(tree.symbol.info) case _ => traverseChildren(tree) def postRefinerCheck(tree: tpd.Tree)(using Context): Unit = - if disallowGlobal then PostRefinerCheck.traverse(tree) + PostRefinerCheck.traverse(tree) end CheckCaptures diff --git a/tests/neg-custom-args/captures/capt-wf.scala b/tests/neg-custom-args/captures/capt-wf.scala index 0998b7d57bb8..41ed177e853d 100644 --- a/tests/neg-custom-args/captures/capt-wf.scala +++ b/tests/neg-custom-args/captures/capt-wf.scala @@ -7,7 +7,12 @@ val x: (x: Cap) => Array[String retains x.type] = ??? // error val y = x def test: Unit = - def f(x: Cap) = // error + def f(x: Cap) = // ok val g = (xs: List[String retains x.type]) => () g + def f2(x: Cap)(xs: List[String retains x.type]) = () + val x = f // error + val x2 = f2 // error + val y = f(C()) // ok + val y2 = f2(C()) // ok () From 6cc124ed0d2f1be000df498abfe84c3a454c0fd7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jun 2021 16:16:00 +0200 Subject: [PATCH 65/87] Avoid abbreviation in warning --- compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 140bff353a10..b083361fe6b2 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -190,7 +190,7 @@ trait TypeAssigner { case ref: NamedType => if ref.isTracked then if tp.captureSet.accountsFor(ref) then - report.warning(em"redundant capture: $tp already contains $ref with cs ${ref.captureSet} in its capture set ${tp.captureSet}", tree.srcPos) + report.warning(em"redundant capture: $tp already contains $ref with capture set ${ref.captureSet} in its capture set ${tp.captureSet}", tree.srcPos) CapturingType(tp, ref) else val reason = From de62914b14b23ab358ac265a0165cad535f5f858 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jun 2021 17:18:52 +0200 Subject: [PATCH 66/87] Tests for strict and lazy maps over boxes --- tests/neg-custom-args/captures/boxmap.check | 10 ++++++++++ tests/neg-custom-args/captures/boxmap.scala | 15 +++++++++++++++ tests/pos-special/captures/boxmap.scala | 21 +++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 tests/neg-custom-args/captures/boxmap.check create mode 100644 tests/neg-custom-args/captures/boxmap.scala create mode 100644 tests/pos-special/captures/boxmap.scala diff --git a/tests/neg-custom-args/captures/boxmap.check b/tests/neg-custom-args/captures/boxmap.check new file mode 100644 index 000000000000..64b3af36b66e --- /dev/null +++ b/tests/neg-custom-args/captures/boxmap.check @@ -0,0 +1,10 @@ +-- [E007] Type Mismatch Error: boxmap.scala:15:2 ------------------------------- +15 | () => b[Box[B]]((x: A) => box(f(x))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (() => Box[B]) retains b retains f + | Required: () => Box[B] + | + | where: B is a type in method lazymap with bounds <: Top + +longer explanation available when compiling with `-explain` +1 error found diff --git a/tests/neg-custom-args/captures/boxmap.scala b/tests/neg-custom-args/captures/boxmap.scala new file mode 100644 index 000000000000..e8beeb62efae --- /dev/null +++ b/tests/neg-custom-args/captures/boxmap.scala @@ -0,0 +1,15 @@ +type Top = Any retains * +class Cap extends Retains[*] + +infix type ==> [A, B] = (A => B) retains * + +type Box[+T <: Top] = [K <: Top] => (T ==> K) => K + +def box[T <: Top](x: T): Box[T] = + [K <: Top] => (k: T ==> K) => k(x) + +def map[A <: Top, B <: Top](b: Box[A])(f: A ==> B): Box[B] = + b[Box[B]]((x: A) => box(f(x))) + +def lazymap[A <: Top, B <: Top](b: Box[A])(f: A ==> B): () => Box[B] =//retains b.type | f.type = + () => b[Box[B]]((x: A) => box(f(x))) diff --git a/tests/pos-special/captures/boxmap.scala b/tests/pos-special/captures/boxmap.scala new file mode 100644 index 000000000000..adda37f6c03a --- /dev/null +++ b/tests/pos-special/captures/boxmap.scala @@ -0,0 +1,21 @@ +type Top = Any retains * +class Cap extends Retains[*] + +infix type ==> [A, B] = (A => B) retains * + +type Box[+T <: Top] = [K <: Top] => (T ==> K) => K + +def box[T <: Top](x: T): Box[T] = + [K <: Top] => (k: T ==> K) => k(x) + +def map[A <: Top, B <: Top](b: Box[A])(f: A ==> B): Box[B] = + b[Box[B]]((x: A) => box(f(x))) + +def lazymap[A <: Top, B <: Top](b: Box[A])(f: A ==> B): (() => Box[B]) retains b.type | f.type = + () => b[Box[B]]((x: A) => box(f(x))) + +def test[A <: Top, B <: Top] = + def lazymap[A <: Top, B <: Top](b: Box[A])(f: A ==> B) = + () => b[Box[B]]((x: A) => box(f(x))) + val x: (b: Box[A]) => (f: A ==> B) => (() => Box[B]) retains b.type | f.type = lazymap[A, B] + () From 9fb45c965af504a40caf74ac7a06fdca037ed016 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jun 2021 17:36:09 +0200 Subject: [PATCH 67/87] Fix neg test --- tests/neg-custom-args/captures/boxmap.check | 11 +++++------ tests/neg-custom-args/captures/boxmap.scala | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/neg-custom-args/captures/boxmap.check b/tests/neg-custom-args/captures/boxmap.check index 64b3af36b66e..bbf1879464de 100644 --- a/tests/neg-custom-args/captures/boxmap.check +++ b/tests/neg-custom-args/captures/boxmap.check @@ -1,10 +1,9 @@ --- [E007] Type Mismatch Error: boxmap.scala:15:2 ------------------------------- -15 | () => b[Box[B]]((x: A) => box(f(x))) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (() => Box[B]) retains b retains f +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/boxmap.scala:15:2 ---------------------------------------- +15 | () => b[Box[B]]((x: A) => box(f(x))) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (() => Box[B]) retains b retains f | Required: () => Box[B] | - | where: B is a type in method lazymap with bounds <: Top + | where: B is a type in method lazymap with bounds <: Top longer explanation available when compiling with `-explain` -1 error found diff --git a/tests/neg-custom-args/captures/boxmap.scala b/tests/neg-custom-args/captures/boxmap.scala index e8beeb62efae..d61f2ca0e357 100644 --- a/tests/neg-custom-args/captures/boxmap.scala +++ b/tests/neg-custom-args/captures/boxmap.scala @@ -11,5 +11,5 @@ def box[T <: Top](x: T): Box[T] = def map[A <: Top, B <: Top](b: Box[A])(f: A ==> B): Box[B] = b[Box[B]]((x: A) => box(f(x))) -def lazymap[A <: Top, B <: Top](b: Box[A])(f: A ==> B): () => Box[B] =//retains b.type | f.type = - () => b[Box[B]]((x: A) => box(f(x))) +def lazymap[A <: Top, B <: Top](b: Box[A])(f: A ==> B): () => Box[B] = + () => b[Box[B]]((x: A) => box(f(x))) // error From c03b577d3e8a7c587a04a2264f28991d0aacd6b5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jun 2021 16:43:42 +0200 Subject: [PATCH 68/87] Revert "Target minor version" This reverts commit e96f6536eaaa790eed57504eac523c937523ef2e. Since we are now our own branch on origin, there's no need to deviate in targeting a minor version. We are targeting no specific version at the present time, so there's no need to deviate from the standard build --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 97d07e11edc9..fc89ad68e251 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -65,7 +65,7 @@ object DottyJSPlugin extends AutoPlugin { object Build { val referenceVersion = "3.0.0" - val baseVersion = "3.1.0-RC1" + val baseVersion = "3.0.2-RC1" // Versions used by the vscode extension to create a new project // This should be the latest published releases. From 7433f3facc947ae71b58faa55bee955363a81c26 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jun 2021 20:45:17 +0200 Subject: [PATCH 69/87] Drop implementation restriction for polymorphic functions Drop the restriction that the type parameters must be following at least one value parameter clause. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 50 ++++++++++--------- .../dotty/tools/dotc/core/TypeErasure.scala | 6 ++- .../dotty/tools/dotc/parsing/Parsers.scala | 18 +------ tests/neg/i2887b.scala | 4 +- .../{neg => pos}/polymorphic-functions.scala | 0 5 files changed, 35 insertions(+), 43 deletions(-) rename tests/{neg => pos}/polymorphic-functions.scala (100%) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 870af91e083c..8522fcd781b9 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1632,12 +1632,12 @@ object desugar { } } - def makePolyFunction(targs: List[Tree], body: Tree): Tree = body match { + def makePolyFunction(targs: List[Tree], body: Tree): Tree = body match case Parens(body1) => makePolyFunction(targs, body1) case Block(Nil, body1) => makePolyFunction(targs, body1) - case Function(vargs, res) => + case _ => assert(targs.nonEmpty) // TODO: Figure out if we need a `PolyFunctionWithMods` instead. val mods = body match { @@ -1646,33 +1646,37 @@ object desugar { } val polyFunctionTpt = ref(defn.PolyFunctionType) val applyTParams = targs.asInstanceOf[List[TypeDef]] - if (ctx.mode.is(Mode.Type)) { + if ctx.mode.is(Mode.Type) then // Desugar [T_1, ..., T_M] -> (P_1, ..., P_N) => R // Into scala.PolyFunction { def apply[T_1, ..., T_M](x$1: P_1, ..., x$N: P_N): R } - - val applyVParams = vargs.zipWithIndex.map { - case (p: ValDef, _) => p.withAddedFlags(mods.flags) - case (p, n) => makeSyntheticParameter(n + 1, p).withAddedFlags(mods.flags) - } + val (res, applyVParamss) = body match + case Function(vargs, res) => + ( res, + vargs.zipWithIndex.map { + case (p: ValDef, _) => p.withAddedFlags(mods.flags) + case (p, n) => makeSyntheticParameter(n + 1, p).withAddedFlags(mods.flags) + } :: Nil + ) + case _ => + (body, Nil) RefinedTypeTree(polyFunctionTpt, List( - DefDef(nme.apply, applyTParams :: applyVParams :: Nil, res, EmptyTree) + DefDef(nme.apply, applyTParams :: applyVParamss, res, EmptyTree) )) - } - else { + else // Desugar [T_1, ..., T_M] -> (x_1: P_1, ..., x_N: P_N) => body // Into new scala.PolyFunction { def apply[T_1, ..., T_M](x_1: P_1, ..., x_N: P_N) = body } - - val applyVParams = vargs.asInstanceOf[List[ValDef]] - .map(varg => varg.withAddedFlags(mods.flags | Param)) - New(Template(emptyConstructor, List(polyFunctionTpt), Nil, EmptyValDef, - List(DefDef(nme.apply, applyTParams :: applyVParams :: Nil, TypeTree(), res)) - )) - } - case _ => - // may happen for erroneous input. An error will already have been reported. - assert(ctx.reporter.errorsReported) - EmptyTree - } + val (res, applyVParamss) = body match + case Function(vargs, res) => + ( res, + vargs.asInstanceOf[List[ValDef]] + .map(varg => varg.withAddedFlags(mods.flags | Param)) + :: Nil + ) + case _ => + (body, Nil) + New(Template(emptyConstructor, List(polyFunctionTpt), Nil, EmptyValDef, + List(DefDef(nme.apply, applyTParams :: applyVParamss, TypeTree(), res)) + )) // begin desugar diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 3a077407d0b5..9218877c3f11 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -549,6 +549,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst * - otherwise, if T is a type parameter coming from Java, []Object * - otherwise, Object * - For a term ref p.x, the type # x. + * - For a refined type scala.PolyFunction { def apply[...]: R }, scala.Function0 * - For a refined type scala.PolyFunction { def apply[...](x_1, ..., x_N): R }, scala.FunctionN * - For a typeref scala.Any, scala.AnyVal, scala.Singleton, scala.Tuple, or scala.*: : |java.lang.Object| * - For a typeref scala.Unit, |scala.runtime.BoxedUnit|. @@ -600,8 +601,9 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst assert(refinedInfo.isInstanceOf[PolyType]) val res = refinedInfo.resultType val paramss = res.paramNamess - assert(paramss.length == 1) - this(defn.FunctionType(paramss.head.length, isContextual = res.isImplicitMethod, isErased = res.isErasedMethod)) + val arity = if paramss.isEmpty then 0 else paramss.head.length + assert(arity <= 1) + this(defn.FunctionType(arity, isContextual = res.isImplicitMethod, isErased = res.isErasedMethod)) case tp: TypeProxy => this(tp.underlying) case tp @ AndType(tp1, tp2) => diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 8f0a57dc2fc8..cf49704619de 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1434,14 +1434,7 @@ object Parsers { else if (in.token == ARROW) { val arrowOffset = in.skipToken() val body = toplevelTyp() - atSpan(start, arrowOffset) { - if (isFunction(body)) - PolyFunction(tparams, body) - else { - syntaxError("Implementation restriction: polymorphic function types must have a value parameter", arrowOffset) - Ident(nme.ERROR.toTypeName) - } - } + atSpan(start, arrowOffset) { PolyFunction(tparams, body) } } else { accept(TLARROW); typ() } } @@ -1917,14 +1910,7 @@ object Parsers { val tparams = typeParamClause(ParamOwner.TypeParam) val arrowOffset = accept(ARROW) val body = expr(location) - atSpan(start, arrowOffset) { - if (isFunction(body)) - PolyFunction(tparams, body) - else { - syntaxError("Implementation restriction: polymorphic function literals must have a value parameter", arrowOffset) - errorTermTree - } - } + atSpan(start, arrowOffset) { PolyFunction(tparams, body) } case _ => val saved = placeholderParams placeholderParams = Nil diff --git a/tests/neg/i2887b.scala b/tests/neg/i2887b.scala index 3984949bf580..fea973c9e7cf 100644 --- a/tests/neg/i2887b.scala +++ b/tests/neg/i2887b.scala @@ -1,5 +1,5 @@ -trait A { type S[X[_] <: [_] => Any, Y[_]] <: [_] => Any; type I[_] } // error // error -trait B { type S[X[_],Y[_]]; type I[_] <: [_] => Any } // error +trait A { type S[X[_] <: [_] => Any, Y[_]] <: [_] => Any; type I[_] } +trait B { type S[X[_],Y[_]]; type I[_] <: [_] => Any } trait C { type M <: B } trait D { type M >: A } diff --git a/tests/neg/polymorphic-functions.scala b/tests/pos/polymorphic-functions.scala similarity index 100% rename from tests/neg/polymorphic-functions.scala rename to tests/pos/polymorphic-functions.scala From 797e2617f1d5e4607b248f9a97b7a97b0962baf0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jun 2021 20:58:28 +0200 Subject: [PATCH 70/87] Fix arity check --- compiler/src/dotty/tools/dotc/core/TypeErasure.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 9218877c3f11..291419d5c400 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -601,8 +601,8 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst assert(refinedInfo.isInstanceOf[PolyType]) val res = refinedInfo.resultType val paramss = res.paramNamess + assert(paramss.length <= 1) val arity = if paramss.isEmpty then 0 else paramss.head.length - assert(arity <= 1) this(defn.FunctionType(arity, isContextual = res.isImplicitMethod, isErased = res.isErasedMethod)) case tp: TypeProxy => this(tp.underlying) From d1fab190f6db4c1184af6b02e3db585d7f76e70a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jun 2021 17:01:00 +0200 Subject: [PATCH 71/87] Add IO test --- tests/neg-custom-args/captures/io.scala | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/neg-custom-args/captures/io.scala diff --git a/tests/neg-custom-args/captures/io.scala b/tests/neg-custom-args/captures/io.scala new file mode 100644 index 000000000000..a9636b045694 --- /dev/null +++ b/tests/neg-custom-args/captures/io.scala @@ -0,0 +1,21 @@ +sealed trait IO: + def puts(msg: Any): Unit = println(msg) + +def test1 = + val IO : IO retains * = new IO {} + def foo = IO.puts("hello") + val x : () => Unit = () => foo // error: Found: (() => Unit) retains IO; Required: () => Unit + +def test2 = + val IO : IO retains * = new IO {} + def puts(msg: Any, io: IO retains *) = println(msg) + def foo() = puts("hello", IO) + val x : () => Unit = () => foo() // error: Found: (() => Unit) retains IO; Required: () => Unit + +type Capability[T] = T retains * + +def test3 = + val IO : Capability[IO] = new IO {} + def puts(msg: Any, io: Capability[IO]) = println(msg) + def foo() = puts("hello", IO) + val x : () => Unit = () => foo() // error: Found: (() => Unit) retains IO; Required: () => Unit From c2265771133aaf84708f132f81280894a9984643 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 19 Jun 2021 12:56:46 +0200 Subject: [PATCH 72/87] Refactor tests and fix isGlobal --- .../tools/dotc/typer/CheckCaptures.scala | 1 + .../dotty/tools/dotc/CompilationTests.scala | 2 +- tests/neg-custom-args/captures/io.scala | 21 +++++++++++++++++++ .../captures/boxmap.scala | 0 .../captures/capt-depfun.scala | 0 .../captures/capt1.scala | 0 .../captures/list-encoding.scala | 0 .../captures/try.scala | 0 .../captures/try3.scala | 0 9 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/neg-custom-args/captures/io.scala rename tests/{pos-special => pos-custom-args}/captures/boxmap.scala (100%) rename tests/{pos-special => pos-custom-args}/captures/capt-depfun.scala (100%) rename tests/{pos-special => pos-custom-args}/captures/capt1.scala (100%) rename tests/{pos-special => pos-custom-args}/captures/list-encoding.scala (100%) rename tests/{pos-special => pos-custom-args}/captures/try.scala (100%) rename tests/{pos-special => pos-custom-args}/captures/try3.scala (100%) diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index 4fceb72046ef..dce20d5a1d54 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -124,6 +124,7 @@ class CheckCaptures extends RefineTypes: val isGlobal = ref match case ref: TypeRef => ref.isRootCapability case ref: TermRef => ref.prefix != NoPrefix && ref.symbol.hasAnnotation(defn.AbilityAnnot) + case _ => false val what = if ref.isRootCapability then "universal" else "global" if isGlobal then val notAllowed = i" is not allowed to capture the $what capability $ref" diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 6b0c77d77b32..757ebf8310b9 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -33,13 +33,13 @@ class CompilationTests { compileFilesInDir("tests/pos-special/sourcepath/outer", defaultOptions.and("-sourcepath", "tests/pos-special/sourcepath")), compileFile("tests/pos-special/sourcepath/outer/nested/Test4.scala", defaultOptions.and("-sourcepath", "tests/pos-special/sourcepath")), compileFilesInDir("tests/pos-special/fatal-warnings", defaultOptions.and("-Xfatal-warnings", "-deprecation", "-feature")), - compileFilesInDir("tests/pos-special/captures", defaultOptions.and("-Ycc")), compileFile("tests/pos-special/avoid-warn-deprecation.scala", defaultOptions.and("-Xfatal-warnings", "-feature")), compileFilesInDir("tests/pos-special/spec-t5545", defaultOptions), compileFilesInDir("tests/pos-special/strawman-collections", allowDeepSubtypes), compileFilesInDir("tests/pos-special/isInstanceOf", allowDeepSubtypes.and("-Xfatal-warnings")), compileFilesInDir("tests/new", defaultOptions), compileFilesInDir("tests/pos-scala2", scala2CompatMode), + compileFilesInDir("tests/pos-custom-args/captures", defaultOptions.and("-Ycc")), compileFilesInDir("tests/pos-custom-args/erased", defaultOptions.and("-language:experimental.erasedDefinitions")), compileFilesInDir("tests/pos", defaultOptions.and("-Ysafe-init")), compileFilesInDir("tests/pos-deep-subtype", allowDeepSubtypes), diff --git a/tests/neg-custom-args/captures/io.scala b/tests/neg-custom-args/captures/io.scala new file mode 100644 index 000000000000..a9636b045694 --- /dev/null +++ b/tests/neg-custom-args/captures/io.scala @@ -0,0 +1,21 @@ +sealed trait IO: + def puts(msg: Any): Unit = println(msg) + +def test1 = + val IO : IO retains * = new IO {} + def foo = IO.puts("hello") + val x : () => Unit = () => foo // error: Found: (() => Unit) retains IO; Required: () => Unit + +def test2 = + val IO : IO retains * = new IO {} + def puts(msg: Any, io: IO retains *) = println(msg) + def foo() = puts("hello", IO) + val x : () => Unit = () => foo() // error: Found: (() => Unit) retains IO; Required: () => Unit + +type Capability[T] = T retains * + +def test3 = + val IO : Capability[IO] = new IO {} + def puts(msg: Any, io: Capability[IO]) = println(msg) + def foo() = puts("hello", IO) + val x : () => Unit = () => foo() // error: Found: (() => Unit) retains IO; Required: () => Unit diff --git a/tests/pos-special/captures/boxmap.scala b/tests/pos-custom-args/captures/boxmap.scala similarity index 100% rename from tests/pos-special/captures/boxmap.scala rename to tests/pos-custom-args/captures/boxmap.scala diff --git a/tests/pos-special/captures/capt-depfun.scala b/tests/pos-custom-args/captures/capt-depfun.scala similarity index 100% rename from tests/pos-special/captures/capt-depfun.scala rename to tests/pos-custom-args/captures/capt-depfun.scala diff --git a/tests/pos-special/captures/capt1.scala b/tests/pos-custom-args/captures/capt1.scala similarity index 100% rename from tests/pos-special/captures/capt1.scala rename to tests/pos-custom-args/captures/capt1.scala diff --git a/tests/pos-special/captures/list-encoding.scala b/tests/pos-custom-args/captures/list-encoding.scala similarity index 100% rename from tests/pos-special/captures/list-encoding.scala rename to tests/pos-custom-args/captures/list-encoding.scala diff --git a/tests/pos-special/captures/try.scala b/tests/pos-custom-args/captures/try.scala similarity index 100% rename from tests/pos-special/captures/try.scala rename to tests/pos-custom-args/captures/try.scala diff --git a/tests/pos-special/captures/try3.scala b/tests/pos-custom-args/captures/try3.scala similarity index 100% rename from tests/pos-special/captures/try3.scala rename to tests/pos-custom-args/captures/try3.scala From acc93bb1639deacd32b8b0f46ced82c30d51dc23 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 19 Jun 2021 13:00:58 +0200 Subject: [PATCH 73/87] Fix printing of poly function types --- .../tools/dotc/printing/RefinedPrinter.scala | 37 ++++++++++++++----- tests/neg/polymorphic-functions1.check | 7 ++++ tests/neg/polymorphic-functions1.scala | 1 + 3 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 tests/neg/polymorphic-functions1.check create mode 100644 tests/neg/polymorphic-functions1.scala diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 54de6a7e13fc..e17798e0ad5d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -158,14 +158,25 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { argStr ~ " " ~ arrow(isGiven) ~ " " ~ argText(args.last) } - def toTextDependentFunction(appType: MethodType): Text = - "(" - ~ keywordText("erased ").provided(appType.isErasedMethod) - ~ paramsText(appType) - ~ ") " - ~ arrow(appType.isImplicitMethod) - ~ " " - ~ toText(appType.resultType) + def toTextMethodAsFunction(info: Type): Text = info match + case info: MethodType => + "(" + ~ keywordText("erased ").provided(info.isErasedMethod) + ~ ( if info.isParamDependent || info.isResultDependent + then paramsText(info) + else argsText(info.paramInfos) + ) + ~ ") " + ~ arrow(info.isImplicitMethod) + ~ " " + ~ toTextMethodAsFunction(info.resultType) + case info: PolyType => + "[" + ~ paramsText(info) + ~ "] => " + ~ toTextMethodAsFunction(info.resultType) + case _ => + toText(info) def isInfixType(tp: Type): Boolean = tp match case AppliedType(tycon, args) => @@ -229,8 +240,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if !printDebug && appliedText(tp.asInstanceOf[HKLambda].resType).isEmpty => // don't eta contract if the application would be printed specially toText(tycon) - case tp: RefinedType if defn.isFunctionType(tp) && !printDebug => - toTextDependentFunction(tp.refinedInfo.asInstanceOf[MethodType]) + case tp: RefinedType + if (defn.isFunctionType(tp) || (tp.parent.typeSymbol eq defn.PolyFunctionClass)) + && !printDebug => + toTextMethodAsFunction(tp.refinedInfo) case tp: TypeRef => if (tp.symbol.isAnonymousClass && !showUniqueIds) toText(tp.info) @@ -244,6 +257,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case ErasedValueType(tycon, underlying) => "ErasedValueType(" ~ toText(tycon) ~ ", " ~ toText(underlying) ~ ")" case tp: ClassInfo => + if tp.cls.derivesFrom(defn.PolyFunctionClass) then + tp.member(nme.apply).info match + case info: PolyType => return toTextMethodAsFunction(info) + case _ => toTextParents(tp.parents) ~~ "{...}" case JavaArrayType(elemtp) => toText(elemtp) ~ "[]" diff --git a/tests/neg/polymorphic-functions1.check b/tests/neg/polymorphic-functions1.check new file mode 100644 index 000000000000..b9459340fac7 --- /dev/null +++ b/tests/neg/polymorphic-functions1.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/polymorphic-functions1.scala:1:53 --------------------------------------------- +1 |val f: [T] => (x: T) => x.type = [T] => (x: Int) => x // error + | ^ + | Found: [T] => (Int) => Int + | Required: [T] => (x: T) => x.type + +longer explanation available when compiling with `-explain` diff --git a/tests/neg/polymorphic-functions1.scala b/tests/neg/polymorphic-functions1.scala new file mode 100644 index 000000000000..de887f3b8c50 --- /dev/null +++ b/tests/neg/polymorphic-functions1.scala @@ -0,0 +1 @@ +val f: [T] => (x: T) => x.type = [T] => (x: Int) => x // error From 87c92d145a15d06ff6d7a1a437dc28ac8920d50a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 19 Jun 2021 14:56:48 +0200 Subject: [PATCH 74/87] Allow capturing types as CFT protos --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 25dc0602ae76..397392dbec84 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1565,7 +1565,7 @@ class Definitions { * - the upper bound of a TypeParamRef in the current constraint */ def asContextFunctionType(tp: Type)(using Context): Type = - tp.stripTypeVar.dealias match + tp.stripped.dealias match case tp1: TypeParamRef if ctx.typerState.constraint.contains(tp1) => asContextFunctionType(TypeComparer.bounds(tp1).hiBound) case tp1 => From 651b79183fef333538c94506ecfdba0ca9cffc75 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 19 Jun 2021 14:58:26 +0200 Subject: [PATCH 75/87] Don't print trees twice after refiner phases --- compiler/src/dotty/tools/dotc/Run.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 0afea7988958..f4d0c4973e18 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -9,7 +9,7 @@ import Types._ import Scopes._ import Names.Name import Denotations.Denotation -import typer.Typer +import typer.{Typer, RefineTypes} import typer.ImportInfo._ import Decorators._ import io.{AbstractFile, PlainFile, VirtualFile} @@ -204,7 +204,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint val profileBefore = profiler.beforePhase(phase) units = phase.runOn(units) profiler.afterPhase(phase, profileBefore) - if (ctx.settings.Xprint.value.containsPhase(phase)) + if ctx.settings.Xprint.value.containsPhase(phase) && !phase.isInstanceOf[RefineTypes] then for (unit <- units) lastPrintedTree = printTree(lastPrintedTree)(using ctx.fresh.setPhase(phase.next).setCompilationUnit(unit)) diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index dce20d5a1d54..e38d590c1e0c 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -11,7 +11,7 @@ import StdNames._ import Decorators._ import ProtoTypes._ import Inferencing.isFullyDefined -import config.Printers.refinr +import config.Printers.capt import ast.{tpd, untpd, Trees} import NameKinds.{DocArtifactName, OuterSelectName, DefaultGetterName} import Trees._ @@ -78,7 +78,7 @@ class CheckCaptures extends RefineTypes: override def typedClosure(tree: untpd.Closure, pt: Type)(using Context): Tree = super.typedClosure(tree, pt) match case tree1: Closure => - refinr.println(i"typing closure ${tree1.meth.symbol} with fvs ${capturedVars(tree1.meth.symbol)}") + capt.println(i"typing closure ${tree1.meth.symbol} with fvs ${capturedVars(tree1.meth.symbol)}") tree1.withType(tree1.tpe.capturing(capturedVars(tree1.meth.symbol))) case tree1 => tree1 From f5d25dae25807c46bbfad3460c1f80bba60ce6a1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 19 Jun 2021 15:00:30 +0200 Subject: [PATCH 76/87] Shallow capture sets --- .../dotty/tools/dotc/core/CaptureSet.scala | 70 ++++++++----------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala index cf1d1aae21fa..8210863ce9a0 100644 --- a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala @@ -35,7 +35,16 @@ case class CaptureSet private (elems: CaptureSet.Refs) extends Showable: myClosure def ++ (that: CaptureSet): CaptureSet = - CaptureSet(elems ++ that.elems) + if this.isEmpty then that + else if that.isEmpty then this + else CaptureSet(elems ++ that.elems) + + def + (ref: CaptureRef) = + if elems.contains(ref) then this + else CaptureSet(elems + ref) + + def intersect (that: CaptureSet): CaptureSet = + CaptureSet(this.elems.intersect(that.elems)) /** {x} <:< this where <:< is subcapturing */ def accountsFor(x: CaptureRef)(using Context) = @@ -82,46 +91,23 @@ object CaptureSet: css.foldLeft(empty)(_ ++ _) def ofType(tp: Type)(using Context): CaptureSet = - val collect = new TypeAccumulator[Refs]: - var localBinders: SimpleIdentitySet[BindingType] = SimpleIdentitySet.empty - var seenLazyRefs: SimpleIdentitySet[LazyRef] = SimpleIdentitySet.empty - def apply(elems: Refs, tp: Type): Refs = trace(i"capt $elems, $tp", capt, show = true) { - tp match - case tp: NamedType => - if variance < 0 then elems - else elems ++ tp.captureSet.elems - case tp: ParamRef => - if variance < 0 || localBinders.contains(tp.binder) then elems - else elems ++ tp.captureSet.elems - case tp: LambdaType => - localBinders += tp - try apply(elems, tp.resultType) - finally localBinders -= tp - case AndType(tp1, tp2) => - val elems1 = apply(SimpleIdentitySet.empty, tp1) - val elems2 = apply(SimpleIdentitySet.empty, tp2) - elems ++ elems1.intersect(elems2) - case CapturingType(parent, ref) => - val elems1 = apply(elems, parent) - if variance >= 0 then elems1 + ref else elems1 - case TypeBounds(_, hi) => - apply(elems, hi) - case tp: ClassInfo => - elems ++ ofClass(tp, Nil).elems - case tp: LazyRef => - if seenLazyRefs.contains(tp) - || tp.evaluating // shapeless gets an assertion error without this test - then elems - else - seenLazyRefs += tp - foldOver(elems, tp) -// case tp: MatchType => -// val normed = tp.tryNormalize -// if normed.exists then apply(elems, normed) else foldOver(elems, tp) - case _ => - foldOver(elems, tp) - } - - CaptureSet(collect(empty.elems, tp)) + def recur(tp: Type): CaptureSet = tp match + case tp: NamedType => + tp.captureSet + case tp: ParamRef => + tp.captureSet + case CapturingType(parent, ref) => + recur(parent) + ref + case tp: TypeProxy => + recur(tp.underlying) + case AndType(tp1, tp2) => + recur(tp1).intersect(recur(tp2)) + case OrType(tp1, tp2) => + recur(tp1) ++ recur(tp2) + case tp: ClassInfo => + ofClass(tp, Nil) + case _ => + empty + recur(tp) .showing(i"capture set of $tp = $result", capt) From 9b7fe8b46e43a933c19b735746d95623fd4692df Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 19 Jun 2021 16:26:09 +0200 Subject: [PATCH 77/87] Fix condition in TreeChecker The previous condition did not work for qualifiers with the ``` PolyFunction { ... } retains ... ``` --- compiler/src/dotty/tools/dotc/transform/TreeChecker.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 8972a1e12ddd..b932ea865ac5 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -375,14 +375,14 @@ class TreeChecker extends Phase with SymTransformer { val tpe = tree.typeOpt // Polymorphic apply methods stay structural until Erasure - val isPolyFunctionApply = (tree.name eq nme.apply) && (tree.qualifier.typeOpt <:< defn.PolyFunctionType) + val isPolyFunctionApply = (tree.name eq nme.apply) && tree.qualifier.typeOpt.derivesFrom(defn.PolyFunctionClass) // Outer selects are pickled specially so don't require a symbol val isOuterSelect = tree.name.is(OuterSelectName) val isPrimitiveArrayOp = ctx.erasedTypes && nme.isPrimitiveName(tree.name) if !(tree.isType || isPolyFunctionApply || isOuterSelect || isPrimitiveArrayOp) then val denot = tree.denot assert(denot.exists, i"Selection $tree with type $tpe does not have a denotation") - assert(denot.symbol.exists, i"Denotation $denot of selection $tree with type $tpe does not have a symbol") + assert(denot.symbol.exists, i"Denotation $denot of selection $tree with type $tpe does not have a symbol, ${tree.qualifier.typeOpt}") val sym = tree.symbol val symIsFixed = tpe match { From 3cf68e3bdfad1d81467816e3890f0b40542fac0a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 19 Jun 2021 16:35:38 +0200 Subject: [PATCH 78/87] Fix precedence of dependent and poly function printing --- .../tools/dotc/printing/RefinedPrinter.scala | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index e17798e0ad5d..bcc473fb450b 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -160,21 +160,25 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def toTextMethodAsFunction(info: Type): Text = info match case info: MethodType => - "(" - ~ keywordText("erased ").provided(info.isErasedMethod) - ~ ( if info.isParamDependent || info.isResultDependent - then paramsText(info) - else argsText(info.paramInfos) - ) - ~ ") " - ~ arrow(info.isImplicitMethod) - ~ " " - ~ toTextMethodAsFunction(info.resultType) + changePrec(GlobalPrec) { + "(" + ~ keywordText("erased ").provided(info.isErasedMethod) + ~ ( if info.isParamDependent || info.isResultDependent + then paramsText(info) + else argsText(info.paramInfos) + ) + ~ ") " + ~ arrow(info.isImplicitMethod) + ~ " " + ~ toTextMethodAsFunction(info.resultType) + } case info: PolyType => - "[" - ~ paramsText(info) - ~ "] => " - ~ toTextMethodAsFunction(info.resultType) + changePrec(GlobalPrec) { + "[" + ~ paramsText(info) + ~ "] => " + ~ toTextMethodAsFunction(info.resultType) + } case _ => toText(info) From 25b23c1d3b1f8f1bc212e22ea7344dde573ba74a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 20 Jun 2021 10:04:47 +0200 Subject: [PATCH 79/87] Fix capture sets of applied types A substitution was missing for type parameters that appear in the capture set of the constructor. --- .../src/dotty/tools/dotc/core/CaptureSet.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala index 8210863ce9a0..d28d271fc3b9 100644 --- a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala @@ -54,6 +54,15 @@ case class CaptureSet private (elems: CaptureSet.Refs) extends Showable: def <:< (that: CaptureSet)(using Context): Boolean = elems.isEmpty || elems.forall(that.accountsFor) + def flatMap(f: CaptureRef => CaptureSet)(using Context): CaptureSet = + (empty /: elems)((cs, ref) => cs ++ f(ref)) + + def substParams(tl: BindingType, to: List[Type])(using Context) = + flatMap { + case ref: ParamRef if ref.binder eq tl => to(ref.paramNum).captureSet + case ref => ref.singletonCaptureSet + } + override def toString = elems.toString override def toText(printer: Printer): Text = @@ -98,6 +107,11 @@ object CaptureSet: tp.captureSet case CapturingType(parent, ref) => recur(parent) + ref + case AppliedType(tycon, args) => + val cs = recur(tycon) + tycon.typeParams match + case tparams @ (LambdaParam(tl, _) :: _) => cs.substParams(tl, args) + case _ => cs case tp: TypeProxy => recur(tp.underlying) case AndType(tp1, tp2) => From 27c9fc53fbff333798f1914ab8e3390b5d6aeff6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 20 Jun 2021 10:06:35 +0200 Subject: [PATCH 80/87] Fix decomposeProto Strip off capturing types before decomposing a function prototype --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 111f5f42a525..0fc519ace9f0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1127,7 +1127,7 @@ class Typer extends Namer case _ => mapOver(t) } - val pt1 = pt.stripTypeVar.dealias + val pt1 = pt.stripped.dealias if (pt1 ne pt1.dropDependentRefinement) && defn.isContextFunctionType(pt1.nonPrivateMember(nme.apply).info.finalResultType) then From 988c4c054b120bfbb3c3a50dc2686699dda9c702 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 20 Jun 2021 10:07:27 +0200 Subject: [PATCH 81/87] Adapt tests to shallow capture sets --- tests/neg-custom-args/captures/boxmap.scala | 2 +- tests/neg-custom-args/captures/capt1.check | 42 +++++++++++++++++++ tests/neg-custom-args/captures/capt1.scala | 3 +- tests/neg-custom-args/captures/try2.check | 4 +- tests/neg-custom-args/captures/try2.scala | 2 +- tests/pos-custom-args/captures/boxmap.scala | 4 +- tests/pos-custom-args/captures/capt1.scala | 7 ++-- .../captures/list-encoding.scala | 2 +- 8 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 tests/neg-custom-args/captures/capt1.check diff --git a/tests/neg-custom-args/captures/boxmap.scala b/tests/neg-custom-args/captures/boxmap.scala index d61f2ca0e357..cb77c6794cb1 100644 --- a/tests/neg-custom-args/captures/boxmap.scala +++ b/tests/neg-custom-args/captures/boxmap.scala @@ -3,7 +3,7 @@ class Cap extends Retains[*] infix type ==> [A, B] = (A => B) retains * -type Box[+T <: Top] = [K <: Top] => (T ==> K) => K +type Box[+T <: Top] = ([K <: Top] => (T ==> K) => K) retains T def box[T <: Top](x: T): Box[T] = [K <: Top] => (k: T ==> K) => k(x) diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check new file mode 100644 index 000000000000..15ba35a52e91 --- /dev/null +++ b/tests/neg-custom-args/captures/capt1.check @@ -0,0 +1,42 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:3:2 ------------------------------------------ +3 | () => if x == null then y else y // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (() => C) retains x + | Required: () => C + +longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:6:2 ------------------------------------------ +6 | () => if x == null then y else y // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (() => C) retains x + | Required: Any + +longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:14:2 ----------------------------------------- +14 | f // error + | ^ + | Found: (Int => Int) retains x + | Required: Any + +longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:23:3 ----------------------------------------- +23 | F(22) // error + | ^^^^^ + | Found: F retains x + | Required: A + +longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:27:40 ---------------------------------------- +27 | def m() = if x == null then y else y // error + | ^ + | Found: A {...} retains x + | Required: A + +longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:32:24 ---------------------------------------- +32 | val z2 = h[() => Cap](() => x)(() => C()) // error + | ^^^^^^^ + | Found: (() => Cap) retains x + | Required: () => Cap + +longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/capt1.scala b/tests/neg-custom-args/captures/capt1.scala index a7f2b0925dca..6f9c02d1540f 100644 --- a/tests/neg-custom-args/captures/capt1.scala +++ b/tests/neg-custom-args/captures/capt1.scala @@ -30,5 +30,6 @@ def foo() = val x: C retains * = ??? def h[X <:Top](a: X)(b: X) = a val z2 = h[() => Cap](() => x)(() => C()) // error - val z3 = h(() => x)(() => C()) // error + val z3 = h[(() => Cap) retains x.type](() => x)(() => C()) // ok + val z4 = h[(() => Cap) retains x.type](() => x)(() => C()) // what was inferred for z3 diff --git a/tests/neg-custom-args/captures/try2.check b/tests/neg-custom-args/captures/try2.check index 55af935635e7..a73ee901406d 100644 --- a/tests/neg-custom-args/captures/try2.check +++ b/tests/neg-custom-args/captures/try2.check @@ -29,10 +29,10 @@ longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/try2.scala:24:28 -------------------------------------------------------------- 24 | val a = handle[Exception, CanThrow[Exception]] { // error | ^^^^^^^^^^^^^^^^^^^ - | type argument is not allowed to capture the global capability (canThrow : List[*]) + | type argument is not allowed to capture the global capability (canThrow : *) -- Error: tests/neg-custom-args/captures/try2.scala:36:11 -------------------------------------------------------------- 36 | val xx = handle { // error | ^^^^^^ - |inferred type argument ((() => Int) retains canThrow) is not allowed to capture the global capability (canThrow : List[*]) + |inferred type argument ((() => Int) retains canThrow) is not allowed to capture the global capability (canThrow : *) | |The inferred arguments are: [Exception, ((() => Int) retains canThrow)] diff --git a/tests/neg-custom-args/captures/try2.scala b/tests/neg-custom-args/captures/try2.scala index 3b7bee8224a9..469d9cf8d2f2 100644 --- a/tests/neg-custom-args/captures/try2.scala +++ b/tests/neg-custom-args/captures/try2.scala @@ -1,7 +1,7 @@ import language.experimental.erasedDefinitions import annotation.ability -@ability erased val canThrow: List[*] = ??? +@ability erased val canThrow: * = ??? class CanThrow[E <: Exception] extends Retains[canThrow.type] type Top = Any retains * diff --git a/tests/pos-custom-args/captures/boxmap.scala b/tests/pos-custom-args/captures/boxmap.scala index adda37f6c03a..50a84e5c6ae5 100644 --- a/tests/pos-custom-args/captures/boxmap.scala +++ b/tests/pos-custom-args/captures/boxmap.scala @@ -3,7 +3,7 @@ class Cap extends Retains[*] infix type ==> [A, B] = (A => B) retains * -type Box[+T <: Top] = [K <: Top] => (T ==> K) => K +type Box[+T <: Top] = ([K <: Top] => (T ==> K) => K) retains T def box[T <: Top](x: T): Box[T] = [K <: Top] => (k: T ==> K) => k(x) @@ -17,5 +17,5 @@ def lazymap[A <: Top, B <: Top](b: Box[A])(f: A ==> B): (() => Box[B]) retains b def test[A <: Top, B <: Top] = def lazymap[A <: Top, B <: Top](b: Box[A])(f: A ==> B) = () => b[Box[B]]((x: A) => box(f(x))) - val x: (b: Box[A]) => (f: A ==> B) => (() => Box[B]) retains b.type | f.type = lazymap[A, B] + val x: (b: Box[A]) => ((f: A ==> B) => (() => Box[B]) retains b.type | f.type) retains b.type = lazymap[A, B] () diff --git a/tests/pos-custom-args/captures/capt1.scala b/tests/pos-custom-args/captures/capt1.scala index cada7aa41de5..b311e1bf9d8f 100644 --- a/tests/pos-custom-args/captures/capt1.scala +++ b/tests/pos-custom-args/captures/capt1.scala @@ -1,7 +1,7 @@ class C type Cap = C retains * type Top = Any retains * -def f1(c: Cap): () => C retains c.type = () => c // ok +def f1(c: Cap): (() => c.type) retains c.type = () => c // ok def f2: Int = val g: (Boolean => Int) retains * = ??? @@ -18,11 +18,10 @@ def foo() = val x: C retains * = ??? val y: C retains x.type = x val x2: (() => C) retains x.type = ??? - val y2: () => C retains x.type = x2 + val y2: (() => C retains x.type) retains x.type = x2 - val z1: () => Cap = f1(x) + val z1: (() => Cap) retains * = f1(x) def h[X <:Top](a: X)(b: X) = a val z2 = if x == null then () => x else () => C() - diff --git a/tests/pos-custom-args/captures/list-encoding.scala b/tests/pos-custom-args/captures/list-encoding.scala index eda95898de65..91cf6d92c08f 100644 --- a/tests/pos-custom-args/captures/list-encoding.scala +++ b/tests/pos-custom-args/captures/list-encoding.scala @@ -5,7 +5,7 @@ type Op[T <: Top, C <: Top] = ((v: T) => ((s: C) => C) retains *) retains * type List[T <: Top] = - [C <: Top] => (op: Op[T, C]) => ((s: C) => C) retains op.type + ([C <: Top] => (op: Op[T, C]) => ((s: C) => C) retains op.type) retains T def nil[T <: Top]: List[T] = [C <: Top] => (op: Op[T, C]) => (s: C) => s From 6977ad147a577af7bf3e3b873408d010f25df16f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 21 Jun 2021 18:07:12 +0200 Subject: [PATCH 82/87] Handle TypeVars in CaptureSets --- .../dotty/tools/dotc/core/CaptureSet.scala | 21 +-------------- .../src/dotty/tools/dotc/core/Types.scala | 27 ++++++++++++++++--- .../tools/dotc/typer/CheckCaptures.scala | 10 ++++--- tests/neg-custom-args/captures/try3.scala | 2 +- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala index d28d271fc3b9..6f7d03b92733 100644 --- a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala @@ -17,23 +17,6 @@ case class CaptureSet private (elems: CaptureSet.Refs) extends Showable: def isEmpty: Boolean = elems.isEmpty def nonEmpty: Boolean = !isEmpty - private var myClosure: Refs | Null = null - - def closure(using Context): Refs = - if myClosure == null then - var cl = elems - var seen: Refs = SimpleIdentitySet.empty - while - val prev = cl - for ref <- cl do - if !seen.contains(ref) then - seen += ref - cl = cl ++ ref.captureSetOfInfo.elems - prev ne cl - do () - myClosure = cl - myClosure - def ++ (that: CaptureSet): CaptureSet = if this.isEmpty then that else if that.isEmpty then this @@ -101,9 +84,7 @@ object CaptureSet: def ofType(tp: Type)(using Context): CaptureSet = def recur(tp: Type): CaptureSet = tp match - case tp: NamedType => - tp.captureSet - case tp: ParamRef => + case tp: CaptureRef => tp.captureSet case CapturingType(parent, ref) => recur(parent) + ref diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b8dd2434a071..5620e76ed0b0 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3620,7 +3620,7 @@ object Types { case tp: TermParamRef if tp.binder eq thisLambdaType => TrueDeps case tp: CapturingType => val status1 = compute(status, tp.parent, theAcc) - tp.ref match + tp.ref.stripTypeVar match case tp: TermParamRef if tp.binder eq thisLambdaType => combine(status1, CaptureDeps) case _ => status1 case _: ThisType | _: BoundType | NoPrefix => status @@ -4505,9 +4505,10 @@ object Types { * @param origin The parameter that's tracked by the type variable. * @param creatorState The typer state in which the variable was created. */ - final class TypeVar private(initOrigin: TypeParamRef, creatorState: TyperState, nestingLevel: Int) extends CachedProxyType with ValueType { + final class TypeVar private(initOrigin: TypeParamRef, creatorState: TyperState, nestingLevel: Int) + extends CachedProxyType, CaptureRef { - private var currentOrigin = initOrigin + private var currentOrigin = initOrigin def origin: TypeParamRef = currentOrigin @@ -4689,6 +4690,26 @@ object Types { if (inst.exists) inst else origin } + // Capture ref methods + + def canBeTracked(using Context): Boolean = underlying match + case ref: CaptureRef => ref.canBeTracked + case _ => false + + override def normalizedRef(using Context): CaptureRef = instanceOpt match + case ref: CaptureRef => ref + case _ => this + + override def singletonCaptureSet(using Context) = instanceOpt match + case ref: CaptureRef => ref.singletonCaptureSet + case _ => super.singletonCaptureSet + + override def captureSetOfInfo(using Context): CaptureSet = instanceOpt match + case ref: CaptureRef => ref.captureSetOfInfo + case tp => tp.captureSet + + // Object members + override def computeHash(bs: Binders): Int = identityHash(bs) override def equals(that: Any): Boolean = this.eq(that.asInstanceOf[AnyRef]) diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index e38d590c1e0c..98d9d1d4d979 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -99,10 +99,12 @@ class CheckCaptures extends RefineTypes: def checkWellFormed(whole: Type, pos: SrcPos)(using Context): Unit = def checkRelativeVariance(mt: MethodType) = new TypeTraverser: def traverse(tp: Type): Unit = tp match - case CapturingType(parent, ref @ TermParamRef(`mt`, _)) => - if variance <= 0 then - val direction = if variance < 0 then "contra" else "in" - report.error(em"captured reference $ref appears ${direction}variantly in type $whole", pos) + case CapturingType(parent, ref) => + ref.stripTypeVar match + case ref @ TermParamRef(`mt`, _) if variance <= 0 => + val direction = if variance < 0 then "contra" else "in" + report.error(em"captured reference $ref appears ${direction}variantly in type $whole", pos) + case _ => traverse(parent) case _ => traverseChildren(tp) diff --git a/tests/neg-custom-args/captures/try3.scala b/tests/neg-custom-args/captures/try3.scala index f90ad6aff0aa..ece7870ffecc 100644 --- a/tests/neg-custom-args/captures/try3.scala +++ b/tests/neg-custom-args/captures/try3.scala @@ -3,7 +3,7 @@ import java.io.IOException class CanThrow[E] extends Retains[*] type Top = Any retains * -def handle[E <: Exception, T <: Top](op: CanThrow[E] ?=> T)(handler: E => T): T = +def handle[E <: Exception, T <: Top](op: (CanThrow[E] ?=> T) retains T)(handler: (E => T) retains T): T = val x: CanThrow[E] = ??? try op(using x) catch case ex: E => handler(ex) From cceff77ca13f17f6177c3d361d7ea9fca66f0cfb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 21 Jun 2021 18:22:58 +0200 Subject: [PATCH 83/87] Assume capture sets of result type in enclosing function --- .../tools/dotc/config/ScalaSettings.scala | 1 + .../tools/dotc/typer/CheckCaptures.scala | 101 ++++++++++++++++++ .../dotty/tools/dotc/typer/TypeAssigner.scala | 10 +- .../src/dotty/tools/dotc/typer/Typer.scala | 16 +-- tests/neg-custom-args/captures/boxmap.check | 2 +- tests/neg-custom-args/captures/capt1.check | 9 +- tests/neg-custom-args/captures/capt1.scala | 2 +- .../captures/try3-abbrev.scala | 26 +++++ .../captures/capt1-abbrev.scala | 27 +++++ 9 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 tests/neg-custom-args/captures/try3-abbrev.scala create mode 100644 tests/pos-custom-args/captures/capt1-abbrev.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 1392baa80a75..094e685ddc75 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -203,6 +203,7 @@ trait AllScalaSettings extends CommonScalaSettings { self: Settings.SettingGroup val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation") val YrefineTypes: Setting[Boolean] = BooleanSetting("-Yrefine-types", "Run experimental type refiner (test only)") val Ycc: Setting[Boolean] = BooleanSetting("-Ycc", "Check captured references") + val YccNoAbbrev: Setting[Boolean] = BooleanSetting("-Ycc-no-abbrev", "Used in conjunction with -Ycc, suppress type abbreviations") /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index 98d9d1d4d979..5a33ab726a9f 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -152,4 +152,105 @@ class CheckCaptures extends RefineTypes: def postRefinerCheck(tree: tpd.Tree)(using Context): Unit = PostRefinerCheck.traverse(tree) + +object CheckCaptures: + import ast.tpd.* + + def expandFunctionTypes(using Context) = + ctx.settings.Ycc.value && !ctx.settings.YccNoAbbrev.value && !ctx.isAfterTyper + + object FunctionTypeTree: + def unapply(tree: Tree)(using Context): Option[(List[Type], Type)] = + if defn.isFunctionType(tree.tpe) then + tree match + case AppliedTypeTree(tycon: TypeTree, args) => + Some((args.init.tpes, args.last.tpe)) + case RefinedTypeTree(_, (appDef: DefDef) :: Nil) if appDef.span == tree.span => + appDef.symbol.info match + case mt: MethodType => Some((mt.paramInfos, mt.resultType)) + case _ => None + case _ => + None + else None + + object CapturingTypeTree: + def unapply(tree: Tree)(using Context): Option[(Tree, Tree, CaptureRef)] = tree match + case AppliedTypeTree(tycon, parent :: _ :: Nil) + if tycon.symbol == defn.Predef_retainsType => + tree.tpe match + case CapturingType(_, ref) => Some((tycon, parent, ref)) + case _ => None + case _ => None + + def addRetains(tree: Tree, ref: CaptureRef)(using Context): Tree = + untpd.AppliedTypeTree( + TypeTree(defn.Predef_retainsType.typeRef), List(tree, TypeTree(ref))) + .withType(CapturingType(tree.tpe, ref)) + .showing(i"add inferred capturing $result", capt) + + /** Under -Ycc but not -Ycc-no-abbrev, if `tree` represents a function type + * `(ARGS) => T` where T is tracked and all ARGS are pure, expand it to + * `(ARGS) => T retains CS` where CS is the capture set of `T`. These synthesized + * additions will be removed again if the function type is wrapped in an + * explicit `retains` type. + */ + def addResultCaptures(tree: Tree)(using Context): Tree = + if expandFunctionTypes then + tree match + case FunctionTypeTree(argTypes, resType) => + val cs = resType.captureSet + if cs.nonEmpty && argTypes.forall(_.captureSet.isEmpty) + then (tree /: cs.elems)(addRetains) + else tree + case _ => + tree + else tree + + private def addCaptures(tp: Type, refs: Type)(using Context): Type = refs match + case ref: CaptureRef => CapturingType(tp, ref) + case OrType(refs1, refs2) => addCaptures(addCaptures(tp, refs1), refs2) + case _ => tp + + /** @pre: `tree is a tree of the form `T retains REFS`. + * Return the same tree with `parent1` instead of `T` with its type + * recomputed accordingly. + */ + private def derivedCapturingTree(tree: AppliedTypeTree, parent1: Tree)(using Context): AppliedTypeTree = + tree match + case AppliedTypeTree(tycon, parent :: (rest @ (refs :: Nil))) if parent ne parent1 => + cpy.AppliedTypeTree(tree)(tycon, parent1 :: rest) + .withType(addCaptures(parent1.tpe, refs.tpe)) + case _ => + tree + + private def stripCaptures(tree: Tree, ref: CaptureRef)(using Context): Tree = tree match + case tree @ AppliedTypeTree(tycon, parent :: refs :: Nil) if tycon.symbol == defn.Predef_retainsType => + val parent1 = stripCaptures(parent, ref) + val isSynthetic = tycon.isInstanceOf[TypeTree] + if isSynthetic then + parent1.showing(i"drop inferred capturing $tree => $result", capt) + else + if parent1.tpe.captureSet.accountsFor(ref) then + report.warning( + em"redundant capture: $parent1 already contains $ref with capture set ${ref.captureSet} in its capture set ${parent1.tpe.captureSet}", + tree.srcPos) + derivedCapturingTree(tree, parent1) + case _ => tree + + private def stripCaptures(tree: Tree, refs: Type)(using Context): Tree = refs match + case ref: CaptureRef => stripCaptures(tree, ref) + case OrType(refs1, refs2) => stripCaptures(stripCaptures(tree, refs1), refs2) + case _ => tree + + /** If this is a tree of the form `T retains REFS`, + * - strip any synthesized captures directly in T; + * - warn if a reference in REFS is accounted for by the capture set of the remaining type + */ + def refineNestedCaptures(tree: AppliedTypeTree)(using Context): AppliedTypeTree = tree match + case AppliedTypeTree(tycon, parent :: (rest @ (refs :: Nil))) if tycon.symbol == defn.Predef_retainsType => + derivedCapturingTree(tree, stripCaptures(parent, refs.tpe)) + case _ => + tree + end CheckCaptures + diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index b083361fe6b2..32a1200c5c4d 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -12,6 +12,7 @@ import config.Printers.typr import ast.Trees._ import NameOps._ import ProtoTypes._ +import CheckCaptures.refineNestedCaptures import collection.mutable import reporting._ import Checking.{checkNoPrivateLeaks, checkNoWildcard} @@ -189,8 +190,6 @@ trait TypeAssigner { def captType(tp: Type, refs: Type): Type = refs match case ref: NamedType => if ref.isTracked then - if tp.captureSet.accountsFor(ref) then - report.warning(em"redundant capture: $tp already contains $ref with capture set ${ref.captureSet} in its capture set ${tp.captureSet}", tree.srcPos) CapturingType(tp, ref) else val reason = @@ -479,7 +478,7 @@ trait TypeAssigner { tree.withType(RecType.closeOver(rt => refined.substThis(refineCls, rt.recThis))) } - def assignType(tree: untpd.AppliedTypeTree, tycon: Tree, args: List[Tree])(using Context): AppliedTypeTree = { + def assignType(tree: untpd.AppliedTypeTree, tycon: Tree, args: List[Tree])(using Context): AppliedTypeTree = assert(!hasNamedArg(args) || ctx.reporter.errorsReported, tree) val tparams = tycon.tpe.typeParams val ownType = @@ -487,8 +486,9 @@ trait TypeAssigner { wrongNumberOfTypeArgs(tycon.tpe, tparams, args, tree.srcPos) else processAppliedType(tree, tycon.tpe.appliedTo(args.tpes)) - tree.withType(ownType) - } + val tree1 = tree.withType(ownType) + if ctx.settings.Ycc.value then refineNestedCaptures(tree1) + else tree1 def assignType(tree: untpd.LambdaTypeTree, tparamDefs: List[TypeDef], body: Tree)(using Context): LambdaTypeTree = tree.withType(HKTypeLambda.fromParams(tparamDefs.map(_.symbol.asType), body.tpe)) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0fc519ace9f0..f020ea8f802c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -28,6 +28,7 @@ import Checking._ import Inferencing._ import Dynamic.isDynamicExpansion import EtaExpansion.etaExpand +import CheckCaptures.addResultCaptures import TypeComparer.CompareResult import util.Spans._ import util.common._ @@ -1249,13 +1250,14 @@ class Typer extends Namer RefinedTypeTree(core, List(appDef), ctx.owner.asClass) end typedDependent - args match { - case ValDef(_, _, _) :: _ => - typedDependent(args.asInstanceOf[List[untpd.ValDef]])( - using ctx.fresh.setOwner(newRefinedClassSymbol(tree.span)).setNewScope) - case _ => - propagateErased( - typed(cpy.AppliedTypeTree(tree)(untpd.TypeTree(funCls.typeRef), args :+ body), pt)) + addResultCaptures { + args match + case ValDef(_, _, _) :: _ => + typedDependent(args.asInstanceOf[List[untpd.ValDef]])( + using ctx.fresh.setOwner(newRefinedClassSymbol(tree.span)).setNewScope) + case _ => + propagateErased( + typed(cpy.AppliedTypeTree(tree)(untpd.TypeTree(funCls.typeRef), args :+ body), pt)) } } diff --git a/tests/neg-custom-args/captures/boxmap.check b/tests/neg-custom-args/captures/boxmap.check index bbf1879464de..fa9189457180 100644 --- a/tests/neg-custom-args/captures/boxmap.check +++ b/tests/neg-custom-args/captures/boxmap.check @@ -2,7 +2,7 @@ 15 | () => b[Box[B]]((x: A) => box(f(x))) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | Found: (() => Box[B]) retains b retains f - | Required: () => Box[B] + | Required: (() => Box[B]) retains B | | where: B is a type in method lazymap with bounds <: Top diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index 15ba35a52e91..6a52a1cd7481 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -33,10 +33,7 @@ longer explanation available when compiling with `-explain` | Required: A longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:32:24 ---------------------------------------- +-- Error: tests/neg-custom-args/captures/capt1.scala:32:13 ------------------------------------------------------------- 32 | val z2 = h[() => Cap](() => x)(() => C()) // error - | ^^^^^^^ - | Found: (() => Cap) retains x - | Required: () => Cap - -longer explanation available when compiling with `-explain` + | ^^^^^^^^^ + | type argument is not allowed to capture the universal capability * diff --git a/tests/neg-custom-args/captures/capt1.scala b/tests/neg-custom-args/captures/capt1.scala index 6f9c02d1540f..9f68a09cbab8 100644 --- a/tests/neg-custom-args/captures/capt1.scala +++ b/tests/neg-custom-args/captures/capt1.scala @@ -30,6 +30,6 @@ def foo() = val x: C retains * = ??? def h[X <:Top](a: X)(b: X) = a val z2 = h[() => Cap](() => x)(() => C()) // error - val z3 = h[(() => Cap) retains x.type](() => x)(() => C()) // ok + val z3 = h(() => x)(() => C()) // ok val z4 = h[(() => Cap) retains x.type](() => x)(() => C()) // what was inferred for z3 diff --git a/tests/neg-custom-args/captures/try3-abbrev.scala b/tests/neg-custom-args/captures/try3-abbrev.scala new file mode 100644 index 000000000000..f90ad6aff0aa --- /dev/null +++ b/tests/neg-custom-args/captures/try3-abbrev.scala @@ -0,0 +1,26 @@ +import java.io.IOException + +class CanThrow[E] extends Retains[*] +type Top = Any retains * + +def handle[E <: Exception, T <: Top](op: CanThrow[E] ?=> T)(handler: E => T): T = + val x: CanThrow[E] = ??? + try op(using x) + catch case ex: E => handler(ex) + +def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = + throw ex + +@main def Test: Int = + def f(a: Boolean) = + handle { // error + if !a then raise(IOException()) + (b: Boolean) => + if !b then raise(IOException()) + 0 + } { + ex => (b: Boolean) => -1 + } + val g = f(true) + g(false) // would raise an uncaught exception + f(true)(false) // would raise an uncaught exception diff --git a/tests/pos-custom-args/captures/capt1-abbrev.scala b/tests/pos-custom-args/captures/capt1-abbrev.scala new file mode 100644 index 000000000000..c2b72bb21b70 --- /dev/null +++ b/tests/pos-custom-args/captures/capt1-abbrev.scala @@ -0,0 +1,27 @@ +class C +type Cap = C retains * +type Top = Any retains * +def f1(c: Cap): () => c.type = () => c // ok + +def f2: Int = + val g: (Boolean => Int) retains * = ??? + val x = g(true) + x + +def f3: Int = + def g: (Boolean => Int) retains * = ??? + def h = g + val x = g.apply(true) + x + +def foo() = + val x: C retains * = ??? + val y: C retains x.type = x + val x2: (() => C) retains x.type = ??? + val y2: () => C retains x.type = x2 + + val z1: (() => Cap) retains * = f1(x) + def h[X <:Top](a: X)(b: X) = a + + val z2 = + if x == null then () => x else () => C() From 7a282ad41657614289ba5b682212f678fa327edf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 21 Jun 2021 18:24:46 +0200 Subject: [PATCH 84/87] Drop unused val in appliedTo --- compiler/src/dotty/tools/dotc/core/TypeApplications.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index aa380b574b98..968fffe82809 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -310,7 +310,6 @@ class TypeApplications(val self: Type) extends AnyVal { */ final def appliedTo(args: List[Type])(using Context): Type = { record("appliedTo") - val typParams = self.typeParams val stripped = self.stripTypeVar val dealiased = stripped.safeDealias if (args.isEmpty || ctx.erasedTypes) self From 59151bdb6c2237fce7d0e6b07b6abfcd799be5bc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 21 Jun 2021 18:25:18 +0200 Subject: [PATCH 85/87] Print `retains` type trees infix --- .../src/dotty/tools/dotc/printing/RefinedPrinter.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index bcc473fb450b..37fd56931a87 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -527,13 +527,16 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case RefinedTypeTree(tpt, refines) => toTextLocal(tpt) ~ " " ~ blockText(refines) case AppliedTypeTree(tpt, args) => - if (tpt.symbol == defn.orType && args.length == 2) + if tpt.symbol == defn.orType && args.length == 2 then changePrec(OrTypePrec) { toText(args(0)) ~ " | " ~ atPrec(OrTypePrec + 1) { toText(args(1)) } } - else if (tpt.symbol == defn.andType && args.length == 2) + else if tpt.symbol == defn.andType && args.length == 2 then changePrec(AndTypePrec) { toText(args(0)) ~ " & " ~ atPrec(AndTypePrec + 1) { toText(args(1)) } } + else if tpt.symbol == defn.Predef_retainsType && args.length == 2 then + changePrec(InfixPrec) { toText(args(0)) ~ " retains " ~ toText(args(1)) } else if defn.isFunctionClass(tpt.symbol) && tpt.isInstanceOf[TypeTree] && tree.hasType && !printDebug - then changePrec(GlobalPrec) { toText(tree.typeOpt) } + then + changePrec(GlobalPrec) { toText(tree.typeOpt) } else args match case arg :: _ if arg.isTerm => toTextLocal(tpt) ~ "(" ~ Text(args.map(argText), ", ") ~ ")" From 3274eb2bec0426d3bd7b370029cddb8c2e3425b3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 21 Jun 2021 23:24:33 +0200 Subject: [PATCH 86/87] Track variable insytantiations in capture sets If a variable in a capture set gets instantiated, the capture set has to update itself as well. --- .../dotty/tools/dotc/core/CaptureSet.scala | 32 +++++++++++++----- .../src/dotty/tools/dotc/core/Types.scala | 6 +++- tests/neg-custom-args/captures/try4.scala | 33 +++++++++++++++++++ 3 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 tests/neg-custom-args/captures/try4.scala diff --git a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala index 6f7d03b92733..2af1e22c205a 100644 --- a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala @@ -11,22 +11,36 @@ import reporting.trace import printing.{Showable, Printer} import printing.Texts.* -case class CaptureSet private (elems: CaptureSet.Refs) extends Showable: +case class CaptureSet private (elems0: CaptureSet.Refs) extends Showable: import CaptureSet.* - def isEmpty: Boolean = elems.isEmpty - def nonEmpty: Boolean = !isEmpty + def isEmpty(using Context): Boolean = elems.isEmpty + def nonEmpty(using Context): Boolean = !isEmpty - def ++ (that: CaptureSet): CaptureSet = + private var isProvisional = true + private var myElems: CaptureSet.Refs = elems0 + + def elems(using Context): CaptureSet.Refs = + if isProvisional then + isProvisional = false + myElems.foreach { + case tv: TypeVar => + if tv.isInstantiated then myElems = myElems - tv ++ tv.inst.captureSet.elems + else isProvisional = true + case _ => + } + myElems + + def ++ (that: CaptureSet)(using Context): CaptureSet = if this.isEmpty then that else if that.isEmpty then this - else CaptureSet(elems ++ that.elems) + else CaptureSet(myElems ++ that.elems) - def + (ref: CaptureRef) = + def + (ref: CaptureRef)(using Context) = if elems.contains(ref) then this else CaptureSet(elems + ref) - def intersect (that: CaptureSet): CaptureSet = + def intersect (that: CaptureSet)(using Context): CaptureSet = CaptureSet(this.elems.intersect(that.elems)) /** {x} <:< this where <:< is subcapturing */ @@ -46,10 +60,10 @@ case class CaptureSet private (elems: CaptureSet.Refs) extends Showable: case ref => ref.singletonCaptureSet } - override def toString = elems.toString + override def toString = myElems.toString override def toText(printer: Printer): Text = - Str("{") ~ Text(elems.toList.map(printer.toTextCaptureRef), ", ") ~ Str("}") + Str("{") ~ Text(myElems.toList.map(printer.toTextCaptureRef), ", ") ~ Str("}") object CaptureSet: type Refs = SimpleIdentitySet[CaptureRef] diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5620e76ed0b0..419e031a38b3 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4706,7 +4706,11 @@ object Types { override def captureSetOfInfo(using Context): CaptureSet = instanceOpt match case ref: CaptureRef => ref.captureSetOfInfo - case tp => tp.captureSet + case _ => underlying.captureSet + + override def captureSet(using Context): CaptureSet = + if isInstantiated then inst.captureSet + else super.captureSet // Object members diff --git a/tests/neg-custom-args/captures/try4.scala b/tests/neg-custom-args/captures/try4.scala new file mode 100644 index 000000000000..02b4c980c53f --- /dev/null +++ b/tests/neg-custom-args/captures/try4.scala @@ -0,0 +1,33 @@ +import language.experimental.erasedDefinitions +import annotation.ability +import java.io.IOException + +class CanThrow[E] extends Retains[*] +type Top = Any retains * +infix type ==> [A, B] = (A => B) retains * + +def handle[E <: Exception, T <: Top](op: (CanThrow[E] ?=> T))(handler: (E => T) retains T): T = + val x: CanThrow[E] = ??? + try op(using x) + catch case ex: E => handler(ex) + +def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = + throw ex + +def test2: Int ==> Int = + def f(a: Boolean): Boolean => CanThrow[IOException] ?=> Int ==> Int = + handle { + if !a then raise(IOException()) + (b: Boolean) => (_: CanThrow[IOException]) ?=> + if !b then raise(IOException()) + (x: Int) => 1 + } { + ex => (b: Boolean) => (_: CanThrow[IOException]) ?=> (x: Int) => -1 + } + handle { // error: inferred type argument ((Int => Int) retains *) is not allowed to capture the universal capability * + val g = f(true) + g(false) // would raise an uncaught exception + f(true)(false) // would raise an uncaught exception + } { + ex => (x: Int) => -1 + } From 4e0d18de6b0b7a02316e1ac6ea34167094aa78c3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 22 Jun 2021 09:53:53 +0200 Subject: [PATCH 87/87] More tests --- tests/pos-custom-args/captures/try3.scala | 25 ++----------- tests/pos-custom-args/captures/try4.scala | 35 ++++++++++++++++++ .../captures/try5.scala.pending | 36 +++++++++++++++++++ 3 files changed, 74 insertions(+), 22 deletions(-) create mode 100644 tests/pos-custom-args/captures/try4.scala create mode 100644 tests/pos-custom-args/captures/try5.scala.pending diff --git a/tests/pos-custom-args/captures/try3.scala b/tests/pos-custom-args/captures/try3.scala index 1d1e9925da33..dd057fc92e4b 100644 --- a/tests/pos-custom-args/captures/try3.scala +++ b/tests/pos-custom-args/captures/try3.scala @@ -15,7 +15,7 @@ def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = def test1: Int = def f(a: Boolean): Boolean => CanThrow[IOException] ?=> Int = - handle { // error + handle { if !a then raise(IOException()) (b: Boolean) => (_: CanThrow[IOException]) ?=> if !b then raise(IOException()) @@ -25,27 +25,8 @@ def test1: Int = } handle { val g = f(true) - g(false) // would raise an uncaught exception - f(true)(false) // would raise an uncaught exception + g(false) + f(true)(false) } { ex => -1 } -/* -def test2: Int = - def f(a: Boolean): Boolean => CanThrow[IOException] ?=> Int = - handle { // error - if !a then raise(IOException()) - (b: Boolean) => - if !b then raise(IOException()) - 0 - } { - ex => (b: Boolean) => -1 - } - handle { - val g = f(true) - g(false) // would raise an uncaught exception - f(true)(false) // would raise an uncaught exception - } { - ex => -1 - } -*/ \ No newline at end of file diff --git a/tests/pos-custom-args/captures/try4.scala b/tests/pos-custom-args/captures/try4.scala new file mode 100644 index 000000000000..0ac654035295 --- /dev/null +++ b/tests/pos-custom-args/captures/try4.scala @@ -0,0 +1,35 @@ +import language.experimental.erasedDefinitions +import annotation.ability +import java.io.IOException + +class CanThrow[E] extends Retains[*] +type Top = Any retains * +infix type ==> [A, B] = (A => B) retains * +class OtherCap extends Retains[*] + +def handle[E <: Exception, T <: Top](op: (CanThrow[E] ?=> T))(handler: (E => T) retains T): T = + val x: CanThrow[E] = ??? + try op(using x) + catch case ex: E => handler(ex) + +def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = + throw ex + +def test2: Int = + def f(c: OtherCap, a: Boolean): Boolean => CanThrow[IOException] ?=> Int = + handle { + if !a then raise(IOException()) + (b: Boolean) => (_: CanThrow[IOException]) ?=> + if !b then raise(IOException()) + 1 + } { + ex => (b: Boolean) => (_: CanThrow[IOException]) ?=> -1 + } + handle { + val c = OtherCap() + val g = f(c, true) + g(false) + f(c, true)(false) + } { + ex => -1 + } diff --git a/tests/pos-custom-args/captures/try5.scala.pending b/tests/pos-custom-args/captures/try5.scala.pending new file mode 100644 index 000000000000..05c5e95a8c7a --- /dev/null +++ b/tests/pos-custom-args/captures/try5.scala.pending @@ -0,0 +1,36 @@ +import language.experimental.erasedDefinitions +import annotation.ability +import java.io.IOException + +class CanThrow[E] extends Retains[*] +type Top = Any retains * +infix type ==> [A, B] = (A => B) retains * +class OtherCap extends Retains[*] + +def handle[E <: Exception, T <: Top](op: (CanThrow[E] ?=> T) retains T)(handler: (E => T) retains T): T = + val x: CanThrow[E] = ??? + try op(using x) + catch case ex: E => handler(ex) + +def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = + throw ex + +def test2: Unit = + def f(c: OtherCap, a: Boolean): (Boolean => (CanThrow[IOException] ?=> (Int => Int) retains c.type) retains c.type) retains c.type = + handle { + if !a then raise(IOException()) + (b: Boolean) => (_: CanThrow[IOException]) ?=> + if !b then raise(IOException()) + (x: Int) => { 1 } + } { + ex => (b: Boolean) => (_: CanThrow[IOException]) ?=> (x: Int) => { c; -1 } + } + val c = OtherCap() + handle[IOException, (Int => Int) retains c.type] { + val g = f(c, true) + g(false) + f(c, true)(false) + } { + ex => (x: Int) => { c; -1 } + } + ()