From bab020a4f241a871644b780797ab428f4d565096 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 25 Dec 2021 11:53:08 +0100 Subject: [PATCH] Pure function types -> and ?-> 1. Allow `->` and `?->` and function operators, treated like `=>` and `?=>`. 2. under -Ycc treat `->` and `?->` as immutable function types, whereas `A => B` is an alias of `{*} A -> B` and `A ?=> B` is an alias of `{*} A ?-> B`. Closures are unaffected, we still use `=>` for all closures where they are pure or not. Improve printing of capturing types Avoid explicit retains annotations also outside phase cc Generate "Impure" function aliases For every (possibly erased and/or context) function class XFunctionN, generate an alias ImpureXFunctionN in the Scala package defined as type ImpureXFunctionN[...] = {*} XFunctionN[...] Also: - Fix a bug in TypeComparer: glb has to test subCapture in a frozen state - Harden EventuallyCapturingType extractor to not crash on illegal capture sets - Cleanup transformation of inferred types --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 6 +- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 20 +- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 6 + .../dotty/tools/dotc/cc/CapturingType.scala | 18 +- .../dotty/tools/dotc/core/Definitions.scala | 124 +++++++---- .../src/dotty/tools/dotc/core/Flags.scala | 4 +- .../src/dotty/tools/dotc/core/NameOps.scala | 40 ++-- .../src/dotty/tools/dotc/core/StdNames.scala | 2 + .../dotty/tools/dotc/core/TypeComparer.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 6 +- .../dotty/tools/dotc/parsing/Parsers.scala | 63 +++--- .../tools/dotc/printing/PlainPrinter.scala | 13 +- .../tools/dotc/printing/RefinedPrinter.scala | 52 +++-- .../tools/dotc/typer/CheckCaptures.scala | 206 ++++++++++++------ .../dotty/tools/dotc/typer/Implicits.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 9 +- .../quoted/runtime/impl/QuotesImpl.scala | 2 +- tests/neg-custom-args/capt-wf.scala | 6 +- tests/neg-custom-args/captures/bounded.scala | 4 +- tests/neg-custom-args/captures/boxmap.check | 8 +- tests/neg-custom-args/captures/boxmap.scala | 10 +- tests/neg-custom-args/captures/byname.scala | 2 +- .../captures/capt-box-env.scala | 5 +- tests/neg-custom-args/captures/capt-box.scala | 6 +- tests/neg-custom-args/captures/capt-env.scala | 2 +- tests/neg-custom-args/captures/capt1.check | 14 +- tests/neg-custom-args/captures/capt1.scala | 8 +- tests/neg-custom-args/captures/capt2.scala | 6 +- tests/neg-custom-args/captures/capt3.scala | 8 +- tests/neg-custom-args/captures/io.scala | 6 +- tests/neg-custom-args/captures/lazylist.check | 2 +- tests/neg-custom-args/captures/lazylist.scala | 6 +- .../neg-custom-args/captures/lazylists1.scala | 2 +- .../neg-custom-args/captures/lazylists2.scala | 10 +- tests/neg-custom-args/captures/lazyref.check | 8 +- tests/neg-custom-args/captures/lazyref.scala | 8 +- tests/neg-custom-args/captures/try.check | 14 +- tests/neg-custom-args/captures/try.scala | 2 +- tests/neg-custom-args/captures/vars.check | 24 +- tests/neg-custom-args/captures/vars.scala | 14 +- tests/pos-custom-args/captures/bounded.scala | 4 +- .../captures/boxmap-paper.scala | 23 +- tests/pos-custom-args/captures/boxmap.scala | 14 +- .../captures/capt-capability.scala | 12 +- .../captures/capt-depfun.scala | 12 +- .../captures/capt-depfun2.scala | 2 +- .../pos-custom-args/captures/capt-test.scala | 8 +- tests/pos-custom-args/captures/capt1.scala | 12 +- tests/pos-custom-args/captures/capt2.scala | 12 +- .../pos-custom-args/captures/cc-expand.scala | 16 +- .../pos-custom-args/captures/impurefun.scala | 8 + .../captures/lazylists-mono.scala | 8 +- .../pos-custom-args/captures/lazylists.scala | 2 +- .../pos-custom-args/captures/lazylists1.scala | 10 +- tests/pos-custom-args/captures/lazyref.scala | 11 +- .../captures/list-encoding.scala | 4 +- tests/pos-custom-args/captures/lists.scala | 87 ++++---- tests/pos-custom-args/captures/pairs.scala | 13 +- tests/pos-custom-args/captures/try.scala | 4 +- tests/pos-custom-args/captures/try3.scala | 5 +- tests/pos-custom-args/captures/vars.scala | 11 +- tests/pos/i12723.scala | 8 +- tests/pos/impurefun.scala | 4 + 63 files changed, 594 insertions(+), 446 deletions(-) create mode 100644 tests/pos-custom-args/captures/impurefun.scala create mode 100644 tests/pos/impurefun.scala diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index b9960cbb4652..740b2e3d9ab8 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -70,13 +70,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class InterpolatedString(id: TermName, segments: List[Tree])(implicit @constructorOnly src: SourceFile) extends TermTree - /** A function type */ + /** A function type or closure */ case class Function(args: List[Tree], body: Tree)(implicit @constructorOnly src: SourceFile) extends Tree { override def isTerm: Boolean = body.isTerm override def isType: Boolean = body.isType } - /** A function type with `implicit`, `erased`, or `given` modifiers */ + /** A function type or closure with `implicit`, `erased`, or `given` modifiers */ class FunctionWithMods(args: List[Tree], body: Tree, val mods: Modifiers)(implicit @constructorOnly src: SourceFile) extends Function(args, body) @@ -217,6 +217,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Transparent()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Transparent) case class Infix()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Infix) + + case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure) } /** Modifiers and annotations for definitions diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 09064314b1bf..4c201d7edf54 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -17,9 +17,13 @@ def retainedElems(tree: Tree)(using Context): List[Tree] = tree match case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) => elems case _ => Nil +class IllegalCaptureRef(tpe: Type) extends Exception + extension (tree: Tree) - def toCaptureRef(using Context): CaptureRef = tree.tpe.asInstanceOf[CaptureRef] + def toCaptureRef(using Context): CaptureRef = tree.tpe match + case ref: CaptureRef => ref + case tpe => throw IllegalCaptureRef(tpe) def toCaptureSet(using Context): CaptureSet = tree.getAttachment(Captures) match @@ -59,20 +63,6 @@ extension (tp: Type) def isBoxedCapturing(using Context) = !tp.boxedCaptured.isAlwaysEmpty - def canHaveInferredCapture(using Context): Boolean = tp match - case tp: TypeRef if tp.symbol.isClass => - !tp.symbol.isValueClass && tp.symbol != defn.AnyClass - case _: TypeVar | _: TypeParamRef => - false - case tp: TypeProxy => - tp.superType.canHaveInferredCapture - case tp: AndType => - tp.tp1.canHaveInferredCapture && tp.tp2.canHaveInferredCapture - case tp: OrType => - tp.tp1.canHaveInferredCapture || tp.tp2.canHaveInferredCapture - case _ => - false - def stripCapturing(using Context): Type = tp.dealiasKeepAnnots match case CapturingType(parent, _, _) => parent.stripCapturing diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index cd8d67399d8d..82e5e6e14a4b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -56,6 +56,12 @@ sealed abstract class CaptureSet extends Showable: assert(v.isConst) Const(v.elems) + final def isUniversal(using Context) = + elems.exists { + case ref: TermRef => ref.symbol == defn.captureRoot + case _ => false + } + /** Cast to variable. @pre: !isConst */ def asVar: Var = assert(!isConst) diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index 2eeb1ff41b72..738e746d0178 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -12,10 +12,22 @@ object CapturingType: else AnnotatedType(parent, CaptureAnnotation(refs, boxed)) def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet, Boolean)] = - if ctx.phase == Phases.checkCapturesPhase && tp.annot.symbol == defn.RetainsAnnot then + if ctx.phase == Phases.checkCapturesPhase then EventuallyCapturingType.unapply(tp) + else None + +end CapturingType + +object EventuallyCapturingType: + + def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet, Boolean)] = + if tp.annot.symbol == defn.RetainsAnnot then tp.annot match case ann: CaptureAnnotation => Some((tp.parent, ann.refs, ann.boxed)) - case ann => Some((tp.parent, ann.tree.toCaptureSet, ann.tree.isBoxedCapturing)) + case ann => + try Some((tp.parent, ann.tree.toCaptureSet, ann.tree.isBoxedCapturing)) + catch case ex: IllegalCaptureRef => None else None -end CapturingType +end EventuallyCapturingType + + diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 513833ea2d12..e2eddcfaef21 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -8,7 +8,7 @@ import Flags._, Scopes._, Decorators._, NameOps._, Periods._, NullOpsDecorator._ import unpickleScala2.Scala2Unpickler.ensureConstructor import scala.collection.mutable import collection.mutable -import Denotations.SingleDenotation +import Denotations.{SingleDenotation, staticRef} import util.{SimpleIdentityMap, SourceFile, NoSource} import typer.ImportInfo.RootRef import Comments.CommentsContext @@ -86,7 +86,7 @@ class Definitions { * * FunctionN traits follow this template: * - * trait FunctionN[T0,...T{N-1}, R] extends Object { + * trait FunctionN[-T0,...-T{N-1}, +R] extends Object { * def apply($x0: T0, ..., $x{N_1}: T{N-1}): R * } * @@ -96,46 +96,65 @@ class Definitions { * * ContextFunctionN traits follow this template: * - * trait ContextFunctionN[T0,...,T{N-1}, R] extends Object { + * trait ContextFunctionN[-T0,...,-T{N-1}, +R] extends Object { * def apply(using $x0: T0, ..., $x{N_1}: T{N-1}): R * } * * ErasedFunctionN traits follow this template: * - * trait ErasedFunctionN[T0,...,T{N-1}, R] extends Object { + * trait ErasedFunctionN[-T0,...,-T{N-1}, +R] extends Object { * def apply(erased $x0: T0, ..., $x{N_1}: T{N-1}): R * } * * ErasedContextFunctionN traits follow this template: * - * trait ErasedContextFunctionN[T0,...,T{N-1}, R] extends Object { + * trait ErasedContextFunctionN[-T0,...,-T{N-1}, +R] extends Object { * def apply(using erased $x0: T0, ..., $x{N_1}: T{N-1}): R * } * * ErasedFunctionN and ErasedContextFunctionN erase to Function0. + * + * EffXYZFunctionN afollow this template: + * + * type EffXYZFunctionN[-T0,...,-T{N-1}, +R] = {*} XYZFunctionN[T0,...,T{N-1}, R] */ - def newFunctionNTrait(name: TypeName): ClassSymbol = { + private def newFunctionNType(name: TypeName): Symbol = { + val impure = name.startsWith("Impure") val completer = new LazyType { def complete(denot: SymDenotation)(using Context): Unit = { - val cls = denot.asClass.classSymbol - val decls = newScope val arity = name.functionArity - val paramNamePrefix = tpnme.scala ++ str.NAME_JOIN ++ name ++ str.EXPAND_SEPARATOR - val argParamRefs = List.tabulate(arity) { i => - enterTypeParam(cls, paramNamePrefix ++ "T" ++ (i + 1).toString, Contravariant, decls).typeRef - } - val resParamRef = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls).typeRef - val methodType = MethodType.companion( - isContextual = name.isContextFunction, - isImplicit = false, - isErased = name.isErasedFunction) - decls.enter(newMethod(cls, nme.apply, methodType(argParamRefs, resParamRef), Deferred)) - denot.info = - ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: Nil, decls) + if impure then + val argParamNames = List.tabulate(arity)(tpnme.syntheticTypeParamName) + val argVariances = List.fill(arity)(Contravariant) + val underlyingName = name.asSimpleName.drop(6) + val underlyingClass = ScalaPackageVal.requiredClass(underlyingName) + denot.info = TypeAlias( + HKTypeLambda(argParamNames :+ "R".toTypeName, argVariances :+ Covariant)( + tl => List.fill(arity + 1)(TypeBounds.empty), + tl => CapturingType(underlyingClass.typeRef.appliedTo(tl.paramRefs), + CaptureSet.universal, boxed = false) + )) + else + val cls = denot.asClass.classSymbol + val decls = newScope + val paramNamePrefix = tpnme.scala ++ str.NAME_JOIN ++ name ++ str.EXPAND_SEPARATOR + val argParamRefs = List.tabulate(arity) { i => + enterTypeParam(cls, paramNamePrefix ++ "T" ++ (i + 1).toString, Contravariant, decls).typeRef + } + val resParamRef = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls).typeRef + val methodType = MethodType.companion( + isContextual = name.isContextFunction, + isImplicit = false, + isErased = name.isErasedFunction) + decls.enter(newMethod(cls, nme.apply, methodType(argParamRefs, resParamRef), Deferred)) + denot.info = + ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: Nil, decls) } } - val flags = Trait | NoInits - newPermanentClassSymbol(ScalaPackageClass, name, flags, completer) + if impure then + newPermanentSymbol(ScalaPackageClass, name, EmptyFlags, completer) + else + newPermanentClassSymbol(ScalaPackageClass, name, Trait | NoInits, completer) } private def newMethod(cls: ClassSymbol, name: TermName, info: Type, flags: FlagSet = EmptyFlags): TermSymbol = @@ -209,7 +228,7 @@ class Definitions { val cls = ScalaPackageVal.moduleClass.asClass cls.info.decls.openForMutations.useSynthesizer( name => - if (name.isTypeName && name.isSyntheticFunction) newFunctionNTrait(name.asTypeName) + if (name.isTypeName && name.isSyntheticFunction) newFunctionNType(name.asTypeName) else NoSymbol) cls } @@ -1273,37 +1292,54 @@ class Definitions { @tu lazy val TupleType: Array[TypeRef] = mkArityArray("scala.Tuple", MaxTupleArity, 1) + /** Cached function types of arbitary arities. + * Function types are created on demand with newFunctionNTrait, which is + * called from a synthesizer installed in ScalaPackageClass. + */ private class FunType(prefix: String): private var classRefs: Array[TypeRef] = new Array(22) + def apply(n: Int): TypeRef = while n >= classRefs.length do val classRefs1 = new Array[TypeRef](classRefs.length * 2) Array.copy(classRefs, 0, classRefs1, 0, classRefs.length) classRefs = classRefs1 + val funName = s"scala.$prefix$n" if classRefs(n) == null then - classRefs(n) = requiredClassRef(prefix + n.toString) + classRefs(n) = + if prefix.startsWith("Impure") + then staticRef(funName.toTypeName).symbol.typeRef + else requiredClassRef(funName) classRefs(n) - - private val erasedContextFunType = FunType("scala.ErasedContextFunction") - private val contextFunType = FunType("scala.ContextFunction") - private val erasedFunType = FunType("scala.ErasedFunction") - private val funType = FunType("scala.Function") - - def FunctionClass(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): Symbol = - ( if isContextual && isErased then erasedContextFunType(n) - else if isContextual then contextFunType(n) - else if isErased then erasedFunType(n) - else funType(n) - ).symbol.asClass + end FunType + + private def funTypeIdx(isContextual: Boolean, isErased: Boolean, isImpure: Boolean): Int = + (if isContextual then 1 else 0) + + (if isErased then 2 else 0) + + (if isImpure then 4 else 0) + + private val funTypeArray: IArray[FunType] = + val arr = Array.ofDim[FunType](8) + val choices = List(false, true) + for contxt <- choices; erasd <- choices; impure <- choices do + var str = "Function" + if contxt then str = "Context" + str + if erasd then str = "Erased" + str + if impure then str = "Impure" + str + arr(funTypeIdx(contxt, erasd, impure)) = FunType(str) + IArray.unsafeFromArray(arr) + + def FunctionSymbol(n: Int, isContextual: Boolean = false, isErased: Boolean = false, isImpure: Boolean = false)(using Context): Symbol = + funTypeArray(funTypeIdx(isContextual, isErased, isImpure))(n).symbol @tu lazy val Function0_apply: Symbol = Function0.requiredMethod(nme.apply) - @tu lazy val Function0: Symbol = FunctionClass(0) - @tu lazy val Function1: Symbol = FunctionClass(1) - @tu lazy val Function2: Symbol = FunctionClass(2) + @tu lazy val Function0: Symbol = FunctionSymbol(0) + @tu lazy val Function1: Symbol = FunctionSymbol(1) + @tu lazy val Function2: Symbol = FunctionSymbol(2) - def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): TypeRef = - FunctionClass(n, isContextual && !ctx.erasedTypes, isErased).typeRef + def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false, isImpure: Boolean = false)(using Context): TypeRef = + FunctionSymbol(n, isContextual && !ctx.erasedTypes, isErased, isImpure).typeRef lazy val PolyFunctionClass = requiredClass("scala.PolyFunction") def PolyFunctionType = PolyFunctionClass.typeRef @@ -1345,6 +1381,10 @@ class Definitions { */ def isFunctionClass(cls: Symbol): Boolean = scalaClassName(cls).isFunction + /** Is a function class, or an impure function type alias */ + def isFunctionSymbol(sym: Symbol): Boolean = + sym.isType && (sym.owner eq ScalaPackageClass) && sym.name.isFunction + /** Is a function class where * - FunctionN for N >= 0 and N != XXL */ @@ -1550,7 +1590,7 @@ class Definitions { new PerRun(Function2SpecializedReturnTypes.map(_.symbol)) def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(using Context): Boolean = - paramTypes.length <= 2 && cls.derivesFrom(FunctionClass(paramTypes.length)) + paramTypes.length <= 2 && cls.derivesFrom(FunctionSymbol(paramTypes.length)) && isSpecializableFunctionSAM(paramTypes, retType) /** If the Single Abstract Method of a Function class has this type, is it specializable? */ diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index cb590e2384a0..f2682621a7bd 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -314,8 +314,8 @@ object Flags { /** A Scala 2x super accessor / an unpickled Scala 2.x class */ val (SuperParamAliasOrScala2x @ _, SuperParamAlias @ _, Scala2x @ _) = newFlags(26, "", "") - /** A parameter with a default value */ - val (_, HasDefault @ _, _) = newFlags(27, "") + /** A parameter with a default value / an impure untpd.Function type */ + val (_, HasDefault @ _, Impure @ _) = newFlags(27, "", "<{*}>") /** An extension method, or a collective extension instance */ val (Extension @ _, ExtensionMethod @ _, _) = newFlags(28, "") diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index fb35ac0ac91f..d7c224d90821 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -197,20 +197,25 @@ object NameOps { else collectDigits(acc * 10 + d, idx + 1) collectDigits(0, suffixStart + 8) - /** name[0..suffixStart) == `str` */ - private def isPreceded(str: String, suffixStart: Int) = - str.length == suffixStart && name.firstPart.startsWith(str) + private def isFunctionPrefix(suffixStart: Int, mustHave: String = ""): Boolean = + suffixStart >= 0 + && { + val first = name.firstPart + var found = mustHave.isEmpty + def skip(idx: Int, str: String) = + if first.startsWith(str, idx) then + if str == mustHave then found = true + idx + str.length + else idx + skip(skip(skip(0, "Impure"), "Erased"), "Context") == suffixStart + && found + } /** Same as `funArity`, except that it returns -1 if the prefix * is not one of "", "Context", "Erased", "ErasedContext" */ private def checkedFunArity(suffixStart: Int): Int = - if suffixStart == 0 - || isPreceded("Context", suffixStart) - || isPreceded("Erased", suffixStart) - || isPreceded("ErasedContext", suffixStart) - then funArity(suffixStart) - else -1 + if isFunctionPrefix(suffixStart) then funArity(suffixStart) else -1 /** Is a function name, i.e one of FunctionXXL, FunctionN, ContextFunctionN, ErasedFunctionN, ErasedContextFunctionN for N >= 0 */ @@ -222,19 +227,14 @@ object NameOps { */ def isPlainFunction: Boolean = functionArity >= 0 - /** Is an context function name, i.e one of ContextFunctionN or ErasedContextFunctionN for N >= 0 - */ - def isContextFunction: Boolean = + /** Is a function name that contains `mustHave` as a substring */ + private def isSpecificFunction(mustHave: String): Boolean = val suffixStart = functionSuffixStart - (isPreceded("Context", suffixStart) || isPreceded("ErasedContext", suffixStart)) - && funArity(suffixStart) >= 0 + isFunctionPrefix(suffixStart, mustHave) && funArity(suffixStart) >= 0 - /** Is an erased function name, i.e. one of ErasedFunctionN, ErasedContextFunctionN for N >= 0 - */ - def isErasedFunction: Boolean = - val suffixStart = functionSuffixStart - (isPreceded("Erased", suffixStart) || isPreceded("ErasedContext", suffixStart)) - && funArity(suffixStart) >= 0 + def isContextFunction: Boolean = isSpecificFunction("Context") + def isErasedFunction: Boolean = isSpecificFunction("Erased") + def isImpureFunction: Boolean = isSpecificFunction("Impure") /** Is a synthetic function name, i.e. one of * - FunctionN for N > 22 diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 9ff15ae53537..a12f6c478c72 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -741,6 +741,8 @@ object StdNames { val XOR : N = "^" val ZAND : N = "&&" val ZOR : N = "||" + val PUREARROW: N = "->" + val PURECTXARROW: N = "?->" // unary operators val UNARY_PREFIX: N = "unary_" diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 5a11c9d2e30f..32f31c95d783 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2413,7 +2413,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case tp1: TypeVar if tp1.isInstantiated => tp1.underlying & tp2 case CapturingType(parent1, refs1, _) => - if subCaptures(tp2.captureSet, refs1, frozenConstraint).isOK then + if subCaptures(tp2.captureSet, refs1, frozen = true).isOK then parent1 & tp2 else tp1.derivedCapturingType(parent1 & tp2, refs1) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b528bd2137ae..746a66e2344d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -183,7 +183,7 @@ object Types { case _ => false } - /** Is this type a (possibly refined or applied or aliased) type reference + /** Is this type a (possibly refined, applied, aliased or annotated) type reference * to the given type symbol? * @sym The symbol to compare to. It must be a class symbol or abstract type. * It makes no sense for it to be an alias type because isRef would always @@ -204,9 +204,7 @@ object Types { case this1: TypeVar => this1.instanceOpt.isRef(sym, skipRefined) case this1: AnnotatedType => - this1 match - case CapturingType(_, _, _) => false - case _ => this1.parent.isRef(sym, skipRefined) + this1.parent.isRef(sym, skipRefined) case _ => false } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index f2e3cb16bc45..875d135c38a8 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1363,47 +1363,56 @@ object Parsers { * | InfixType * | CaptureSet Type * FunType ::= (MonoFunType | PolyFunType) - * MonoFunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type - * PolyFunType ::= HKTypeParamClause '=>' Type + * MonoFunType ::= FunTypeArgs (‘=>’ | ‘?=>’ | ‘->’ | ‘?->’ ) Type + * PolyFunType ::= HKTypeParamClause ('=>' | ‘->’_) Type * FunTypeArgs ::= InfixType * | `(' [ [ ‘[using]’ ‘['erased'] FunArgType {`,' FunArgType } ] `)' * | '(' [ ‘[using]’ ‘['erased'] TypedFunParam {',' TypedFunParam } ')' * CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` * CaptureRef ::= Ident */ - def typ(): Tree = { + def typ(): Tree = val start = in.offset var imods = Modifiers() def functionRest(params: List[Tree]): Tree = val paramSpan = Span(start, in.lastOffset) atSpan(start, in.offset) { - if in.token == TLARROW then + var token = in.token + if in.isIdent(nme.PUREARROW) then + token = ARROW + else if in.isIdent(nme.PURECTXARROW) then + token = CTXARROW + else if token == TLARROW then if !imods.flags.isEmpty || params.isEmpty then syntaxError(em"illegal parameter list for type lambda", start) - in.token = ARROW - else - for case ValDef(_, tpt: ByNameTypeTree, _) <- params do - syntaxError(em"parameter of type lambda may not be call-by-name", tpt.span) - in.nextToken() - return TermLambdaTypeTree(params.asInstanceOf[List[ValDef]], typ()) + token = ARROW + else if ctx.settings.Ycc.value then + // `=>` means impure function under -Ycc whereas `->` is a regular function. + // Without -Ycc they both mean regular function. + imods |= Impure - if in.token == CTXARROW then + if token == CTXARROW then in.nextToken() imods |= Given + else if token == ARROW || token == TLARROW then + in.nextToken() else accept(ARROW) - val t = typ() - if imods.isOneOf(Given | Erased) then + val resultType = typ() + if token == TLARROW then + for case ValDef(_, tpt: ByNameTypeTree, _) <- params do + syntaxError(em"parameter of type lambda may not be call-by-name", tpt.span) + TermLambdaTypeTree(params.asInstanceOf[List[ValDef]], resultType) + else if imods.isOneOf(Given | Erased | Impure) then if imods.is(Given) && params.isEmpty then syntaxError("context function types require at least one parameter", paramSpan) - new FunctionWithMods(params, t, imods) + FunctionWithMods(params, resultType, imods) else if !ctx.settings.YkindProjector.isDefault then - val (newParams :+ newT, tparams) = replaceKindProjectorPlaceholders(params :+ t) - - lambdaAbstract(tparams, Function(newParams, newT)) + val (newParams :+ newResultType, tparams) = replaceKindProjectorPlaceholders(params :+ resultType) + lambdaAbstract(tparams, Function(newParams, newResultType)) else - Function(params, t) + Function(params, resultType) } def funTypeArgsRest(first: Tree, following: () => Tree) = { val buf = new ListBuffer[Tree] += first @@ -1461,7 +1470,7 @@ object Parsers { val tparams = typeParamClause(ParamOwner.TypeParam) if (in.token == TLARROW) atSpan(start, in.skipToken())(LambdaTypeTree(tparams, toplevelTyp())) - else if (in.token == ARROW) { + else if (in.token == ARROW || in.isIdent(nme.PUREARROW)) { val arrowOffset = in.skipToken() val body = toplevelTyp() atSpan(start, arrowOffset) { @@ -1482,16 +1491,18 @@ object Parsers { else if (in.token == INDENT) enclosed(INDENT, typ()) else infixType() - in.token match { + in.token match case ARROW | CTXARROW => functionRest(t :: Nil) case MATCH => matchType(t) case FORSOME => syntaxError(ExistentialTypesNoLongerSupported()); t case _ => - if (imods.is(Erased) && !t.isInstanceOf[FunctionWithMods]) - syntaxError(ErasedTypesCanOnlyBeFunctionTypes(), implicitKwPos(start)) - t - } - } + if isIdent(nme.PUREARROW) || isIdent(nme.PURECTXARROW) then + functionRest(t :: Nil) + else + if (imods.is(Erased) && !t.isInstanceOf[FunctionWithMods]) + syntaxError(ErasedTypesCanOnlyBeFunctionTypes(), implicitKwPos(start)) + t + end typ private def makeKindProjectorTypeDef(name: TypeName): TypeDef = { val isVarianceAnnotated = name.startsWith("+") || name.startsWith("-") @@ -1549,7 +1560,7 @@ object Parsers { def infixTypeRest(t: Tree): Tree = infixOps(t, canStartInfixTypeTokens, refinedTypeFn, Location.ElseWhere, isType = true, - isOperator = !followingIsVararg()) + isOperator = !followingIsVararg() && !isIdent(nme.PUREARROW) && !isIdent(nme.PURECTXARROW)) /** RefinedType ::= WithType {[nl] Refinement} */ diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 054fe62682dd..f2b5047da944 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -15,7 +15,7 @@ import util.SourcePosition import scala.util.control.NonFatal import scala.annotation.switch import config.Config -import cc.{CapturingType, CaptureSet} +import cc.{EventuallyCapturingType, CaptureSet} class PlainPrinter(_ctx: Context) extends Printer { @@ -140,6 +140,9 @@ class PlainPrinter(_ctx: Context) extends Printer { + defn.ObjectClass + defn.FromJavaObjectSymbol + def toText(cs: CaptureSet): Text = + "{" ~ Text(cs.elems.toList.map(toTextCaptureRef), ", ") ~ "}" + def toText(tp: Type): Text = controlled { homogenize(tp) match { case tp: TypeType => @@ -189,7 +192,7 @@ class PlainPrinter(_ctx: Context) extends Printer { keywordStr(" match ") ~ "{" ~ casesText ~ "}" ~ (" <: " ~ toText(bound) provided !bound.isAny) }.close - case CapturingType(parent, refs, boxed) => + case EventuallyCapturingType(parent, refs, boxed) => def box = Str("box ") provided boxed if printDebug && !refs.isConst then changePrec(GlobalPrec)(box ~ s"$refs " ~ toText(parent)) @@ -198,11 +201,7 @@ class PlainPrinter(_ctx: Context) extends Printer { else if !refs.isConst && refs.elems.isEmpty then changePrec(GlobalPrec)("?" ~ " " ~ toText(parent)) else if Config.printCaptureSetsAsPrefix then - changePrec(GlobalPrec)( - box ~ "{" - ~ Text(refs.elems.toList.map(toTextCaptureRef), ", ") - ~ "} " - ~ toText(parent)) + changePrec(GlobalPrec)(box ~ toText(refs) ~ " " ~ toText(parent)) else changePrec(InfixPrec)(toText(parent) ~ " retains " ~ box ~ toText(refs.toRetainsTypeArg)) case tp: PreviousErrorType if ctx.settings.XprintTypes.value => diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index c7606acf3dd6..0424a9b5f767 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -15,7 +15,7 @@ import Annotations.Annotation import Denotations._ import SymDenotations._ import StdNames.{nme, tpnme} -import ast.{Trees, untpd} +import ast.{Trees, tpd, untpd} import typer.{Implicits, Namer, Applications} import typer.ProtoTypes._ import Trees._ @@ -25,10 +25,12 @@ import NameKinds.{WildcardParamName, DefaultGetterName} import util.Chars.isOperatorPart import transform.TypeUtils._ import transform.SymUtils._ +import config.Config import language.implicitConversions import dotty.tools.dotc.util.{NameTransformer, SourcePosition} import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} +import cc.{EventuallyCapturingType, CaptureSet, toCaptureSet, IllegalCaptureRef} class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { @@ -136,14 +138,14 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else simpleNameString(tsym) } - private def arrow(isGiven: Boolean): String = - if isGiven then "?=>" else "=>" + private def arrow(isGiven: Boolean, isPure: Boolean): String = + (if isGiven then "?" else "") + (if isPure then "->" else "=>") override def toText(tp: Type): Text = controlled { def toTextTuple(args: List[Type]): Text = "(" ~ argsText(args) ~ ")" - def toTextFunction(args: List[Type], isGiven: Boolean, isErased: Boolean): Text = + def toTextFunction(args: List[Type], isGiven: Boolean, isErased: Boolean, isPure: Boolean): Text = changePrec(GlobalPrec) { val argStr: Text = if args.length == 2 @@ -156,26 +158,26 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { ~ keywordText("erased ").provided(isErased) ~ argsText(args.init) ~ ")" - argStr ~ " " ~ arrow(isGiven) ~ " " ~ argText(args.last) + argStr ~ " " ~ arrow(isGiven, isPure) ~ " " ~ argText(args.last) } - def toTextMethodAsFunction(info: Type): Text = info match + def toTextMethodAsFunction(info: Type, isPure: Boolean): Text = info match case info: MethodType => changePrec(GlobalPrec) { "(" ~ keywordText("erased ").provided(info.isErasedMethod) ~ paramsText(info) ~ ") " - ~ arrow(info.isImplicitMethod) + ~ arrow(info.isImplicitMethod, isPure) ~ " " - ~ toTextMethodAsFunction(info.resultType) + ~ toTextMethodAsFunction(info.resultType, isPure) } case info: PolyType => changePrec(GlobalPrec) { "[" ~ paramsText(info) ~ "] => " - ~ toTextMethodAsFunction(info.resultType) + ~ toTextMethodAsFunction(info.resultType, isPure) } case _ => toText(info) @@ -214,9 +216,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def appliedText(tp: Type): Text = tp match case tp @ AppliedType(tycon, args) => - val cls = tycon.typeSymbol + val tsym = tycon.typeSymbol if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*" - else if defn.isFunctionClass(cls) then toTextFunction(args, cls.name.isContextFunction, cls.name.isErasedFunction) + else if defn.isFunctionSymbol(tsym) then + toTextFunction(args, tsym.name.isContextFunction, tsym.name.isErasedFunction, + isPure = ctx.settings.Ycc.value && !tsym.name.isImpureFunction) else if tp.tupleArity >= 2 && !printDebug then toTextTuple(tp.tupleElementTypes) else if isInfixType(tp) then val l :: r :: Nil = args @@ -243,7 +247,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { // don't eta contract if the application would be printed specially toText(tycon) case tp: RefinedType if defn.isFunctionOrPolyType(tp) && !printDebug => - toTextMethodAsFunction(tp.refinedInfo) + toTextMethodAsFunction(tp.refinedInfo, + isPure = ctx.settings.Ycc.value && !tp.typeSymbol.name.isImpureFunction) case tp: TypeRef => if (tp.symbol.isAnonymousClass && !showUniqueIds) toText(tp.info) @@ -259,7 +264,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case tp: ClassInfo => if tp.cls.derivesFrom(defn.PolyFunctionClass) then tp.member(nme.apply).info match - case info: PolyType => return toTextMethodAsFunction(info) + case info: PolyType => return toTextMethodAsFunction(info, isPure = false) case _ => toTextParents(tp.parents) ~~ "{...}" case JavaArrayType(elemtp) => @@ -527,7 +532,7 @@ 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) + else if defn.isFunctionSymbol(tpt.symbol) && tpt.isInstanceOf[TypeTree] && tree.hasType && !printDebug then changePrec(GlobalPrec) { toText(tree.typeOpt) } else args match @@ -602,7 +607,17 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case tree: Template => toTextTemplate(tree) case Annotated(arg, annot) => - toTextLocal(arg) ~~ annotText(annot.symbol.enclosingClass, annot) + def captureSet = + annot.asInstanceOf[tpd.Tree].toCaptureSet + def toTextAnnot = + toTextLocal(arg) ~~ annotText(annot.symbol.enclosingClass, annot) + def toTextRetainsAnnot = + try changePrec(GlobalPrec)(toText(captureSet) ~ " " ~ toText(arg)) + catch case ex: IllegalCaptureRef => toTextAnnot + if annot.symbol.maybeOwner == defn.RetainsAnnot + && ctx.settings.Ycc.value && Config.printCaptureSetsAsPrefix && !printDebug + then toTextRetainsAnnot + else toTextAnnot case EmptyTree => "" case TypedSplice(t) => @@ -645,7 +660,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { ~ Text(args.map(argToText), ", ") ~ ")" } - argsText ~ " " ~ arrow(isGiven) ~ " " ~ toText(body) + val isPure = + ctx.settings.Ycc.value + && tree.match + case tree: FunctionWithMods => !tree.mods.is(Impure) + case _ => true + argsText ~ " " ~ arrow(isGiven, isPure) ~ " " ~ toText(body) case PolyFunction(targs, body) => val targsText = "[" ~ Text(targs.map((arg: Tree) => toText(arg)), ", ") ~ "]" changePrec(GlobalPrec) { diff --git a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala index 1f30dd989f3a..e78776cb0158 100644 --- a/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala @@ -117,82 +117,150 @@ class CheckCaptures extends Recheck: override def transformType(tp: Type, inferred: Boolean, boxed: Boolean)(using Context): Type = - def addInnerVars(tp: Type): Type = tp match - case tp @ AppliedType(tycon, args) => - tp.derivedAppliedType(tycon, args.map(addVars(_, boxed = true))) - case tp @ RefinedType(core, rname, rinfo) => - val rinfo1 = addVars(rinfo) - if defn.isFunctionType(tp) then - rinfo1.toFunctionType(isJava = false, alwaysDependent = true) - else - tp.derivedRefinedType(addInnerVars(core), rname, rinfo1) - case tp: MethodType => - tp.derivedLambdaType( - paramInfos = tp.paramInfos.mapConserve(addVars(_)), - resType = addVars(tp.resType)) - case tp: PolyType => - tp.derivedLambdaType( - resType = addVars(tp.resType)) - case tp: ExprType => - tp.derivedExprType(resType = addVars(tp.resType)) - case _ => - tp - - def addFunctionRefinements(tp: Type): Type = tp match - case tp @ AppliedType(tycon, args) => - if defn.isNonRefinedFunction(tp) then - MethodType.companion( - isContextual = defn.isContextFunctionClass(tycon.classSymbol), - isErased = defn.isErasedFunctionClass(tycon.classSymbol) - )(args.init, addFunctionRefinements(args.last)) - .toFunctionType(isJava = false, alwaysDependent = true) - .showing(i"add function refinement $tp --> $result", capt) - else - tp.derivedAppliedType(tycon, args.map(addFunctionRefinements(_))) - case tp @ RefinedType(core, rname, rinfo) if !defn.isFunctionType(tp) => - tp.derivedRefinedType( - addFunctionRefinements(core), rname, addFunctionRefinements(rinfo)) - case tp: MethodOrPoly => - tp.derivedLambdaType(resType = addFunctionRefinements(tp.resType)) - case tp: ExprType => - tp.derivedExprType(resType = addFunctionRefinements(tp.resType)) - case _ => - tp - - /** Refine a possibly applied class type C where the class has tracked parameters - * x_1: T_1, ..., x_n: T_n to C { val x_1: CV_1 T_1, ..., val x_n: CV_n T_n } - * where CV_1, ..., CV_n are fresh capture sets. + def depFun(tycon: Type, argTypes: List[Type], resType: Type): Type = + MethodType.companion( + isContextual = defn.isContextFunctionClass(tycon.classSymbol), + isErased = defn.isErasedFunctionClass(tycon.classSymbol) + )(argTypes, resType) + .toFunctionType(isJava = false, alwaysDependent = true) + + def box(tp: Type): Type = tp match + case CapturingType(parent, refs, false) => CapturingType(parent, refs, true) + case _ => tp + + /** Perform the following transformation steps everywhere in a type: + * 1. Drop retains annotations + * 2. Turn plain function types into dependent function types, so that + * we can refer to their parameter in capture sets. Currently this is + * only done at the toplevel, i.e. for function types that are not + * themselves argument types of other function types. Without this restriction + * boxmap-paper.scala fails. Need to figure out why. + * 3. Refine other class types C by adding capture set variables to their parameter getters + * (see addCaptureRefinements) + * 4. Add capture set variables to all types that can be tracked + * + * Polytype bounds are only cleaned using step 1, but not otherwise transformed. */ - def addCaptureRefinements(tp: Type): Type = tp.stripped match - case _: TypeRef | _: AppliedType if tp.typeSymbol.isClass => - val cls = tp.typeSymbol.asClass - cls.paramGetters.foldLeft(tp) { (core, getter) => - if getter.termRef.isTracked then - val getterType = tp.memberInfo(getter).strippedDealias - RefinedType(core, getter.name, CapturingType(getterType, CaptureSet.Var(), boxed = false)) - .showing(i"add capture refinement $tp --> $result", capt) - else - core - } - case _ => - tp + def mapInferred = new TypeMap: - def addVars(tp: Type, boxed: Boolean = false): Type = - var tp1 = addInnerVars(tp) - val tp2 = addCaptureRefinements(tp1) - if tp1.canHaveInferredCapture - then CapturingType(tp2, CaptureSet.Var(), boxed) - else tp2 - - if inferred then - val cleanup = new TypeMap: + /** Drop @retains annotations everywhere */ + object cleanup extends TypeMap: def apply(t: Type) = t match case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot => apply(parent) case _ => mapOver(t) - addVars(addFunctionRefinements(cleanup(tp)), boxed) - .showing(i"reinfer $tp --> $result", capt) + + /** Refine a possibly applied class type C where the class has tracked parameters + * x_1: T_1, ..., x_n: T_n to C { val x_1: CV_1 T_1, ..., val x_n: CV_n T_n } + * where CV_1, ..., CV_n are fresh capture sets. + */ + def addCaptureRefinements(tp: Type): Type = tp match + case _: TypeRef | _: AppliedType if tp.typeParams.isEmpty => + tp.typeSymbol match + case cls: ClassSymbol if !defn.isFunctionClass(cls) => + cls.paramGetters.foldLeft(tp) { (core, getter) => + if getter.termRef.isTracked then + val getterType = tp.memberInfo(getter).strippedDealias + RefinedType(core, getter.name, CapturingType(getterType, CaptureSet.Var(), boxed = false)) + .showing(i"add capture refinement $tp --> $result", capt) + else + core + } + case _ => tp + case _ => tp + + /** Should a capture set variable be added on type `tp`? */ + def canHaveInferredCapture(tp: Type): Boolean = + tp.typeParams.isEmpty && tp.match + case tp: (TypeRef | AppliedType) => + val sym = tp.typeSymbol + if sym.isClass then !sym.isValueClass && sym != defn.AnyClass + else canHaveInferredCapture(tp.superType.dealias) + case tp: (RefinedOrRecType | MatchType) => + canHaveInferredCapture(tp.underlying) + case tp: AndType => + canHaveInferredCapture(tp.tp1) && canHaveInferredCapture(tp.tp2) + case tp: OrType => + canHaveInferredCapture(tp.tp1) || canHaveInferredCapture(tp.tp2) + case _ => + false + + /** Add a capture set variable to `tp` if necessary, or maybe pull out + * an embedded capture set variables from a part of `tp`. + */ + def addVar(tp: Type) = tp match + case tp @ RefinedType(parent @ CapturingType(parent1, refs, boxed), rname, rinfo) => + CapturingType(tp.derivedRefinedType(parent1, rname, rinfo), refs, boxed) + case tp: RecType => + tp.parent match + case CapturingType(parent1, refs, boxed) => + CapturingType(tp.derivedRecType(parent1), refs, boxed) + case _ => + tp // can return `tp` here since unlike RefinedTypes, RecTypes are never created + // by `mapInferred`. Hence if the underlying type admits capture variables + // a variable was already added, and the first case above would apply. + case AndType(CapturingType(parent1, refs1, boxed1), CapturingType(parent2, refs2, boxed2)) => + assert(refs1.asVar.elems.isEmpty) + assert(refs2.asVar.elems.isEmpty) + assert(boxed1 == boxed2) + CapturingType(AndType(parent1, parent2), refs1, boxed1) + case tp @ OrType(CapturingType(parent1, refs1, boxed1), CapturingType(parent2, refs2, boxed2)) => + assert(refs1.asVar.elems.isEmpty) + assert(refs2.asVar.elems.isEmpty) + assert(boxed1 == boxed2) + CapturingType(OrType(parent1, parent2, tp.isSoft), refs1, boxed1) + case tp @ OrType(CapturingType(parent1, refs1, boxed1), tp2) => + CapturingType(OrType(parent1, tp2, tp.isSoft), refs1, boxed1) + case tp @ OrType(tp1, CapturingType(parent2, refs2, boxed2)) => + CapturingType(OrType(tp1, parent2, tp.isSoft), refs2, boxed2) + case _ if canHaveInferredCapture(tp) => + CapturingType(tp, CaptureSet.Var(), boxed = false) + case _ => + tp + + var isTopLevel = true + + def mapNested(ts: List[Type]): List[Type] = + val saved = isTopLevel + isTopLevel = false + try ts.mapConserve(this) finally isTopLevel = saved + + def apply(t: Type) = + val t1 = t match + case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot => + apply(parent) + case tp @ AppliedType(tycon, args) => + val tycon1 = this(tycon) + if defn.isNonRefinedFunction(tp) then + val args1 = mapNested(args.init) + val res1 = this(args.last) + if isTopLevel then + depFun(tycon1, args1, res1) + .showing(i"add function refinement $tp --> $result", capt) + else + tp.derivedAppliedType(tycon1, args1 :+ res1) + else + tp.derivedAppliedType(tycon1, args.mapConserve(arg => box(this(arg)))) + case tp @ RefinedType(core, rname, rinfo) if defn.isFunctionType(tp) => + apply(rinfo).toFunctionType(isJava = false, alwaysDependent = true) + case tp: MethodType => + tp.derivedLambdaType( + paramInfos = mapNested(tp.paramInfos), + resType = this(tp.resType)) + case tp: TypeLambda => + // Don't recurse into parameter bounds, just cleanup any stray retains annotations + tp.derivedLambdaType( + paramInfos = tp.paramInfos.mapConserve(cleanup(_).bounds), + resType = this(tp.resType)) + case _ => + mapOver(t) + addVar(addCaptureRefinements(t1)) + end mapInferred + + if inferred then + val tp1 = mapInferred(tp) + if boxed then box(tp1) else tp1 else def setBoxed(t: Type) = t match case AnnotatedType(_, annot) if annot.symbol == defn.RetainsAnnot => diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index fecdf3eb6cef..0fc0f852d471 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -815,7 +815,7 @@ trait Implicits: def isOldStyleFunctionConversion(tpe: Type): Boolean = tpe match { case PolyType(_, resType) => isOldStyleFunctionConversion(resType) - case _ => tpe.derivesFrom(defn.FunctionClass(1)) && !tpe.derivesFrom(defn.ConversionClass) && !tpe.derivesFrom(defn.SubTypeClass) + case _ => tpe.derivesFrom(defn.FunctionSymbol(1)) && !tpe.derivesFrom(defn.ConversionClass) && !tpe.derivesFrom(defn.SubTypeClass) } try diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 86db24e14c4c..42889b23bb04 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1238,7 +1238,8 @@ class Typer extends Namer val numArgs = args.length val isContextual = funFlags.is(Given) val isErased = funFlags.is(Erased) - val funCls = defn.FunctionClass(numArgs, isContextual, isErased) + val isImpure = funFlags.is(Impure) + val funSym = defn.FunctionSymbol(numArgs, isContextual, isErased, isImpure) /** If `app` is a function type with arguments that are all erased classes, * turn it into an erased function type. @@ -1248,7 +1249,7 @@ class Typer extends Namer if !isErased && numArgs > 0 && args.indexWhere(!_.tpe.isErasedClass) == numArgs => - val tycon1 = TypeTree(defn.FunctionClass(numArgs, isContextual, isErased = true).typeRef) + val tycon1 = TypeTree(defn.FunctionSymbol(numArgs, isContextual, true, isImpure).typeRef) .withSpan(tycon.span) assignType(cpy.AppliedTypeTree(app)(tycon1, args), tycon1, args) case _ => @@ -1275,7 +1276,7 @@ class Typer extends Namer report.error(i"$mt is an illegal function type because it has inter-parameter dependencies", tree.srcPos) val resTpt = TypeTree(mt.nonDependentResultApprox).withSpan(body.span) val typeArgs = appDef.termParamss.head.map(_.tpt) :+ resTpt - val tycon = TypeTree(funCls.typeRef) + val tycon = TypeTree(funSym.typeRef) val core = propagateErased(AppliedTypeTree(tycon, typeArgs)) RefinedTypeTree(core, List(appDef), ctx.owner.asClass) end typedDependent @@ -1286,7 +1287,7 @@ class Typer extends Namer using ctx.fresh.setOwner(newRefinedClassSymbol(tree.span)).setNewScope) case _ => propagateErased( - typed(cpy.AppliedTypeTree(tree)(untpd.TypeTree(funCls.typeRef), args :+ body), pt)) + typed(cpy.AppliedTypeTree(tree)(untpd.TypeTree(funSym.typeRef), args :+ body), pt)) } } diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index d6e13fbbd168..a1c2367fba9b 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2692,7 +2692,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def SomeModule: Symbol = dotc.core.Symbols.defn.SomeClass.companionModule def ProductClass: Symbol = dotc.core.Symbols.defn.ProductClass def FunctionClass(arity: Int, isImplicit: Boolean = false, isErased: Boolean = false): Symbol = - dotc.core.Symbols.defn.FunctionClass(arity, isImplicit, isErased) + dotc.core.Symbols.defn.FunctionSymbol(arity, isImplicit, isErased) def TupleClass(arity: Int): Symbol = dotc.core.Symbols.defn.TupleType(arity).classSymbol.asClass def isTupleClass(sym: Symbol): Boolean = diff --git a/tests/neg-custom-args/capt-wf.scala b/tests/neg-custom-args/capt-wf.scala index dc4d6a0d4bff..3bd80e0d0f68 100644 --- a/tests/neg-custom-args/capt-wf.scala +++ b/tests/neg-custom-args/capt-wf.scala @@ -8,7 +8,7 @@ def test(c: Cap, other: String): Unit = val x2: {other} C = ??? // error: cs is empty val s1 = () => "abc" val x3: {s1} C = ??? // error: cs is empty - val x3a: () => String = s1 + val x3a: () -> String = s1 val s2 = () => if x1 == null then "" else "abc" val x4: {s2} C = ??? // OK val x5: {c, c} C = ??? // error: redundant @@ -26,8 +26,8 @@ def test(c: Cap, other: String): Unit = val y1: {e1} String = ??? // error cs is empty val y2: {o1} String = ??? // error cs is empty - lazy val ev: (Int => Boolean) = (n: Int) => - lazy val od: (Int => Boolean) = (n: Int) => + lazy val ev: (Int -> Boolean) = (n: Int) => + lazy val od: (Int -> Boolean) = (n: Int) => if n == 1 then true else ev(n - 1) if n == 0 then true else od(n - 1) val y3: {ev} String = ??? // error cs is empty diff --git a/tests/neg-custom-args/captures/bounded.scala b/tests/neg-custom-args/captures/bounded.scala index dc2621e95a65..fb6b198fb3a3 100644 --- a/tests/neg-custom-args/captures/bounded.scala +++ b/tests/neg-custom-args/captures/bounded.scala @@ -9,6 +9,6 @@ def test(c: Cap) = def f(x: Int): Int = if c == c then x else 0 val b = new B(f) val r1 = b.elem - val r1c: {c} Int => Int = r1 + val r1c: {c} Int -> Int = r1 val r2 = b.lateElem - val r2c: () => {c} Int => Int = r2 // error \ No newline at end of file + val r2c: () -> {c} Int -> Int = r2 // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/boxmap.check b/tests/neg-custom-args/captures/boxmap.check index 406077077af5..b3d6605989bf 100644 --- a/tests/neg-custom-args/captures/boxmap.check +++ b/tests/neg-custom-args/captures/boxmap.check @@ -1,7 +1,7 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/boxmap.scala:14:2 ---------------------------------------- -14 | () => b[Box[B]]((x: A) => box(f(x))) // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/boxmap.scala:12:2 ---------------------------------------- +12 | () => b[Box[B]]((x: A) => box(f(x))) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: {f} () => ? Box[B] - | Required: () => Box[B] + | Found: {f} () -> ? Box[B] + | Required: () -> Box[B] longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/boxmap.scala b/tests/neg-custom-args/captures/boxmap.scala index e335320ef9d4..114aaccb6bb5 100644 --- a/tests/neg-custom-args/captures/boxmap.scala +++ b/tests/neg-custom-args/captures/boxmap.scala @@ -1,14 +1,12 @@ type Top = Any @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) def box[T <: Top](x: T): Box[T] = - [K <: Top] => (k: T ==> K) => k(x) + [K <: Top] => (k: T => K) => k(x) -def map[A <: Top, B <: Top](b: Box[A])(f: A ==> B): Box[B] = +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] = +def lazymap[A <: Top, B <: Top](b: Box[A])(f: A => B): () -> Box[B] = () => b[Box[B]]((x: A) => box(f(x))) // error diff --git a/tests/neg-custom-args/captures/byname.scala b/tests/neg-custom-args/captures/byname.scala index ef5876be2c11..feb9461dc4c7 100644 --- a/tests/neg-custom-args/captures/byname.scala +++ b/tests/neg-custom-args/captures/byname.scala @@ -3,7 +3,7 @@ def test(cap1: Cap, cap2: Cap) = def f() = if cap1 == cap1 then g else g def g(x: Int) = if cap2 == cap2 then 1 else x - def h(ff: => {cap2} Int => Int) = ff + def h(ff: => {cap2} Int -> Int) = ff h(f()) // error diff --git a/tests/neg-custom-args/captures/capt-box-env.scala b/tests/neg-custom-args/captures/capt-box-env.scala index e9743054076e..605b446d5262 100644 --- a/tests/neg-custom-args/captures/capt-box-env.scala +++ b/tests/neg-custom-args/captures/capt-box-env.scala @@ -1,5 +1,4 @@ -class C -type Cap = {*} C +@annotation.capability class Cap class Pair[+A, +B](x: A, y: B): def fst: A = x @@ -9,4 +8,4 @@ def test(c: Cap) = def f(x: Cap): Unit = if c == x then () val p = Pair(f, f) val g = () => p.fst == p.snd - val gc: () => Boolean = g // error + val gc: () -> Boolean = g // error diff --git a/tests/neg-custom-args/captures/capt-box.scala b/tests/neg-custom-args/captures/capt-box.scala index 317fc064ec0b..634470704fc5 100644 --- a/tests/neg-custom-args/captures/capt-box.scala +++ b/tests/neg-custom-args/captures/capt-box.scala @@ -1,6 +1,4 @@ -//import scala.retains -class C -type Cap = {*} C +@annotation.capability class Cap def test(x: Cap) = @@ -10,4 +8,4 @@ def test(x: Cap) = val x2 = identity(x1) - val x3: Cap => Unit = x2 // error \ No newline at end of file + val x3: Cap -> Unit = x2 // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capt-env.scala b/tests/neg-custom-args/captures/capt-env.scala index 84b4b57a7930..52fa4abfdaa8 100644 --- a/tests/neg-custom-args/captures/capt-env.scala +++ b/tests/neg-custom-args/captures/capt-env.scala @@ -9,5 +9,5 @@ def test(c: Cap) = def f(x: Cap): Unit = if c == x then () val p = Pair(f, f) val g = () => p.fst == p.snd - val gc: () => Boolean = g // error + val gc: () -> Boolean = g // error diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index ce7c4833bf9c..0b99f1bac09e 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -1,21 +1,21 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:3:2 ------------------------------------------ 3 | () => if x == null then y else y // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: {x} () => ? C - | Required: () => C + | 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: {x} () => ? C + | Found: {x} () -> ? C | Required: Matchable longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:13:2 ----------------------------------------- 13 | def f(y: Int) = if x == null then y else y // error | ^ - | Found: {x} Int => Int + | Found: {x} Int -> Int | Required: Matchable 14 | f @@ -38,9 +38,9 @@ longer explanation available when compiling with `-explain` longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:31:24 ---------------------------------------- -31 | val z2 = h[() => Cap](() => x)(() => C()) // error +31 | val z2 = h[() -> Cap](() => x)(() => C()) // error | ^^^^^^^ - | Found: {x} () => ? Cap - | Required: () => Cap + | Found: {x} () -> Cap + | 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 4da49c5f4f1e..e230defda170 100644 --- a/tests/neg-custom-args/captures/capt1.scala +++ b/tests/neg-custom-args/captures/capt1.scala @@ -1,5 +1,5 @@ class C -def f(x: C @retains(*), y: C): () => C = +def f(x: C @retains(*), y: C): () -> C = () => if x == null then y else y // error def g(x: C @retains(*), y: C): Matchable = @@ -28,7 +28,7 @@ def h4(x: Cap, y: Int): A = def foo() = val x: C @retains(*) = ??? def h[X](a: X)(b: X) = a - val z2 = h[() => Cap](() => x)(() => C()) // error - val z3 = h[(() => Cap) @retains(x)](() => x)(() => C()) // ok - val z4 = h[(() => Cap) @retains(x)](() => x)(() => C()) // what was inferred for z3 + val z2 = h[() -> Cap](() => x)(() => C()) // error + val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // ok + val z4 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // what was inferred for z3 diff --git a/tests/neg-custom-args/captures/capt2.scala b/tests/neg-custom-args/captures/capt2.scala index 1eee53463f6d..8b08832dfdb9 100644 --- a/tests/neg-custom-args/captures/capt2.scala +++ b/tests/neg-custom-args/captures/capt2.scala @@ -2,8 +2,8 @@ class C type Cap = {*} C -def f1(c: Cap): (() => {c} C) = () => c // error, but would be OK under capture abbreciations for funciton types -def f2(c: Cap): ({c} () => C) = () => c // error +def f1(c: Cap): (() -> {c} C) = () => c // error, but would be OK under capture abbreciations for funciton types +def f2(c: Cap): ({c} () -> C) = () => c // error -def h5(x: Cap): () => C = +def h5(x: Cap): () -> C = f1(x) // error diff --git a/tests/neg-custom-args/captures/capt3.scala b/tests/neg-custom-args/captures/capt3.scala index 80b937276f73..6e9ea02fe8e3 100644 --- a/tests/neg-custom-args/captures/capt3.scala +++ b/tests/neg-custom-args/captures/capt3.scala @@ -5,22 +5,22 @@ def test1() = val x: Cap = C() val y = () => { x; () } val z = y - z: (() => Unit) // error + z: (() -> Unit) // error def test2() = val x: Cap = C() def y = () => { x; () } def z = y - z: (() => Unit) // error + z: (() -> Unit) // error def test3() = val x: Cap = C() def y = () => { x; () } val z = y - z: (() => Unit) // error + z: (() -> Unit) // error def test4() = val x: Cap = C() val y = () => { x; () } def z = y - z: (() => Unit) // error + z: (() -> Unit) // error diff --git a/tests/neg-custom-args/captures/io.scala b/tests/neg-custom-args/captures/io.scala index 17c22a2111e4..c0cb11686b32 100644 --- a/tests/neg-custom-args/captures/io.scala +++ b/tests/neg-custom-args/captures/io.scala @@ -4,13 +4,13 @@ sealed trait IO: def test1 = val IO : IO @retains(*) = new IO {} def foo = {IO; IO.puts("hello") } - val x : () => Unit = () => foo // error: Found: (() => Unit) retains IO; Required: () => Unit + 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 + val x : () -> Unit = () => foo() // error: Found: (() -> Unit) retains IO; Required: () -> Unit type Capability[T] = T @retains(*) @@ -18,5 +18,5 @@ 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 + val x : () -> Unit = () => foo() // error: Found: (() -> Unit) retains IO; Required: () -> Unit diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index 3a80de9bdf16..0de190df8f11 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -8,7 +8,7 @@ longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:35:29 ------------------------------------- 35 | val ref1c: LazyList[Int] = ref1 // error | ^^^^ - | Found: (ref1 : {cap1} lazylists.LazyCons[Int]{xs: {cap1} () => {*} lazylists.LazyList[Int]}) + | Found: (ref1 : {cap1} lazylists.LazyCons[Int]{xs: {cap1} () -> {*} lazylists.LazyList[Int]}) | Required: lazylists.LazyList[Int] longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/lazylist.scala b/tests/neg-custom-args/captures/lazylist.scala index f7be43e8dc27..56bfc3ea6da2 100644 --- a/tests/neg-custom-args/captures/lazylist.scala +++ b/tests/neg-custom-args/captures/lazylist.scala @@ -7,11 +7,11 @@ abstract class LazyList[+T]: def head: T def tail: LazyList[T] - def map[U](f: {*} T => U): {f, this} LazyList[U] = + def map[U](f: T => U): {f, this} LazyList[U] = if isEmpty then LazyNil else LazyCons(f(head), () => tail.map(f)) -class LazyCons[+T](val x: T, val xs: {*} () => {*} LazyList[T]) extends LazyList[T]: +class LazyCons[+T](val x: T, val xs: () => {*} LazyList[T]) extends LazyList[T]: def isEmpty = false def head = x def tail = xs() // error: cannot have an inferred type @@ -21,7 +21,7 @@ object LazyNil extends LazyList[Nothing]: def head = ??? def tail: {*} LazyList[Nothing] = ??? // error overriding -def map[A, B](xs: {*} LazyList[A], f: {*} A => B): {f, xs} LazyList[B] = +def map[A, B](xs: {*} LazyList[A], f: A => B): {f, xs} LazyList[B] = xs.map(f) class CC diff --git a/tests/neg-custom-args/captures/lazylists1.scala b/tests/neg-custom-args/captures/lazylists1.scala index 02c7cb4ff3e5..4091ee2c62ae 100644 --- a/tests/neg-custom-args/captures/lazylists1.scala +++ b/tests/neg-custom-args/captures/lazylists1.scala @@ -14,7 +14,7 @@ object LazyNil extends LazyList[Nothing]: def tail = ??? extension [A](xs: {*} LazyList[A]) - def map[B](f: {*} A => B): {xs, f} LazyList[B] = + def map[B](f: A => B): {xs, f} LazyList[B] = final class Mapped extends LazyList[B]: this: ({xs, f} Mapped) => diff --git a/tests/neg-custom-args/captures/lazylists2.scala b/tests/neg-custom-args/captures/lazylists2.scala index c31a1ae5d04f..b9ebb0a7a9f0 100644 --- a/tests/neg-custom-args/captures/lazylists2.scala +++ b/tests/neg-custom-args/captures/lazylists2.scala @@ -14,7 +14,7 @@ object LazyNil extends LazyList[Nothing]: def tail = ??? extension [A](xs: {*} LazyList[A]) - def map[B](f: {*} A => B): {f} LazyList[B] = + def map[B](f: A => B): {f} LazyList[B] = final class Mapped extends LazyList[B]: // error this: ({xs, f} Mapped) => @@ -23,7 +23,7 @@ extension [A](xs: {*} LazyList[A]) def tail: {this} LazyList[B] = xs.tail.map(f) new Mapped - def map2[B](f: {*} A => B): {xs} LazyList[B] = + def map2[B](f: A => B): {xs} LazyList[B] = final class Mapped extends LazyList[B]: // error this: ({xs, f} Mapped) => @@ -32,7 +32,7 @@ extension [A](xs: {*} LazyList[A]) def tail: {this} LazyList[B] = xs.tail.map(f) new Mapped - def map3[B](f: {*} A => B): {xs} LazyList[B] = + def map3[B](f: A => B): {xs} LazyList[B] = final class Mapped extends LazyList[B]: this: ({xs} Mapped) => @@ -41,7 +41,7 @@ extension [A](xs: {*} LazyList[A]) def tail: {this} LazyList[B] = xs.tail.map(f) // error new Mapped - def map4[B](f: {*} A => B): {xs} LazyList[B] = + def map4[B](f: A => B): {xs} LazyList[B] = final class Mapped extends LazyList[B]: this: ({xs, f} Mapped) => @@ -50,7 +50,7 @@ extension [A](xs: {*} LazyList[A]) def tail: {xs, f} LazyList[B] = xs.tail.map(f) // error new Mapped - def map5[B](f: {*} A => B): LazyList[B] = + def map5[B](f: A => B): LazyList[B] = class Mapped extends LazyList[B]: this: ({xs, f} Mapped) => diff --git a/tests/neg-custom-args/captures/lazyref.check b/tests/neg-custom-args/captures/lazyref.check index 2affed020dec..e4e06f8c52cb 100644 --- a/tests/neg-custom-args/captures/lazyref.check +++ b/tests/neg-custom-args/captures/lazyref.check @@ -1,28 +1,28 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:19:28 -------------------------------------- 19 | val ref1c: LazyRef[Int] = ref1 // error | ^^^^ - | Found: (ref1 : {cap1} LazyRef[Int]{elem: {cap1} () => Int}) + | Found: (ref1 : {cap1} LazyRef[Int]{elem: {cap1} () -> Int}) | Required: LazyRef[Int] longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:21:35 -------------------------------------- 21 | val ref2c: {cap2} LazyRef[Int] = ref2 // error | ^^^^ - | Found: (ref2 : {cap2, ref1} LazyRef[Int]{elem: {*} () => Int}) + | Found: (ref2 : {cap2, ref1} LazyRef[Int]{elem: {*} () -> Int}) | Required: {cap2} LazyRef[Int] longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:23:35 -------------------------------------- 23 | val ref3c: {ref1} LazyRef[Int] = ref3 // error | ^^^^ - | Found: (ref3 : {cap2, ref1} LazyRef[Int]{elem: {*} () => Int}) + | Found: (ref3 : {cap2, ref1} LazyRef[Int]{elem: {*} () -> Int}) | Required: {ref1} LazyRef[Int] longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:25:35 -------------------------------------- 25 | val ref4c: {cap1} LazyRef[Int] = ref4 // error | ^^^^ - | Found: (ref4 : {cap2, cap1} LazyRef[Int]{elem: {*} () => Int}) + | Found: (ref4 : {cap2, cap1} LazyRef[Int]{elem: {*} () -> Int}) | Required: {cap1} LazyRef[Int] longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/lazyref.scala b/tests/neg-custom-args/captures/lazyref.scala index 1002f9685675..2b278fd51a43 100644 --- a/tests/neg-custom-args/captures/lazyref.scala +++ b/tests/neg-custom-args/captures/lazyref.scala @@ -1,15 +1,15 @@ class CC type Cap = {*} CC -class LazyRef[T](val elem: {*} () => T): +class LazyRef[T](val elem: () => T): val get = elem - def map[U](f: {*} T => U): {f, this} LazyRef[U] = + def map[U](f: T => U): {f, this} LazyRef[U] = new LazyRef(() => f(elem())) -def map[A, B](ref: {*} LazyRef[A], f: {*} A => B): {f, ref} LazyRef[B] = +def map[A, B](ref: {*} LazyRef[A], f: A => B): {f, ref} LazyRef[B] = new LazyRef(() => f(ref.elem())) -def mapc[A, B]: (ref: {*} LazyRef[A], f: {*} A => B) => {f, ref} LazyRef[B] = +def mapc[A, B]: (ref: {*} LazyRef[A], f: A => B) -> {f, ref} LazyRef[B] = (ref1, f1) => map[A, B](ref1, f1) def test(cap1: Cap, cap2: Cap) = diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index bd95835c6525..a2fe96016b80 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,8 +1,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:28:43 ------------------------------------------ -28 | val b = handle[Exception, () => Nothing] { // error +28 | val b = handle[Exception, () -> Nothing] { // error | ^ - | Found: ? (x: CanThrow[Exception]) => {x} () => ? Nothing - | Required: CanThrow[Exception] => () => Nothing + | Found: ? (x: CanThrow[Exception]) -> {x} () -> ? Nothing + | Required: CanThrow[Exception] => () -> Nothing 29 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) 30 | } { @@ -14,12 +14,12 @@ 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 is not allowed to capture the universal capability (* : Any) + | inferred type argument {*} () -> Int is not allowed to capture the universal capability (* : Any) | - | The inferred arguments are: [? Exception, {*} () => Int] + | The inferred arguments are: [? Exception, {*} () -> Int] -- Error: tests/neg-custom-args/captures/try.scala:46:13 --------------------------------------------------------------- 46 |val global = handle { // error | ^^^^^^ - | inferred type argument {*} () => Int is not allowed to capture the universal capability (* : Any) + | inferred type argument {*} () -> Int is not allowed to capture the universal capability (* : Any) | - | The inferred arguments are: [? Exception, {*} () => Int] + | The inferred arguments are: [? Exception, {*} () -> Int] diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index 804a16192be0..b128f82a2a3c 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -25,7 +25,7 @@ def test = (ex: Exception) => ??? } - val b = handle[Exception, () => Nothing] { // error + val b = handle[Exception, () -> Nothing] { // error (x: CanThrow[Exception]) => () => raise(new Exception)(using x) } { (ex: Exception) => ??? diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index 4eab5b6b2b3a..0df38b918862 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -1,21 +1,21 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:11:24 ----------------------------------------- -11 | val z2c: () => Unit = z2 // error +11 | val z2c: () -> Unit = z2 // error | ^^ - | Found: (z2 : {x, cap1} () => Unit) - | Required: () => Unit + | Found: (z2 : {x, cap1} () -> Unit) + | Required: () -> Unit longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/vars.scala:13:10 -------------------------------------------------------------- -13 | var a: {*} String => String = f // error - | ^^^^^^^^^^^^^^^^^^^ - | type of mutable variable box {*} String => String is not allowed to capture the universal capability (* : Any) +-- Error: tests/neg-custom-args/captures/vars.scala:13:16 -------------------------------------------------------------- +13 | var a: String => String = f // error + | ^^^^^^^^^^^^^^^^ + | type of mutable variable String => String is not allowed to capture the universal capability (* : Any) -- Error: tests/neg-custom-args/captures/vars.scala:14:9 --------------------------------------------------------------- -14 | var b: List[{*} String => String] = Nil // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - |type of mutable variable List[box {*} String => String] is not allowed to capture the universal capability (* : Any) +14 | var b: List[String => String] = Nil // error + | ^^^^^^^^^^^^^^^^^^^^^^ + | type of mutable variable List[String => String] is not allowed to capture the universal capability (* : Any) -- Error: tests/neg-custom-args/captures/vars.scala:29:2 --------------------------------------------------------------- 29 | local { cap3 => // error | ^^^^^ - |inferred type argument {*} (x$0: ? String) => ? String is not allowed to capture the universal capability (* : Any) + |inferred type argument {*} (x$0: ? String) -> ? String is not allowed to capture the universal capability (* : Any) | - |The inferred arguments are: [{*} (x$0: ? String) => ? String] + |The inferred arguments are: [{*} (x$0: ? String) -> ? String] diff --git a/tests/neg-custom-args/captures/vars.scala b/tests/neg-custom-args/captures/vars.scala index 4a58f79932b3..e85bcbe2db04 100644 --- a/tests/neg-custom-args/captures/vars.scala +++ b/tests/neg-custom-args/captures/vars.scala @@ -6,12 +6,12 @@ def test(cap1: Cap, cap2: Cap) = var x = f val y = x val z = () => if x("") == "" then "a" else "b" - val zc: {cap1} () => String = z + val zc: {cap1} () -> String = z val z2 = () => { x = identity } - val z2c: () => Unit = z2 // error + val z2c: () -> Unit = z2 // error - var a: {*} String => String = f // error - var b: List[{*} String => String] = Nil // error + var a: String => String = f // error + var b: List[String => String] = Nil // error def scope = val cap3: Cap = CC() @@ -22,9 +22,9 @@ def test(cap1: Cap, cap2: Cap) = g val s = scope - val sc: {*} String => String = scope + val sc: String => String = scope - def local[T](op: Cap => T): T = op(CC()) + def local[T](op: Cap -> T): T = op(CC()) local { cap3 => // error def g(x: String): String = if cap3 == cap3 then "" else "a" @@ -32,7 +32,7 @@ def test(cap1: Cap, cap2: Cap) = } class Ref: - var elem: {cap1} String => String = null + var elem: {cap1} String -> String = null val r = Ref() r.elem = f diff --git a/tests/pos-custom-args/captures/bounded.scala b/tests/pos-custom-args/captures/bounded.scala index fad0b50c2137..85c1a67387b5 100644 --- a/tests/pos-custom-args/captures/bounded.scala +++ b/tests/pos-custom-args/captures/bounded.scala @@ -9,6 +9,6 @@ def test(c: Cap) = def f(x: Int): Int = if c == c then x else 0 val b = new B(f) val r1 = b.elem - val r1c: {c} Int => Int = r1 + val r1c: {c} Int -> Int = r1 val r2 = b.lateElem - val r2c: {c} () => {c} Int => Int = r2 \ No newline at end of file + val r2c: {c} () -> {c} Int -> Int = r2 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/boxmap-paper.scala b/tests/pos-custom-args/captures/boxmap-paper.scala index ed8c648526d1..aff4c38e1b9d 100644 --- a/tests/pos-custom-args/captures/boxmap-paper.scala +++ b/tests/pos-custom-args/captures/boxmap-paper.scala @@ -1,19 +1,18 @@ -infix type ==> [A, B] = {*} (A => B) -type Cell[+T] = [K] => (T ==> K) => K +type Cell[+T] = [K] -> (T => K) -> K def cell[T](x: T): Cell[T] = - [K] => (k: T ==> K) => k(x) + [K] => (k: T => K) => k(x) def get[T](c: Cell[T]): T = c[T](identity) -def map[A, B](c: Cell[A])(f: A ==> B): Cell[B] +def map[A, B](c: Cell[A])(f: A => B): Cell[B] = c[Cell[B]]((x: A) => cell(f(x))) -def pureMap[A, B](c: Cell[A])(f: A => B): Cell[B] +def pureMap[A, B](c: Cell[A])(f: A -> B): Cell[B] = c[Cell[B]]((x: A) => cell(f(x))) -def lazyMap[A, B](c: Cell[A])(f: A ==> B): {f} () => Cell[B] +def lazyMap[A, B](c: Cell[A])(f: A => B): {f} () -> Cell[B] = () => c[Cell[B]]((x: A) => cell(f(x))) trait IO: @@ -21,17 +20,17 @@ trait IO: def test(io: {*} IO) = - val loggedOne: {io} () => Int = () => { io.print("1"); 1 } + val loggedOne: {io} () -> Int = () => { io.print("1"); 1 } - val c: Cell[{io} () => Int] - = cell[{io} () => Int](loggedOne) + val c: Cell[{io} () -> Int] + = cell[{io} () -> Int](loggedOne) - val g = (f: {io} () => Int) => + val g = (f: {io} () -> Int) => val x = f(); io.print(" + ") val y = f(); io.print(s" = ${x + y}") - val r = lazyMap[{io} () => Int, Unit](c)(f => g(f)) - val r2 = lazyMap[{io} () => Int, Unit](c)(g) + val r = lazyMap[{io} () -> Int, Unit](c)(f => g(f)) + val r2 = lazyMap[{io} () -> Int, Unit](c)(g) val r3 = lazyMap(c)(g) val _ = r() val _ = r2() diff --git a/tests/pos-custom-args/captures/boxmap.scala b/tests/pos-custom-args/captures/boxmap.scala index a0dcade2b179..003e46804a9d 100644 --- a/tests/pos-custom-args/captures/boxmap.scala +++ b/tests/pos-custom-args/captures/boxmap.scala @@ -1,20 +1,18 @@ type Top = Any @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) def box[T <: Top](x: T): Box[T] = - [K <: Top] => (k: T ==> K) => k(x) + [K <: Top] => (k: T => K) => k(x) -def map[A <: Top, B <: Top](b: Box[A])(f: A ==> B): Box[B] = +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(f) = +def lazymap[A <: Top, B <: Top](b: Box[A])(f: A => B): (() -> Box[B]) @retains(f) = () => 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) = + 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(f) = lazymap[A, B] + val x: (b: Box[A]) -> (f: A => B) -> (() -> Box[B]) @retains(f) = lazymap[A, B] () diff --git a/tests/pos-custom-args/captures/capt-capability.scala b/tests/pos-custom-args/captures/capt-capability.scala index 41da15d288f1..9990542a199d 100644 --- a/tests/pos-custom-args/captures/capt-capability.scala +++ b/tests/pos-custom-args/captures/capt-capability.scala @@ -1,15 +1,15 @@ import annotation.capability @capability class Cap -def f1(c: Cap): {c} () => c.type = () => c // ok +def f1(c: Cap): {c} () -> c.type = () => c // ok def f2: Int = - val g: {*} Boolean => Int = ??? + val g: Boolean => Int = ??? val x = g(true) x def f3: Int = - def g: {*} Boolean => Int = ??? + def g: Boolean => Int = ??? def h = g val x = g.apply(true) x @@ -17,10 +17,10 @@ def f3: Int = def foo() = val x: Cap = ??? val y: Cap = x - val x2: {x} () => Cap = ??? - val y2: {x} () => Cap = x2 + val x2: {x} () -> Cap = ??? + val y2: {x} () -> Cap = x2 - val z1: {*} () => Cap = f1(x) + val z1: () => Cap = f1(x) def h[X](a: X)(b: X) = a val z2 = diff --git a/tests/pos-custom-args/captures/capt-depfun.scala b/tests/pos-custom-args/captures/capt-depfun.scala index 6b99eff32692..072eaefd3e78 100644 --- a/tests/pos-custom-args/captures/capt-depfun.scala +++ b/tests/pos-custom-args/captures/capt-depfun.scala @@ -1,18 +1,18 @@ class C type Cap = C @retains(*) -type T = (x: Cap) => String @retains(x) +type T = (x: Cap) -> String @retains(x) -val aa: ((x: Cap) => String @retains(x)) = (x: Cap) => "" +val aa: ((x: Cap) -> String @retains(x)) = (x: Cap) => "" def f(y: Cap, z: Cap): String @retains(*) = - val a: ((x: Cap) => String @retains(x)) = (x: Cap) => "" + val a: ((x: Cap) -> String @retains(x)) = (x: Cap) => "" val b = a(y) val c: String @retains(y) = b def g(): C @retains(y, z) = ??? val d = a(g()) - val ac: ((x: Cap) => String @retains(x) => String @retains(x)) = ??? - val bc: (({y} String) => {y} String) = ac(y) - val dc: (String => {y, z} String) = ac(g()) + val ac: ((x: Cap) -> String @retains(x) -> String @retains(x)) = ??? + val bc: (({y} String) -> {y} String) = ac(y) + val dc: (String -> {y, z} String) = ac(g()) c diff --git a/tests/pos-custom-args/captures/capt-depfun2.scala b/tests/pos-custom-args/captures/capt-depfun2.scala index 17f98b4a1554..98ee9dbfdc6b 100644 --- a/tests/pos-custom-args/captures/capt-depfun2.scala +++ b/tests/pos-custom-args/captures/capt-depfun2.scala @@ -3,6 +3,6 @@ type Cap = C @retains(*) def f(y: Cap, z: Cap) = def g(): C @retains(y, z) = ??? - val ac: ((x: Cap) => Array[String @retains(x)]) = ??? + val ac: ((x: Cap) -> Array[String @retains(x)]) = ??? val dc: Array[? >: String <: {y, z} String] = ac(g()) // needs to be inferred val ec = ac(y) diff --git a/tests/pos-custom-args/captures/capt-test.scala b/tests/pos-custom-args/captures/capt-test.scala index f40bd2ff1746..6ee0d2a4d9f4 100644 --- a/tests/pos-custom-args/captures/capt-test.scala +++ b/tests/pos-custom-args/captures/capt-test.scala @@ -2,7 +2,7 @@ abstract class LIST[+T]: def isEmpty: Boolean def head: T def tail: LIST[T] - def map[U](f: {*} T => U): LIST[U] = + def map[U](f: T => U): LIST[U] = if isEmpty then NIL else CONS(f(head), tail.map(f)) @@ -15,7 +15,7 @@ object NIL extends LIST[Nothing]: def head = ??? def tail = ??? -def map[A, B](f: {*} A => B)(xs: LIST[A]): LIST[B] = +def map[A, B](f: A => B)(xs: LIST[A]): LIST[B] = xs.map(f) class C @@ -29,7 +29,7 @@ def test(c: Cap, d: Cap) = val zs = val z = g CONS(z, ys) - val zsc: LIST[{d, y} Cap => Unit] = zs + val zsc: LIST[{d, y} Cap -> Unit] = zs val a4 = zs.map(identity) - val a4c: LIST[{d, y} Cap => Unit] = a4 + val a4c: LIST[{d, y} Cap -> Unit] = a4 diff --git a/tests/pos-custom-args/captures/capt1.scala b/tests/pos-custom-args/captures/capt1.scala index 14c0855544d4..e8e217435f96 100644 --- a/tests/pos-custom-args/captures/capt1.scala +++ b/tests/pos-custom-args/captures/capt1.scala @@ -1,14 +1,14 @@ class C type Cap = {*} C -def f1(c: Cap): {c} () => c.type = () => c // ok +def f1(c: Cap): {c} () -> c.type = () => c // ok def f2: Int = - val g: {*} Boolean => Int = ??? + val g: {*} Boolean -> Int = ??? val x = g(true) x def f3: Int = - def g: {*} Boolean => Int = ??? + def g: Boolean => Int = ??? def h = g val x = g.apply(true) x @@ -16,10 +16,10 @@ def f3: Int = def foo() = val x: {*} C = ??? val y: {x} C = x - val x2: {x} () => C = ??? - val y2: {x} () => {x} C = x2 + val x2: {x} () -> C = ??? + val y2: {x} () -> {x} C = x2 - val z1: {*} () => Cap = f1(x) + val z1: () => Cap = f1(x) def h[X](a: X)(b: X) = a val z2 = diff --git a/tests/pos-custom-args/captures/capt2.scala b/tests/pos-custom-args/captures/capt2.scala index e3d4cd67b30c..11bb2d5eb7b5 100644 --- a/tests/pos-custom-args/captures/capt2.scala +++ b/tests/pos-custom-args/captures/capt2.scala @@ -9,12 +9,12 @@ def test1() = def test2() = val x: Cap = C() val y = () => { x; () } - def z: (() => Unit) @retains(x) = y - z: (() => Unit) @retains(x) - def z2: (() => Unit) @retains(y) = y - z2: (() => Unit) @retains(y) - val p: {*} () => String = () => "abc" + def z: (() -> Unit) @retains(x) = y + z: (() -> Unit) @retains(x) + def z2: (() -> Unit) @retains(y) = y + z2: (() -> Unit) @retains(y) + val p: {*} () -> String = () => "abc" val q: {p} C = ??? - p: ({p} () => String) + p: ({p} () -> String) diff --git a/tests/pos-custom-args/captures/cc-expand.scala b/tests/pos-custom-args/captures/cc-expand.scala index eedc95554b17..7bce1ea8387e 100644 --- a/tests/pos-custom-args/captures/cc-expand.scala +++ b/tests/pos-custom-args/captures/cc-expand.scala @@ -8,14 +8,14 @@ object Test: def test(ct: CT, dt: CT) = - def x0: A => {ct} B = ??? + def x0: A -> {ct} B = ??? - def x1: A => B @retains(ct) = ??? - def x2: A => B => C @retains(ct) = ??? - def x3: A => () => B => C @retains(ct) = ??? + def x1: A -> B @retains(ct) = ??? + def x2: A -> B -> C @retains(ct) = ??? + def x3: A -> () -> B -> C @retains(ct) = ??? - def x4: (x: A @retains(ct)) => B => C = ??? + def x4: (x: A @retains(ct)) -> B -> C = ??? - def x5: A => (x: B @retains(ct)) => () => C @retains(dt) = ??? - def x6: A => (x: B @retains(ct)) => (() => C @retains(dt)) @retains(x, dt) = ??? - def x7: A => (x: B @retains(ct)) => (() => C @retains(dt)) @retains(x) = ??? \ No newline at end of file + def x5: A -> (x: B @retains(ct)) -> () -> C @retains(dt) = ??? + def x6: A -> (x: B @retains(ct)) -> (() -> C @retains(dt)) @retains(x, dt) = ??? + def x7: A -> (x: B @retains(ct)) -> (() -> C @retains(dt)) @retains(x) = ??? \ No newline at end of file diff --git a/tests/pos-custom-args/captures/impurefun.scala b/tests/pos-custom-args/captures/impurefun.scala new file mode 100644 index 000000000000..6e31008fe54a --- /dev/null +++ b/tests/pos-custom-args/captures/impurefun.scala @@ -0,0 +1,8 @@ +object Test: + + val f: ImpureFunction1[Int, Int] = (x: Int) => x + 1 + + val g: Int -> Int = (x: Int) => x + 1 + + val h: Int => Int = (x: Int) => x + 1 + diff --git a/tests/pos-custom-args/captures/lazylists-mono.scala b/tests/pos-custom-args/captures/lazylists-mono.scala index 82c44abf703a..44ab36ded6a2 100644 --- a/tests/pos-custom-args/captures/lazylists-mono.scala +++ b/tests/pos-custom-args/captures/lazylists-mono.scala @@ -6,21 +6,21 @@ type Cap = {*} CC def test(E: Cap) = trait LazyList[+A]: - protected def contents: {E} () => (A, {E} LazyList[A]) + protected def contents: {E} () -> (A, {E} LazyList[A]) def isEmpty: Boolean def head: A = contents()._1 def tail: {E} LazyList[A] = contents()._2 - class LazyCons[+A](override val contents: {E} () => (A, {E} LazyList[A])) + class LazyCons[+A](override val contents: {E} () -> (A, {E} LazyList[A])) extends LazyList[A]: def isEmpty: Boolean = false object LazyNil extends LazyList[Nothing]: - def contents: {E} () => (Nothing, LazyList[Nothing]) = ??? + def contents: {E} () -> (Nothing, LazyList[Nothing]) = ??? def isEmpty: Boolean = true extension [A](xs: {E} LazyList[A]) - def map[B](f: {E} A => B): {E} LazyList[B] = + def map[B](f: {E} A -> B): {E} LazyList[B] = if xs.isEmpty then LazyNil else val cons = () => (f(xs.head), xs.tail.map(f)) diff --git a/tests/pos-custom-args/captures/lazylists.scala b/tests/pos-custom-args/captures/lazylists.scala index 17d5f8546edc..bf3e0300b5b5 100644 --- a/tests/pos-custom-args/captures/lazylists.scala +++ b/tests/pos-custom-args/captures/lazylists.scala @@ -14,7 +14,7 @@ object LazyNil extends LazyList[Nothing]: def tail = ??? extension [A](xs: {*} LazyList[A]) - def map[B](f: {*} A => B): {xs, f} LazyList[B] = + def map[B](f: A => B): {xs, f} LazyList[B] = final class Mapped extends LazyList[B]: this: ({xs, f} Mapped) => diff --git a/tests/pos-custom-args/captures/lazylists1.scala b/tests/pos-custom-args/captures/lazylists1.scala index 4c8006fb0e29..7fbdde87ad9b 100644 --- a/tests/pos-custom-args/captures/lazylists1.scala +++ b/tests/pos-custom-args/captures/lazylists1.scala @@ -13,23 +13,23 @@ object LazyNil extends LazyList[Nothing]: def head = ??? def tail = ??? -final class LazyCons[+T](val x: T, val xs: {*} () => {*} LazyList[T]) extends LazyList[T]: +final class LazyCons[+T](val x: T, val xs: Int => {*} LazyList[T]) extends LazyList[T]: this: ({*} LazyList[T]) => def isEmpty = false def head = x - def tail: {this} LazyList[T] = xs() + def tail: {this} LazyList[T] = xs(0) extension [A](xs: {*} LazyList[A]) - def map[B](f: {*} A => B): {xs, f} LazyList[B] = + def map[B](f: A => B): {xs, f} LazyList[B] = if xs.isEmpty then LazyNil - else LazyCons(f(xs.head), () => xs.tail.map(f)) + else LazyCons(f(xs.head), x => xs.tail.map(f)) def test(cap1: Cap, cap2: Cap) = def f(x: String): String = if cap1 == cap1 then "" else "a" def g(x: String): String = if cap2 == cap2 then "" else "a" - val xs = LazyCons("", () => if f("") == f("") then LazyNil else LazyNil) + val xs = new LazyCons("", x => if f("") == f("") then LazyNil else LazyNil) val xsc: {cap1} LazyList[String] = xs val ys = xs.map(g) val ysc: {cap1, cap2} LazyList[String] = ys diff --git a/tests/pos-custom-args/captures/lazyref.scala b/tests/pos-custom-args/captures/lazyref.scala index 39748b00506b..2ab770178a16 100644 --- a/tests/pos-custom-args/captures/lazyref.scala +++ b/tests/pos-custom-args/captures/lazyref.scala @@ -1,15 +1,14 @@ -class CC -type Cap = {*} CC +@annotation.capability class Cap -class LazyRef[T](val elem: {*} () => T): +class LazyRef[T](val elem: () => T): val get = elem - def map[U](f: {*} T => U): {f, this} LazyRef[U] = + def map[U](f: T => U): {f, this} LazyRef[U] = new LazyRef(() => f(elem())) -def map[A, B](ref: {*} LazyRef[A], f: {*} A => B): {f, ref} LazyRef[B] = +def map[A, B](ref: {*} LazyRef[A], f: A => B): {f, ref} LazyRef[B] = new LazyRef(() => f(ref.elem())) -def mapc[A, B]: (ref: {*} LazyRef[A], f: {*} A => B) => {f, ref} LazyRef[B] = +def mapc[A, B]: (ref: {*} LazyRef[A], f: A => B) => {f, ref} LazyRef[B] = (ref1, f1) => map[A, B](ref1, f1) def test(cap1: Cap, cap2: Cap) = diff --git a/tests/pos-custom-args/captures/list-encoding.scala b/tests/pos-custom-args/captures/list-encoding.scala index 74bc8bd2b099..59ae61273af7 100644 --- a/tests/pos-custom-args/captures/list-encoding.scala +++ b/tests/pos-custom-args/captures/list-encoding.scala @@ -3,10 +3,10 @@ package listEncoding class Cap type Op[T, C] = - {*} (v: T) => {*} (s: C) => C + (v: T) => (s: C) => C type List[T] = - [C] => (op: Op[T, C]) => {op} (s: C) => C + [C] -> (op: Op[T, C]) -> {op} (s: C) -> C def nil[T]: List[T] = [C] => (op: Op[T, C]) => (s: C) => s diff --git a/tests/pos-custom-args/captures/lists.scala b/tests/pos-custom-args/captures/lists.scala index 139f885ec87a..f52727af7b94 100644 --- a/tests/pos-custom-args/captures/lists.scala +++ b/tests/pos-custom-args/captures/lists.scala @@ -2,7 +2,7 @@ abstract class LIST[+T]: def isEmpty: Boolean def head: T def tail: LIST[T] - def map[U](f: {*} T => U): LIST[U] = + def map[U](f: {*} T -> U): LIST[U] = if isEmpty then NIL else CONS(f(head), tail.map(f)) @@ -15,11 +15,10 @@ object NIL extends LIST[Nothing]: def head = ??? def tail = ??? -def map[A, B](f: {*} A => B)(xs: LIST[A]): LIST[B] = +def map[A, B](f: A => B)(xs: LIST[A]): LIST[B] = xs.map(f) -class C -type Cap = {*} C +@annotation.capability class Cap def test(c: Cap, d: Cap, e: Cap) = def f(x: Cap): Unit = if c == x then () @@ -29,63 +28,63 @@ def test(c: Cap, d: Cap, e: Cap) = val zs = val z = g CONS(z, ys) - val zsc: LIST[{d, y} Cap => Unit] = zs + val zsc: LIST[{d, y} Cap -> Unit] = zs val z1 = zs.head - val z1c: {y, d} Cap => Unit = z1 + val z1c: {y, d} Cap -> Unit = z1 val ys1 = zs.tail val y1 = ys1.head def m1[A, B] = - (f: {*} A => B) => (xs: LIST[A]) => xs.map(f) + (f: A => B) => (xs: LIST[A]) => xs.map(f) - def m1c: (f: {*} String => Int) => {f} LIST[String] => LIST[Int] = m1[String, Int] + def m1c: (f: String => Int) -> {f} LIST[String] -> LIST[Int] = m1[String, Int] def m2 = [A, B] => - (f: {*} A => B) => (xs: LIST[A]) => xs.map(f) + (f: A => B) => (xs: LIST[A]) => xs.map(f) - def m2c: [A, B] => (f: {*} A => B) => {f} LIST[A] => LIST[B] = m2 + def m2c: [A, B] -> (f: A => B) -> {f} LIST[A] -> LIST[B] = m2 def eff[A](x: A) = if x == e then x else x val eff2 = [A] => (x: A) => if x == e then x else x - val a0 = identity[{d, y} Cap => Unit] - val a0c: ({d, y} Cap => Unit) => {d, y} Cap => Unit = a0 - val a1 = zs.map[{d, y} Cap => Unit](a0) - val a1c: LIST[{d, y} Cap => Unit] = a1 - val a2 = zs.map[{d, y} Cap => Unit](identity[{d, y} Cap => Unit]) - val a2c: LIST[{d, y} Cap => Unit] = a2 - val a3 = zs.map(identity[{d, y} Cap => Unit]) - val a3c: LIST[{d, y} Cap => Unit] = a3 + val a0 = identity[{d, y} Cap -> Unit] + val a0c: ({d, y} Cap -> Unit) -> {d, y} Cap -> Unit = a0 + val a1 = zs.map[{d, y} Cap -> Unit](a0) + val a1c: LIST[{d, y} Cap -> Unit] = a1 + val a2 = zs.map[{d, y} Cap -> Unit](identity[{d, y} Cap -> Unit]) + val a2c: LIST[{d, y} Cap -> Unit] = a2 + val a3 = zs.map(identity[{d, y} Cap -> Unit]) + val a3c: LIST[{d, y} Cap -> Unit] = a3 val a4 = zs.map(identity) - val a4c: LIST[{d, c} Cap => Unit] = a4 - val a5 = map[{d, y} Cap => Unit, {d, y} Cap => Unit](identity)(zs) - val a5c: LIST[{d, c} Cap => Unit] = a5 - val a6 = m1[{d, y} Cap => Unit, {d, y} Cap => Unit](identity)(zs) - val a6c: LIST[{d, c} Cap => Unit] = a6 + val a4c: LIST[{d, c} Cap -> Unit] = a4 + val a5 = map[{d, y} Cap -> Unit, {d, y} Cap -> Unit](identity)(zs) + val a5c: LIST[{d, c} Cap -> Unit] = a5 + val a6 = m1[{d, y} Cap -> Unit, {d, y} Cap -> Unit](identity)(zs) + val a6c: LIST[{d, c} Cap -> Unit] = a6 - val b0 = eff[{d, y} Cap => Unit] - val b0c: {e} ({d, y} Cap => Unit) => {d, y} Cap => Unit = b0 - val b1 = zs.map[{d, y} Cap => Unit](a0) - val b1c: {e} LIST[{d, y} Cap => Unit] = b1 - val b2 = zs.map[{d, y} Cap => Unit](eff[{d, y} Cap => Unit]) - val b2c: {e} LIST[{d, y} Cap => Unit] = b2 - val b3 = zs.map(eff[{d, y} Cap => Unit]) - val b3c: {e} LIST[{d, y} Cap => Unit] = b3 + val b0 = eff[{d, y} Cap -> Unit] + val b0c: {e} ({d, y} Cap -> Unit) -> {d, y} Cap -> Unit = b0 + val b1 = zs.map[{d, y} Cap -> Unit](a0) + val b1c: {e} LIST[{d, y} Cap -> Unit] = b1 + val b2 = zs.map[{d, y} Cap -> Unit](eff[{d, y} Cap -> Unit]) + val b2c: {e} LIST[{d, y} Cap -> Unit] = b2 + val b3 = zs.map(eff[{d, y} Cap -> Unit]) + val b3c: {e} LIST[{d, y} Cap -> Unit] = b3 val b4 = zs.map(eff) - val b4c: {e} LIST[{d, c} Cap => Unit] = b4 - val b5 = map[{d, y} Cap => Unit, {d, y} Cap => Unit](eff)(zs) - val b5c: {e} LIST[{d, c} Cap => Unit] = b5 - val b6 = m1[{d, y} Cap => Unit, {d, y} Cap => Unit](eff)(zs) - val b6c: {e} LIST[{d, c} Cap => Unit] = b6 + val b4c: {e} LIST[{d, c} Cap -> Unit] = b4 + val b5 = map[{d, y} Cap -> Unit, {d, y} Cap -> Unit](eff)(zs) + val b5c: {e} LIST[{d, c} Cap -> Unit] = b5 + val b6 = m1[{d, y} Cap -> Unit, {d, y} Cap -> Unit](eff)(zs) + val b6c: {e} LIST[{d, c} Cap -> Unit] = b6 - val c0 = eff2[{d, y} Cap => Unit] - val c0c: {e} ({d, y} Cap => Unit) => {d, y} Cap => Unit = c0 - val c1 = zs.map[{d, y} Cap => Unit](a0) - val c1c: {e} LIST[{d, y} Cap => Unit] = c1 - val c2 = zs.map[{d, y} Cap => Unit](eff2[{d, y} Cap => Unit]) - val c2c: {e} LIST[{d, y} Cap => Unit] = c2 - val c3 = zs.map(eff2[{d, y} Cap => Unit]) - val c3c: {e} LIST[{d, y} Cap => Unit] = c3 + val c0 = eff2[{d, y} Cap -> Unit] + val c0c: {e} ({d, y} Cap -> Unit) -> {d, y} Cap -> Unit = c0 + val c1 = zs.map[{d, y} Cap -> Unit](a0) + val c1c: {e} LIST[{d, y} Cap -> Unit] = c1 + val c2 = zs.map[{d, y} Cap -> Unit](eff2[{d, y} Cap -> Unit]) + val c2c: {e} LIST[{d, y} Cap -> Unit] = c2 + val c3 = zs.map(eff2[{d, y} Cap -> Unit]) + val c3c: {e} LIST[{d, y} Cap -> Unit] = c3 diff --git a/tests/pos-custom-args/captures/pairs.scala b/tests/pos-custom-args/captures/pairs.scala index 4f23a086a075..14d484ff21b1 100644 --- a/tests/pos-custom-args/captures/pairs.scala +++ b/tests/pos-custom-args/captures/pairs.scala @@ -1,6 +1,5 @@ -class C -type Cap = {*} C +@annotation.capability class Cap object Generic: @@ -13,13 +12,13 @@ object Generic: def g(x: Cap): Unit = if d == x then () val p = Pair(f, g) val x1 = p.fst - val x1c: {c} Cap => Unit = x1 + val x1c: {c} Cap -> Unit = x1 val y1 = p.snd - val y1c: {d} Cap => Unit = y1 + val y1c: {d} Cap -> Unit = y1 object Monomorphic: - class Pair(val x: {*} Cap => Unit, val y: {*} Cap => Unit): + class Pair(val x: Cap => Unit, val y: {*} Cap -> Unit): def fst = x def snd = y @@ -28,6 +27,6 @@ object Monomorphic: def g(x: Cap): Unit = if d == x then () val p = Pair(f, g) val x1 = p.fst - val x1c: {c} Cap => Unit = x1 + val x1c: {c} Cap -> Unit = x1 val y1 = p.snd - val y1c: {d} Cap => Unit = y1 + val y1c: {d} Cap -> Unit = y1 diff --git a/tests/pos-custom-args/captures/try.scala b/tests/pos-custom-args/captures/try.scala index a50eeabfb3a3..14773cd5be0f 100644 --- a/tests/pos-custom-args/captures/try.scala +++ b/tests/pos-custom-args/captures/try.scala @@ -3,7 +3,7 @@ import language.experimental.erasedDefinitions class CT[E <: Exception] type CanThrow[E <: Exception] = CT[E] @retains(*) -infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R +infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?-> R class Fail extends Exception @@ -12,7 +12,7 @@ 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 = +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) diff --git a/tests/pos-custom-args/captures/try3.scala b/tests/pos-custom-args/captures/try3.scala index b29ad2d4b352..b8937bec00f3 100644 --- a/tests/pos-custom-args/captures/try3.scala +++ b/tests/pos-custom-args/captures/try3.scala @@ -2,8 +2,7 @@ import language.experimental.erasedDefinitions import annotation.capability import java.io.IOException -class CT[-E] // variance is needed for correct rechecking inference -type CanThrow[E] = {*} CT[E] +@annotation.capability class CanThrow[-E] def handle[E <: Exception, T](op: CanThrow[E] ?=> T)(handler: E => T): T = val x: CanThrow[E] = ??? @@ -14,7 +13,7 @@ def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = throw ex def test1: Int = - def f(a: Boolean): Boolean => CanThrow[IOException] ?=> Int = + def f(a: Boolean): Boolean -> CanThrow[IOException] ?-> Int = handle { if !a then raise(IOException()) (b: Boolean) => (_: CanThrow[IOException]) ?=> diff --git a/tests/pos-custom-args/captures/vars.scala b/tests/pos-custom-args/captures/vars.scala index aca56c55f386..12721158a2bb 100644 --- a/tests/pos-custom-args/captures/vars.scala +++ b/tests/pos-custom-args/captures/vars.scala @@ -1,18 +1,17 @@ -class CC -type Cap = {*} CC +@annotation.capability class Cap def test(cap1: Cap, cap2: Cap) = def f(x: String): String = if cap1 == cap1 then "" else "a" var x = f val y = x val z = () => if x("") == "" then "a" else "b" - val zc: {cap1} () => String = z + val zc: {cap1} () -> String = z val z2 = () => { x = identity } - val z2c: {cap1} () => Unit = z2 + val z2c: {cap1} () -> Unit = z2 class Ref: - var elem: {cap1} String => String = null + var elem: {cap1} String -> String = null val r = Ref() r.elem = f - val fc: {cap1} String => String = r.elem + val fc: {cap1} String -> String = r.elem diff --git a/tests/pos/i12723.scala b/tests/pos/i12723.scala index d1cab3ede638..022a3a458f04 100644 --- a/tests/pos/i12723.scala +++ b/tests/pos/i12723.scala @@ -1,10 +1,10 @@ class Fun[|*|[_, _]] { - enum ->[A, B] { - case BiId[X, Y]() extends ((X |*| Y) -> (X |*| Y)) + enum -->[A, B] { + case BiId[X, Y]() extends ((X |*| Y) --> (X |*| Y)) } - def go[A, B](f: A -> B): Unit = + def go[A, B](f: A --> B): Unit = f match { - case ->.BiId() => () + case -->.BiId() => () } } diff --git a/tests/pos/impurefun.scala b/tests/pos/impurefun.scala new file mode 100644 index 000000000000..c9f4c54a0b90 --- /dev/null +++ b/tests/pos/impurefun.scala @@ -0,0 +1,4 @@ +object Test: + + val f: ImpureFunction1[Int, Int] = (x: Int) => x + 1 +