From 504361067caa1e78704931932e3abe8fb647e9c1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 2 Jul 2021 17:39:07 +0200 Subject: [PATCH 1/7] Generalize rule for inferring capture sets from the result type. --- .../src/dotty/tools/dotc/typer/CheckCaptures.scala | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index 5a33ab726a9f..491b4e57154b 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -189,9 +189,10 @@ object CheckCaptures: .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 + * `(ARGS) => T` where `T` has capture set CS1, expand it to + * `(ARGS) => T retains CS2` where CS2 consists of those elements in CS1 + * that are not accounted for by the capture set of any argument in ARGS. + * The additions will be removed again if the function type is wrapped in an * explicit `retains` type. */ def addResultCaptures(tree: Tree)(using Context): Tree = @@ -199,9 +200,10 @@ object CheckCaptures: tree match case FunctionTypeTree(argTypes, resType) => val cs = resType.captureSet - if cs.nonEmpty && argTypes.forall(_.captureSet.isEmpty) - then (tree /: cs.elems)(addRetains) - else tree + (tree /: cs.elems)((t, ref) => + if argTypes.exists(_.captureSet.accountsFor(ref)) then t + else addRetains(t, ref) + ) case _ => tree else tree From d579930a6b839c60baf59b390862545b7da129c7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Jul 2021 17:25:48 +0200 Subject: [PATCH 2/7] Simplify typedAhead methods - No need to duplicate mode switches - Remove discrepancy between typedType and typedAheadType --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 8 ++------ compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 57f7cf0f7548..6702624eedee 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1389,14 +1389,10 @@ class Namer { typer: Typer => } def typedAheadType(tree: Tree, pt: Type = WildcardType)(using Context): tpd.Tree = - inMode(ctx.mode &~ Mode.PatternOrTypeBits | Mode.Type) { - typedAhead(tree, typer.typed(_, pt)) - } + typedAhead(tree, typer.typedType(_, pt)) def typedAheadExpr(tree: Tree, pt: Type = WildcardType)(using Context): tpd.Tree = - withoutMode(Mode.PatternOrTypeBits) { - typedAhead(tree, typer.typed(_, pt)) - } + typedAhead(tree, typer.typedExpr(_, pt)) def typedAheadAnnotation(tree: Tree)(using Context): tpd.Tree = typedAheadExpr(tree, defn.AnnotationClass.typeRef) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f020ea8f802c..6ac883990cd6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2962,7 +2962,7 @@ class Typer extends Namer def typedExpr(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = withoutMode(Mode.PatternOrTypeBits)(typed(tree, pt)) def typedType(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = // todo: retract mode between Type and Pattern? - withMode(Mode.Type)(typed(tree, pt)) + withMode(Mode.Type) { typed(tree, pt) } def typedPattern(tree: untpd.Tree, selType: Type = WildcardType)(using Context): Tree = withMode(Mode.Pattern)(typed(tree, selType)) From 098833e959b21ebc4903091fdd3447cc9d394a1a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Jul 2021 12:38:17 +0200 Subject: [PATCH 3/7] Workarounds for scala-meta failures scala-meta gives persistent parse errors for previous version of the two changes. I could not yet minimize. --- compiler/src/dotty/tools/dotc/core/Types.scala | 4 +--- compiler/src/dotty/tools/dotc/typer/Inferencing.scala | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5620e76ed0b0..f4cb1ca07041 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4639,9 +4639,7 @@ object Types { && !linkedVar.inst.isExactlyNothing && linkedVar.inst <:< this else - inst.isExactlyAny - && !linkedVar.inst.isExactlyAny - && this <:< linkedVar.inst + 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) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 93466b536f64..17db403bcfdf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -695,6 +695,7 @@ trait Inferencing { this: Typer => if !argType.isSingleton then argType = SkolemType(argType) argType <:< tvar case _ => + () end constrainIfDependentParamRef } From 5ffb5d6985efeaaac742600a0182a83c155e40f4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Jul 2021 12:40:17 +0200 Subject: [PATCH 4/7] Support abbreviated capturing annotations Infer additional captures in user-written types according to the rules given in the doc comment of `ExpandCaptures`. --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 2 + .../dotty/tools/dotc/core/CaptureSet.scala | 13 +- .../tools/dotc/typer/CheckCaptures.scala | 108 +------------ .../tools/dotc/typer/ExpandCaptures.scala | 147 ++++++++++++++++++ .../src/dotty/tools/dotc/typer/Namer.scala | 3 + .../dotty/tools/dotc/typer/TypeAssigner.scala | 7 +- .../src/dotty/tools/dotc/typer/Typer.scala | 37 +++-- tests/neg-custom-args/captures/capt1.check | 2 +- tests/neg-custom-args/captures/try.check | 33 ++-- tests/neg-custom-args/captures/try.scala | 10 +- tests/neg-custom-args/captures/try2.check | 33 ++-- tests/neg-custom-args/captures/try2.scala | 10 +- .../captures/try3-abbrev.scala | 4 +- .../pos-custom-args/captures/cc-expand.scala | 19 +++ 14 files changed, 251 insertions(+), 177 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/typer/ExpandCaptures.scala create mode 100644 tests/pos-custom-args/captures/cc-expand.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 08ad47c5359d..84bc938093ce 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -700,6 +700,8 @@ object Trees { */ class InferredTypeTree[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends TypeTree[T] + class SimplifiedTypeTree[-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) extends DenotingTree[T] with TypTree[T] { diff --git a/compiler/src/dotty/tools/dotc/core/CaptureSet.scala b/compiler/src/dotty/tools/dotc/core/CaptureSet.scala index 6f7d03b92733..b280ddfe083c 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 private (elems: CaptureSet.Refs) extends Showable: +case class CaptureSet(elems: CaptureSet.Refs) extends Showable: import CaptureSet.* def isEmpty: Boolean = elems.isEmpty @@ -26,6 +26,9 @@ case class CaptureSet private (elems: CaptureSet.Refs) extends Showable: if elems.contains(ref) then this else CaptureSet(elems + ref) + def -- (that: CaptureSet)(using Context) = + CaptureSet(elems.filter(!that.accountsFor(_))) + def intersect (that: CaptureSet): CaptureSet = CaptureSet(this.elems.intersect(that.elems)) @@ -46,6 +49,10 @@ case class CaptureSet private (elems: CaptureSet.Refs) extends Showable: case ref => ref.singletonCaptureSet } + def toRetainsTypeArg(using Context): Type = + ((NoType: Type) /: elems) ((tp, ref) => + if tp.exists then OrType(tp, ref, soft = false) else ref) + override def toString = elems.toString override def toText(printer: Printer): Text = @@ -106,3 +113,7 @@ object CaptureSet: recur(tp) .showing(i"capture set of $tp = $result", capt) + def fromRetainsTypeArg(tp: Type)(using Context): CaptureSet = tp match + case tp: CaptureRef if tp.isTracked => tp.singletonCaptureSet + case OrType(tp1, tp2) => fromRetainsTypeArg(tp1) ++ fromRetainsTypeArg(tp2) + case _ => empty diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index 491b4e57154b..b6ca719d19d4 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -137,6 +137,8 @@ class CheckCaptures extends RefineTypes: |The inferred arguments are: [$args%, %]""" case _ => s"type argument$notAllowed" report.error(msg, arg.srcPos) + case tree: SimplifiedTypeTree[_] => + checkWellFormed(tree.typeOpt, tree.srcPos) case tree: TypeTree => // it's inferred, no need to check case _: TypTree | _: Closure => @@ -151,108 +153,4 @@ 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` has capture set CS1, expand it to - * `(ARGS) => T retains CS2` where CS2 consists of those elements in CS1 - * that are not accounted for by the capture set of any argument in ARGS. - * The 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 - (tree /: cs.elems)((t, ref) => - if argTypes.exists(_.captureSet.accountsFor(ref)) then t - else addRetains(t, ref) - ) - 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 - +end CheckCaptures \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/typer/ExpandCaptures.scala b/compiler/src/dotty/tools/dotc/typer/ExpandCaptures.scala new file mode 100644 index 000000000000..583d7185eaf1 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/ExpandCaptures.scala @@ -0,0 +1,147 @@ +package dotty.tools +package dotc +package typer + +import core._ +import SymDenotations.* +import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.* +import Types._ +import Symbols._ +import StdNames._ +import Decorators._ +import ProtoTypes._ +import Inferencing.isFullyDefined +import config.Printers.capt +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, SrcPos} +import util.Chars.* +import Nullables._ +import transform.* +import scala.collection.mutable +import reporting._ +import ProtoTypes._ +import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions + +/** 1. Copy outer retains sets to functions on the right + * 2. Add tracked parameters to the capture sets of functions on the right + * 3. Copy captures of result to functions on the left unless accounted for or universally quantified. + */ +object ExpandCaptures: + import ast.tpd.* + + /** An extractor for all kinds of function type trees: simple, dependent, or polymoprhic. + */ + object FunctionType: + + /** Recover (1) type and (2) value parameters and (3) result of function type. + * Three cases: + * Simple functions: type parameters are empty, value parameters are general types + * Dependent functions: type parameters are empty, value patameters are ParamRefs + * Polymorphic fucntions: type parameters are nonempty, value parameters are ParamRefs + */ + def unapply(tpe: Type)(using Context): Option[(List[TypeParamRef], List[Type], Type)] = tpe match + case RefinedType(parent, nme.apply, rinfo: MethodType) + if defn.isNonRefinedFunction(parent) => + Some((Nil, rinfo.paramRefs, rinfo.resType)) + case RefinedType(parent, nme.apply, rinfo: PolyType) + if parent.isRef(defn.PolyFunctionClass) => + rinfo.resType match + case mt: MethodType => Some((rinfo.paramRefs, mt.paramRefs, mt.resType)) + case _ => Some((rinfo.paramRefs, Nil, rinfo.resType)) + case defn.FunctionOf(argTypes, resType, _, _) => + Some((Nil, argTypes, resType)) + case _ => + None + + /** A copy of `tpe` (which must be a FunctionType) with a new `result` */ + def copy(tpe: Type)(result: Type)(using Context): Type = tpe match + case rt @ RefinedType(parent, app, rinfo: MethodType) => + rt.derivedRefinedType(parent, app, rinfo.derivedLambdaType(resType = result)) + case rt @ RefinedType(parent, app, rinfo: PolyType) => + rt.derivedRefinedType(parent, app, + rinfo.derivedLambdaType( + resType = rinfo.resType match + case mt: MethodType => mt.derivedLambdaType(resType = result) + case _ => result + ) + ) + case defn.FunctionOf(argTypes, resType, isContextual, isErased) => + if resType ne result then defn.FunctionOf(argTypes, result, isContextual, isErased) + else tpe + + /** Convert non-dependent FunctionType to equivalent refined type */ + def toDependent(tpe: Type)(using Context) = tpe match + case defn.FunctionOf(argTypes, resType, isContextual, isErased) => + val methodType = MethodType.companion(isContextual = isContextual, isErased = isErased) + val rinfo = methodType(argTypes, resType) + RefinedType(tpe, nme.apply, rinfo) + + end FunctionType + + /** Add implied capture sets to function type trees + * @param bound the set of bound type variables enclosing the current tree + * @param outerCaptures the implied captures coming from the outside + * @param canAdd whether implied captures can be added to the next enclosed function tree. + * This is false if the function tree is directly enclosed in an explicit retains node. + */ + private def addImplied(tpe: Type, bound: CaptureSet, outerCaptures: CaptureSet, canAdd: Boolean, pos: SrcPos)(using Context): Type = + trace(i"addImplied $tpe, bound = $bound, outer = $outerCaptures, can add = $canAdd", capt, show = true) { + + def paramCaptures(args: List[Type]): CaptureSet = + args.foldLeft(CaptureSet.empty)((cs, arg) => cs ++ arg.captureSet) + + def nestedCaptures(tpe: Type): CaptureSet = tpe match + case CapturingType(parent, _) => + nestedCaptures(parent) + case FunctionType(tparams, params, body) => + body.captureSet -- paramCaptures(tparams) -- paramCaptures(params) + case _ => + CaptureSet.empty + + def wrapImplied(tpe: Type) = + if canAdd then + (tpe /: (outerCaptures ++ nestedCaptures(tpe)).elems)(CapturingType(_, _)) + else tpe + + def reportOverlap(declared: CaptureSet, implied: CaptureSet, direction: String): Unit = + val redundant = declared.intersect(implied) + if redundant.nonEmpty then + report.echo( + i"declared captures $redundant in $tpe are redundant since they are already implied by the capture sets coming from the $direction", + pos) + + tpe match + case tpe @ CapturingType(parent, ref) => + reportOverlap(ref.singletonCaptureSet, outerCaptures, "left") + reportOverlap(ref.singletonCaptureSet, nestedCaptures(parent), "right") + val parent1 = addImplied(parent, bound, outerCaptures + ref, canAdd = false, pos) + tpe.derivedCapturingType(parent1, ref) + case FunctionType(tparams, params, body) => + val newParamCaptures = paramCaptures(params) -- bound -- outerCaptures + if newParamCaptures.nonEmpty && !tpe.isInstanceOf[RefinedType] then + val refined = FunctionType.toDependent(tpe) + val refined1 = addImplied(refined, bound, outerCaptures, canAdd, pos) + if refined1 ne refined then refined1 else tpe + else + wrapImplied( + FunctionType.copy(tpe)( + addImplied( + body, + bound ++ paramCaptures(tparams), + outerCaptures ++ newParamCaptures, + canAdd = true, pos))) + case _ => + tpe + } + end addImplied + + def apply(tpe: Type, pos: SrcPos)(using Context): Type = + addImplied(tpe, CaptureSet.empty, CaptureSet.empty, canAdd = true, pos) + +end ExpandCaptures + diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 6702624eedee..163e7fa88269 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1376,6 +1376,9 @@ class Namer { typer: Typer => ctx.compilationUnit.suspend() } + def isTypedAhead(tree: Tree)(using Context): Boolean = + expanded(tree).hasAttachment(TypedAhead) + /** Typecheck `tree` during completion using `typed`, and remember result in TypedAhead map */ def typedAhead(tree: Tree, typed: untpd.Tree => tpd.Tree)(using Context): tpd.Tree = { val xtree = expanded(tree) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 32a1200c5c4d..ba3fdbfebc41 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -12,7 +12,6 @@ import config.Printers.typr import ast.Trees._ import NameOps._ import ProtoTypes._ -import CheckCaptures.refineNestedCaptures import collection.mutable import reporting._ import Checking.{checkNoPrivateLeaks, checkNoWildcard} @@ -190,6 +189,8 @@ 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 with capture set ${tp.captureSet} already contains $ref with capture set ${ref.captureSet}", tree.srcPos) CapturingType(tp, ref) else val reason = @@ -486,9 +487,7 @@ trait TypeAssigner { wrongNumberOfTypeArgs(tycon.tpe, tparams, args, tree.srcPos) else processAppliedType(tree, tycon.tpe.appliedTo(args.tpes)) - val tree1 = tree.withType(ownType) - if ctx.settings.Ycc.value then refineNestedCaptures(tree1) - else tree1 + tree.withType(ownType) 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 6ac883990cd6..b4295d9b242c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -28,7 +28,6 @@ import Checking._ import Inferencing._ import Dynamic.isDynamicExpansion import EtaExpansion.etaExpand -import CheckCaptures.addResultCaptures import TypeComparer.CompareResult import util.Spans._ import util.common._ @@ -1250,15 +1249,13 @@ class Typer extends Namer RefinedTypeTree(core, List(appDef), ctx.owner.asClass) end typedDependent - 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)) - } + 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)) } def typedFunctionValue(tree: untpd.Function, pt: Type)(using Context): Tree = { @@ -2961,8 +2958,24 @@ class Typer extends Namer def typedExpr(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = withoutMode(Mode.PatternOrTypeBits)(typed(tree, pt)) + def typedType(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = // todo: retract mode between Type and Pattern? - withMode(Mode.Type) { typed(tree, pt) } + val needsCaptureExpansion = + ctx.settings.Ycc.value + && !ctx.settings.YccNoAbbrev.value + && !ctx.mode.is(Mode.Type) // it's a toplevel type + && !ctx.isAfterTyper + && !ctx.typer.isInstanceOf[ReTyper] + && !isTypedAhead(tree) + && !tree.isInstanceOf[untpd.TypeTree] + val tree1 = withMode(Mode.Type) { typed(tree, pt) } + if needsCaptureExpansion then + val expanded = ExpandCaptures(tree1.tpe, tree.srcPos) + if expanded ne tree1.tpe then + report.echo(i"Expand ${tree1.tpe} to $expanded", tree.srcPos) + SimplifiedTypeTree().withType(expanded).withSpan(tree.span) + else tree1 + def typedPattern(tree: untpd.Tree, selType: Type = WildcardType)(using Context): Tree = withMode(Mode.Pattern)(typed(tree, selType)) @@ -3270,7 +3283,7 @@ class Typer extends Namer else if (tree.symbol.isPrimaryConstructor && tree.symbol.info.firstParamTypes.isEmpty) readapt(tree.appliedToNone) // insert () to primary constructors else - errorTree(tree, em"Missing arguments for $methodStr") + errorTree(tree, em"Missing arguments for $methodStr in $tree: ${tree.tpe.widen}") case _ => tryInsertApplyOrImplicit(tree, pt, locked) { errorTree(tree, MethodDoesNotTakeParameters(tree)) } diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index 6a52a1cd7481..5a07d187de1b 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -33,7 +33,7 @@ longer explanation available when compiling with `-explain` | Required: A longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/capt1.scala:32:13 ------------------------------------------------------------- +-- Error: tests/neg-custom-args/captures/capt1.scala:32:16 ------------------------------------------------------------- 32 | val z2 = h[() => Cap](() => x)(() => C()) // error | ^^^^^^^^^ | type argument is not allowed to capture the universal capability * diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 2a28933971c7..b8e4f49d91e3 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -5,34 +5,25 @@ | 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) retains *)]) - | Required: List[() => Int] +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:36:6 ------------------------------------------- +36 | () => // error + | ^ + | Found: (() => Int) retains x + | Required: () => Int +37 | raise(new Exception)(using x) +38 | 22 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 | () => +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:47:4 ------------------------------------------- +47 | () => // error + | ^ + | Found: (() => Int) retains x + | Required: () => Int 48 | raise(new Exception)(using x) 49 | 22 -50 |} { // error - | ^ - | Found: (() => Int) retains * - | Required: () => Int -51 | (ex: Exception) => () => 22 -52 |} 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 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 universal capability * - | - | 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 4784d055fccc..c79c7579d694 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -31,22 +31,22 @@ def test: List[() => Int] = (ex: Exception) => ??? } - val xx = handle { // error + val xx = handle { (x: CanThrow[Exception]) => - () => + () => // error raise(new Exception)(using x) 22 } { (ex: Exception) => () => 22 } val yy = xx :: Nil - yy // error + yy val global = handle { (x: CanThrow[Exception]) => - () => + () => // error raise(new Exception)(using x) 22 -} { // error +} { (ex: Exception) => () => 22 } \ No newline at end of file diff --git a/tests/neg-custom-args/captures/try2.check b/tests/neg-custom-args/captures/try2.check index a73ee901406d..f14de055939b 100644 --- a/tests/neg-custom-args/captures/try2.check +++ b/tests/neg-custom-args/captures/try2.check @@ -5,34 +5,25 @@ | 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] +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try2.scala:38:6 ------------------------------------------ +38 | () => // error + | ^ + | Found: (() => Int) retains x + | Required: () => Int +39 | raise(new Exception)(using x) +40 | 22 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 | () => +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try2.scala:49:4 ------------------------------------------ +49 | () => // error + | ^ + | Found: (() => Int) retains x + | Required: () => Int 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` -- 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 : *) --- 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 : *) - | - |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 469d9cf8d2f2..8966babb468a 100644 --- a/tests/neg-custom-args/captures/try2.scala +++ b/tests/neg-custom-args/captures/try2.scala @@ -33,22 +33,22 @@ def test: List[() => Int] = (ex: Exception) => ??? } - val xx = handle { // error + val xx = handle { (x: CanThrow[Exception]) => - () => + () => // error raise(new Exception)(using x) 22 } { (ex: Exception) => () => 22 } val yy = xx :: Nil - yy // error + yy val global = handle { (x: CanThrow[Exception]) => - () => + () => // error raise(new Exception)(using x) 22 -} { // error +} { (ex: Exception) => () => 22 } diff --git a/tests/neg-custom-args/captures/try3-abbrev.scala b/tests/neg-custom-args/captures/try3-abbrev.scala index f90ad6aff0aa..ce3379aab2b9 100644 --- a/tests/neg-custom-args/captures/try3-abbrev.scala +++ b/tests/neg-custom-args/captures/try3-abbrev.scala @@ -13,9 +13,9 @@ def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = @main def Test: Int = def f(a: Boolean) = - handle { // error + handle { if !a then raise(IOException()) - (b: Boolean) => + (b: Boolean) => // error if !b then raise(IOException()) 0 } { diff --git a/tests/pos-custom-args/captures/cc-expand.scala b/tests/pos-custom-args/captures/cc-expand.scala new file mode 100644 index 000000000000..c3732598904e --- /dev/null +++ b/tests/pos-custom-args/captures/cc-expand.scala @@ -0,0 +1,19 @@ +object Test: + + class A + class B + class C + class CTC + type CT = CTC retains * + + def test(ct: CT, dt: CT) = + + def x1: A => B retains ct.type = ??? + def x2: A => B => C retains ct.type = ??? + def x3: A => () => B => C retains ct.type = ??? + + def x4: (x: A retains ct.type) => B => C = ??? + + def x5: A => (x: B retains ct.type) => () => C retains dt.type = ??? + def x6: A => (x: B retains ct.type) => (() => C retains dt.type) retains x.type | dt.type = ??? + def x7: A => (x: B retains ct.type) => (() => C retains dt.type) retains x.type = ??? \ No newline at end of file From 5e072e42e413e5f539b01cd861a82f7a3ca165cd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Jul 2021 12:59:58 +0200 Subject: [PATCH 5/7] Print capturing types as in paper --- compiler/src/dotty/tools/dotc/config/Config.scala | 2 ++ .../src/dotty/tools/dotc/printing/PlainPrinter.scala | 6 +++++- tests/neg-custom-args/captures/boxmap.check | 4 ++-- tests/neg-custom-args/captures/capt1.check | 10 +++++----- tests/neg-custom-args/captures/try.check | 6 +++--- tests/neg-custom-args/captures/try2.check | 6 +++--- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index da2755b76423..06d51f54eaaa 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -224,4 +224,6 @@ object Config { * reduces the number of allocated denotations by ~50%. */ inline val reuseSymDenotations = true + + inline val printCaptureSetsAsPrefix = true } diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 120c954cd68a..43b52f9bdfcb 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -15,6 +15,7 @@ import util.SourcePosition import java.lang.Integer.toOctalString import scala.util.control.NonFatal import scala.annotation.switch +import config.Config class PlainPrinter(_ctx: Context) extends Printer { /** The context of all public methods in Printer and subclasses. @@ -188,7 +189,10 @@ class PlainPrinter(_ctx: Context) extends Printer { (" <: " ~ toText(bound) provided !bound.isAny) }.close case CapturingType(parent, ref) => - changePrec(InfixPrec)(toText(parent) ~ " retains " ~ toTextCaptureRef(ref)) + if Config.printCaptureSetsAsPrefix then + changePrec(GlobalPrec)("{" ~ toTextCaptureRef(ref) ~ "} " ~ toText(parent)) + else + 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/tests/neg-custom-args/captures/boxmap.check b/tests/neg-custom-args/captures/boxmap.check index fa9189457180..da1c38b4a887 100644 --- a/tests/neg-custom-args/captures/boxmap.check +++ b/tests/neg-custom-args/captures/boxmap.check @@ -1,8 +1,8 @@ -- [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]) retains B + | Found: {f} {b} () => Box[B] + | Required: {B} () => Box[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 5a07d187de1b..6ac34be33056 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -1,35 +1,35 @@ -- [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 + | Found: {x} () => C | 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 + | Found: {x} () => C | 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 + | Found: {x} Int => Int | 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 + | Found: {x} F | 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 + | Found: {x} A {...} | Required: A longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index b8e4f49d91e3..0ca5f28452ff 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) retains x + | Found: {x} () => Nothing | Required: () => Nothing longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:36:6 ------------------------------------------- 36 | () => // error | ^ - | Found: (() => Int) retains x + | Found: {x} () => Int | Required: () => Int 37 | raise(new Exception)(using x) 38 | 22 @@ -17,7 +17,7 @@ longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:47:4 ------------------------------------------- 47 | () => // error | ^ - | Found: (() => Int) retains x + | Found: {x} () => Int | Required: () => Int 48 | raise(new Exception)(using x) 49 | 22 diff --git a/tests/neg-custom-args/captures/try2.check b/tests/neg-custom-args/captures/try2.check index f14de055939b..31108c63d083 100644 --- a/tests/neg-custom-args/captures/try2.check +++ b/tests/neg-custom-args/captures/try2.check @@ -1,14 +1,14 @@ -- [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 + | Found: {x} () => Nothing | Required: () => Nothing longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try2.scala:38:6 ------------------------------------------ 38 | () => // error | ^ - | Found: (() => Int) retains x + | Found: {x} () => Int | Required: () => Int 39 | raise(new Exception)(using x) 40 | 22 @@ -17,7 +17,7 @@ longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try2.scala:49:4 ------------------------------------------ 49 | () => // error | ^ - | Found: (() => Int) retains x + | Found: {x} () => Int | Required: () => Int 50 | raise(new Exception)(using x) 51 | 22 From 78760e2d0a2755a170e1c961d01ca06b933db328 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Jul 2021 14:46:42 +0200 Subject: [PATCH 6/7] Allow paper syntax to parse capturing types --- .../dotty/tools/dotc/core/Definitions.scala | 1 + .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../tools/dotc/core/tasty/TreePickler.scala | 4 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 6 +-- .../dotty/tools/dotc/parsing/Parsers.scala | 37 ++++++++++++++++++- .../src/dotty/tools/dotc/parsing/Tokens.scala | 4 +- .../tools/dotc/printing/PlainPrinter.scala | 4 +- .../tools/dotc/printing/RefinedPrinter.scala | 2 + .../dotty/tools/dotc/typer/TypeAssigner.scala | 3 ++ .../scala/runtime/stdLibPatches/Predef.scala | 1 + tests/pos-custom-args/captures/boxmap.scala | 10 +++-- .../pos-custom-args/captures/cc-expand.scala | 2 + 12 files changed, 60 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 397392dbec84..9bd1bedc7e04 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -475,6 +475,7 @@ class Definitions { @tu lazy val Predef_identity : Symbol = ScalaPredefModule.requiredMethod(nme.identity) @tu lazy val Predef_undefined: Symbol = ScalaPredefModule.requiredMethod(nme.???) @tu lazy val Predef_retainsType: Symbol = ScalaPredefModule.requiredType(tpnme.retains) + @tu lazy val Predef_capturing: Symbol = ScalaPredefModule.requiredType(tpnme.CAPTURING) @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 bd8a9ffa554e..362341349cc3 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -119,6 +119,7 @@ object StdNames { val BITMAP_TRANSIENT: N = s"${BITMAP_PREFIX}trans$$" // initialization bitmap for transient lazy vals val BITMAP_CHECKINIT: N = s"${BITMAP_PREFIX}init$$" // initialization bitmap for checkinit values val BITMAP_CHECKINIT_TRANSIENT: N = s"${BITMAP_PREFIX}inittrans$$" // initialization bitmap for transient checkinit values + val CAPTURING = "|>" val DEFAULT_GETTER: N = str.DEFAULT_GETTER val DEFAULT_GETTER_INIT: N = "$lessinit$greater" val DO_WHILE_PREFIX: N = "doWhile$" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index d3dab429d121..f52720aa94e6 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -291,9 +291,9 @@ class TreePickler(pickler: TastyPickler) { case tp: CapturingType => writeByte(APPLIEDtype) withLength { - pickleType(defn.Predef_retainsType.typeRef) - pickleType(tp.parent) + pickleType(defn.Predef_capturing.typeRef) pickleType(tp.ref) + pickleType(tp.parent) } case tpe: PolyType if richTypes => pickleMethodic(POLYtype, tpe, EmptyFlags) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index dbc7e9644954..632569298461 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -360,9 +360,9 @@ class TreeUnpickler(reader: TastyReader, val tycon = readType() val args = until(end)(readType()) tycon match - 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 tycon: TypeRef if tycon.symbol == defn.Predef_capturing => + if ctx.settings.Ycc.value then CapturingType.checked(args(1), args(0)) + else args(1) case _ => tycon.appliedTo(args) case TYPEBOUNDS => diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index cf49704619de..9e0351c4d50b 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -898,6 +898,24 @@ object Parsers { } } + def followingIsCaptureSet(): Boolean = + val lookahead = in.LookaheadScanner() + def recur(): Boolean = + lookahead.isIdent && { + lookahead.nextToken() + if lookahead.token == COMMA then + lookahead.nextToken() + recur() + else + lookahead.token == RBRACE && { + lookahead.nextToken() + canStartInfixTypeTokens.contains(lookahead.token) + || lookahead.token == LBRACKET + } + } + lookahead.nextToken() + recur() + /* --------- OPERAND/OPERATOR STACK --------------------------------------- */ var opStack: List[OpInfo] = Nil @@ -1329,17 +1347,27 @@ object Parsers { case _ => false } + def captureRef(): Tree = + atSpan(in.offset) { + val name = ident() + if name.isVarPattern then SingletonTypeTree(Ident(name)) + else Ident(name.toTypeName) + } + /** Type ::= FunType * | HkTypeParamClause ‘=>>’ Type * | FunParamClause ‘=>>’ Type * | MatchType * | InfixType + * | CaptureSet Type * FunType ::= (MonoFunType | PolyFunType) * MonoFunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type * PolyFunType ::= HKTypeParamClause '=>' Type * FunTypeArgs ::= InfixType * | `(' [ [ ‘[using]’ ‘['erased'] FunArgType {`,' FunArgType } ] `)' * | '(' [ ‘[using]’ ‘['erased'] TypedFunParam {',' TypedFunParam } ')' + * CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` + * CaptureRef ::= Ident */ def typ(): Tree = { val start = in.offset @@ -1438,6 +1466,11 @@ object Parsers { } else { accept(TLARROW); typ() } } + else if in.token == LBRACE && followingIsCaptureSet() then + val refs = inBraces { commaSeparated(captureRef) } + val t = typ() + val captured = refs.reduce(InfixOp(_, Ident(tpnme.raw.BAR), _)) + AppliedTypeTree(TypeTree(defn.Predef_capturing.typeRef), captured :: t :: Nil) else if (in.token == INDENT) enclosed(INDENT, typ()) else infixType() @@ -1506,7 +1539,7 @@ object Parsers { def infixType(): Tree = infixTypeRest(refinedType()) def infixTypeRest(t: Tree): Tree = - infixOps(t, canStartTypeTokens, refinedTypeFn, Location.ElseWhere, + infixOps(t, canStartInfixTypeTokens, refinedTypeFn, Location.ElseWhere, isType = true, isOperator = !followingIsVararg()) @@ -3142,7 +3175,7 @@ object Parsers { ImportSelector( atSpan(in.skipToken()) { Ident(nme.EMPTY) }, bound = - if canStartTypeTokens.contains(in.token) then rejectWildcardType(infixType()) + if canStartInfixTypeTokens.contains(in.token) then rejectWildcardType(infixType()) else EmptyTree) /** id [‘as’ (id | ‘_’) */ diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index cba07a6e5a34..7fadf341905d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -230,8 +230,8 @@ object Tokens extends TokensCommon { final val canStartExprTokens2: TokenSet = canStartExprTokens3 | BitSet(DO) - final val canStartTypeTokens: TokenSet = literalTokens | identifierTokens | BitSet( - THIS, SUPER, USCORE, LPAREN, AT) + final val canStartInfixTypeTokens: TokenSet = literalTokens | identifierTokens | BitSet( + THIS, SUPER, USCORE, LPAREN, LBRACE, AT) final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 43b52f9bdfcb..6b705f1ac590 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -280,7 +280,7 @@ class PlainPrinter(_ctx: Context) extends Printer { /** If -uniqid is set, the unique id of symbol, after a # */ protected def idString(sym: Symbol): String = - if (showUniqueIds || Printer.debugPrintUnique) "#" + sym.id else "" + if showUniqueIds then "#" + sym.id else "" def nameString(sym: Symbol): String = simpleNameString(sym) + idString(sym) // + "<" + (if (sym.exists) sym.owner else "") + ">" @@ -320,7 +320,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case tp @ ConstantType(value) => toText(value) case pref: TermParamRef => - nameString(pref.binder.paramNames(pref.paramNum)) + nameString(pref.binder.paramNames(pref.paramNum)) ~ lambdaHash(pref.binder) case tp: RecThis => val idx = openRecs.reverse.indexOf(tp.binder) if (idx >= 0) selfRecName(idx + 1) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 37fd56931a87..5f18f7c6344f 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -533,6 +533,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { 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 tpt.symbol == defn.Predef_capturing && args.length == 2 then + changePrec(GlobalPrec) { "{" ~ toText(args(0)) ~ "}" ~ toText(args(1)) } else if defn.isFunctionClass(tpt.symbol) && tpt.isInstanceOf[TypeTree] && tree.hasType && !printDebug then diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index ba3fdbfebc41..0d2a909f0ebb 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -211,6 +211,9 @@ trait TypeAssigner { else if constr == defn.Predef_retainsType then if ctx.settings.Ycc.value then captType(args(0), args(1)) else args(0) + else if constr == defn.Predef_capturing then + if ctx.settings.Ycc.value then captType(args(1), args(0)) + else args(1) else tp case _ => tp end processAppliedType diff --git a/library/src/scala/runtime/stdLibPatches/Predef.scala b/library/src/scala/runtime/stdLibPatches/Predef.scala index 1e9dc2303155..80a15ea3b765 100644 --- a/library/src/scala/runtime/stdLibPatches/Predef.scala +++ b/library/src/scala/runtime/stdLibPatches/Predef.scala @@ -51,4 +51,5 @@ object Predef: /** type `A` with capture set `B` */ infix type retains[A, B] + infix type |> [A, B] end Predef diff --git a/tests/pos-custom-args/captures/boxmap.scala b/tests/pos-custom-args/captures/boxmap.scala index 50a84e5c6ae5..eb5632611bc6 100644 --- a/tests/pos-custom-args/captures/boxmap.scala +++ b/tests/pos-custom-args/captures/boxmap.scala @@ -1,9 +1,9 @@ type Top = Any retains * class Cap extends Retains[*] -infix type ==> [A, B] = (A => B) retains * +infix type ==> [A, B] = {*} (A => B) -type Box[+T <: Top] = ([K <: Top] => (T ==> K) => K) retains T +type Box[+T <: Top] = {T} [K <: Top] => (T ==> K) => K def box[T <: Top](x: T): Box[T] = [K <: Top] => (k: T ==> K) => k(x) @@ -11,11 +11,13 @@ 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 = +def lazymap[A <: Top, B <: Top](b: Box[A])(f: A ==> B): {b, f} () => Box[B] = () => 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) retains b.type = lazymap[A, B] + val x: (b: Box[A]) => {b} ((f: A ==> B) => {b, f} () => Box[B]) = lazymap[A, B] + // Does not work yet: + // val x_red: (b: Box[A]) => ((f: A ==> B) => {b, f} () => Box[B]) = lazymap[A, B] () diff --git a/tests/pos-custom-args/captures/cc-expand.scala b/tests/pos-custom-args/captures/cc-expand.scala index c3732598904e..bb94f8fd3ba1 100644 --- a/tests/pos-custom-args/captures/cc-expand.scala +++ b/tests/pos-custom-args/captures/cc-expand.scala @@ -8,6 +8,8 @@ object Test: def test(ct: CT, dt: CT) = + def x0: A => {ct} B = ??? + def x1: A => B retains ct.type = ??? def x2: A => B => C retains ct.type = ??? def x3: A => () => B => C retains ct.type = ??? From c1dc47b3e14679c7f467e3f8f7af3699f812a7ab Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Jul 2021 18:04:07 +0200 Subject: [PATCH 7/7] Tweak type expansion - Fix bug in copy for dependent function types: parent type needs to be changed as well if refinement has changed. - Refine redundant type checking to eliminate false notices. --- .../dotty/tools/dotc/typer/ExpandCaptures.scala | 17 ++++++++++------- compiler/src/dotty/tools/dotc/typer/Typer.scala | 5 ++++- tests/pos-custom-args/captures/boxmap.scala | 5 ++--- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ExpandCaptures.scala b/compiler/src/dotty/tools/dotc/typer/ExpandCaptures.scala index 583d7185eaf1..84be74f3d428 100644 --- a/compiler/src/dotty/tools/dotc/typer/ExpandCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/ExpandCaptures.scala @@ -61,7 +61,12 @@ object ExpandCaptures: /** A copy of `tpe` (which must be a FunctionType) with a new `result` */ def copy(tpe: Type)(result: Type)(using Context): Type = tpe match case rt @ RefinedType(parent, app, rinfo: MethodType) => - rt.derivedRefinedType(parent, app, rinfo.derivedLambdaType(resType = result)) + val rinfo1 = rinfo.derivedLambdaType(resType = result).asInstanceOf[MethodType] + if rinfo1 eq rinfo then rt + else + val defn.FunctionOf(argTypes, resType, isContextual, isErased) = parent + val parent1 = defn.FunctionOf(argTypes, rinfo1.nonDependentResultApprox, isContextual, isErased) + RefinedType(parent1, app, rinfo1) case rt @ RefinedType(parent, app, rinfo: PolyType) => rt.derivedRefinedType(parent, app, rinfo.derivedLambdaType( @@ -108,17 +113,15 @@ object ExpandCaptures: (tpe /: (outerCaptures ++ nestedCaptures(tpe)).elems)(CapturingType(_, _)) else tpe - def reportOverlap(declared: CaptureSet, implied: CaptureSet, direction: String): Unit = - val redundant = declared.intersect(implied) - if redundant.nonEmpty then + def reportOverlap(declared: CaptureSet, implied: CaptureSet): Unit = + if declared <:< implied && implied <:< declared then report.echo( - i"declared captures $redundant in $tpe are redundant since they are already implied by the capture sets coming from the $direction", + i"declared captures $declared in $tpe are redundant since they are already implied by the capture sets coming from the context", pos) tpe match case tpe @ CapturingType(parent, ref) => - reportOverlap(ref.singletonCaptureSet, outerCaptures, "left") - reportOverlap(ref.singletonCaptureSet, nestedCaptures(parent), "right") + reportOverlap(tpe.captureSet, outerCaptures ++ nestedCaptures(parent)) val parent1 = addImplied(parent, bound, outerCaptures + ref, canAdd = false, pos) tpe.derivedCapturingType(parent1, ref) case FunctionType(tparams, params, body) => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b4295d9b242c..5fa4c2757dcd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -49,6 +49,7 @@ import reporting._ import Nullables._ import NullOpsDecorator._ import config.Config +import dotty.tools.dotc.ast.untpd.DependentTypeTree object Typer { @@ -2968,11 +2969,13 @@ class Typer extends Namer && !ctx.typer.isInstanceOf[ReTyper] && !isTypedAhead(tree) && !tree.isInstanceOf[untpd.TypeTree] + && !tree.isInstanceOf[DependentTypeTree] val tree1 = withMode(Mode.Type) { typed(tree, pt) } if needsCaptureExpansion then val expanded = ExpandCaptures(tree1.tpe, tree.srcPos) if expanded ne tree1.tpe then - report.echo(i"Expand ${tree1.tpe} to $expanded", tree.srcPos) + report.echo(i"""Expand ${tree1.tpe} + | to $expanded""", tree.srcPos) SimplifiedTypeTree().withType(expanded).withSpan(tree.span) else tree1 diff --git a/tests/pos-custom-args/captures/boxmap.scala b/tests/pos-custom-args/captures/boxmap.scala index eb5632611bc6..fcb9bb4c75c3 100644 --- a/tests/pos-custom-args/captures/boxmap.scala +++ b/tests/pos-custom-args/captures/boxmap.scala @@ -17,7 +17,6 @@ def lazymap[A <: Top, B <: Top](b: Box[A])(f: A ==> B): {b, f} () => Box[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]) => {b} ((f: A ==> B) => {b, f} () => Box[B]) = lazymap[A, B] - // Does not work yet: - // val x_red: (b: Box[A]) => ((f: A ==> B) => {b, f} () => Box[B]) = lazymap[A, B] + val x: (b: Box[A]) => {b} (f: A ==> B) => {b, f} () => Box[B] = lazymap[A, B] + val x_red: (b: Box[A]) => ((f: A ==> B) => () => Box[B]) = lazymap[A, B] ()