From a2dd16f7cf1f56d52a9b040358492ae22182ea40 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 24 Oct 2022 13:06:29 +0200 Subject: [PATCH 1/7] Convert -Ycc to language import Two new experimental language imports: - captureChecking: replaces -Ycc - pureFunctions: Enables pure function syntax A -> B --- compiler/src/dotty/tools/dotc/Compiler.scala | 4 +-- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 8 ++--- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 7 ++-- .../dotty/tools/dotc/cc/CheckCaptures.scala | 8 ++--- .../src/dotty/tools/dotc/config/Config.scala | 2 +- .../src/dotty/tools/dotc/config/Feature.scala | 7 ++++ .../tools/dotc/config/ScalaSettings.scala | 5 ++- .../dotty/tools/dotc/core/Definitions.scala | 7 ++-- .../src/dotty/tools/dotc/core/NameOps.scala | 5 +-- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 16 +++++----- .../core/unpickleScala2/Scala2Unpickler.scala | 4 +-- .../dotty/tools/dotc/parsing/Parsers.scala | 23 ++++++------- .../tools/dotc/printing/PlainPrinter.scala | 4 +-- .../tools/dotc/printing/RefinedPrinter.scala | 12 +++---- .../tools/dotc/transform/FirstTransform.scala | 5 +-- .../tools/dotc/transform/PostTyper.scala | 5 +-- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- .../dotty/tools/dotc/CompilationTests.scala | 8 ++--- docs/_docs/reference/experimental/cc.md | 15 ++++----- docs/_docs/reference/experimental/purefuns.md | 32 +++++++++++++++++++ docs/sidebar.yml | 1 + ...aptureChecked.scala => WithPureFuns.scala} | 4 +-- .../runtime/stdLibPatches/language.scala | 11 +++++++ .../stdlibExperimentalDefinitions.scala | 2 +- 26 files changed, 128 insertions(+), 73 deletions(-) create mode 100644 docs/_docs/reference/experimental/purefuns.md rename library/src/scala/annotation/internal/{CaptureChecked.scala => WithPureFuns.scala} (52%) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 46d36c4412c7..b121a47781e1 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -81,8 +81,8 @@ class Compiler { new PatternMatcher) :: // Compile pattern matches List(new TestRecheck.Pre) :: // Test only: run rechecker, enabled under -Yrecheck-test List(new TestRecheck) :: // Test only: run rechecker, enabled under -Yrecheck-test - List(new CheckCaptures.Pre) :: // Preparations for check captures phase, enabled under -Ycc - List(new CheckCaptures) :: // Check captures, enabled under -Ycc + List(new CheckCaptures.Pre) :: // Preparations for check captures phase, enabled under captureChecking + List(new CheckCaptures) :: // Check captures, enabled under captureChecking List(new ElimOpaque, // Turn opaque into normal aliases new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only) new ExplicitOuter, // Add accessors to outer classes from nested ones. diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 97e7b70ce890..c0130715a1a0 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -195,11 +195,11 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => case arg => arg.typeOpt.widen.isRepeatedParam } - /** Is tree a type tree of the form `=> T` or (under -Ycc) `{refs}-> T`? */ + /** Is tree a type tree of the form `=> T` or (under pureFunctions) `{refs}-> T`? */ def isByNameType(tree: Tree)(using Context): Boolean = stripByNameType(tree) ne tree - /** Strip `=> T` to `T` and (under -Ycc) `{refs}-> T` to `T` */ + /** Strip `=> T` to `T` and (under pureFunctions) `{refs}-> T` to `T` */ def stripByNameType(tree: Tree)(using Context): Tree = unsplice(tree) match case ByNameTypeTree(t1) => t1 case untpd.CapturingTypeTree(_, parent) => @@ -400,12 +400,12 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] } } - /** Under -Ycc: A builder and extractor for `=> T`, which is an alias for `{*}-> T`. + /** Under pureFunctions: A builder and extractor for `=> T`, which is an alias for `{*}-> T`. * Only trees of the form `=> T` are matched; trees written directly as `{*}-> T` * are ignored by the extractor. */ object ImpureByNameTypeTree: - + def apply(tp: ByNameTypeTree)(using Context): untpd.CapturingTypeTree = untpd.CapturingTypeTree( Ident(nme.CAPTURE_ROOT).withSpan(tp.span.startPos) :: Nil, tp) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 6f3f134f9342..da9f938cb1a6 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -217,7 +217,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Infix()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Infix) - /** Used under -Ycc to mark impure function types `A => B` in `FunctionWithMods` */ + /** Used under pureFunctions to mark impure function types `A => B` in `FunctionWithMods` */ case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure) } diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 69b0567b30c5..cf4c4a4b2328 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -9,6 +9,7 @@ import Decorators.*, NameOps.* import config.Printers.capt import util.Property.Key import tpd.* +import config.Feature private val Captures: Key[CaptureSet] = Key() private val BoxedType: Key[BoxedTypeCache] = Key() @@ -120,11 +121,11 @@ extension (tp: Type) case _ => tp - /** Under -Ycc, map regular function type to impure function type + /** Under pureFunctions, map regular function type to impure function type */ - def adaptFunctionTypeUnderCC(using Context): Type = tp match + def adaptFunctionTypeUnderPureFuns(using Context): Type = tp match case AppliedType(fn, args) - if ctx.settings.Ycc.value && defn.isFunctionClass(fn.typeSymbol) => + if Feature.pureFunsEnabled && defn.isFunctionClass(fn.typeSymbol) => val fname = fn.typeSymbol.name defn.FunctionType( fname.functionArity, diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index ec5be8d0fd4a..e06b05ee3c39 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -7,7 +7,7 @@ import Phases.*, DenotTransformers.*, SymDenotations.* import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.* import Types.*, StdNames.*, Denotations.* import config.Printers.{capt, recheckr} -import config.Config +import config.{Config, Feature} import ast.{tpd, untpd, Trees} import Trees.* import typer.RefChecks.{checkAllOverrides, checkParents} @@ -26,7 +26,7 @@ object CheckCaptures: class Pre extends PreRecheck, SymTransformer: - override def isEnabled(using Context) = ctx.settings.Ycc.value + override def isEnabled(using Context) = Feature.ccEnabled /** Reset `private` flags of parameter accessors so that we can refine them * in Setup if they have non-empty capture sets. Special handling of some @@ -133,7 +133,7 @@ class CheckCaptures extends Recheck, SymTransformer: import CheckCaptures.* def phaseName: String = "cc" - override def isEnabled(using Context) = ctx.settings.Ycc.value + override def isEnabled(using Context) = Feature.ccEnabled def newRechecker()(using Context) = CaptureChecker(ctx) @@ -148,7 +148,7 @@ class CheckCaptures extends Recheck, SymTransformer: /** Check overrides again, taking capture sets into account. * TODO: Can we avoid doing overrides checks twice? * We need to do them here since only at this phase CaptureTypes are relevant - * But maybe we can then elide the check during the RefChecks phase if -Ycc is set? + * But maybe we can then elide the check during the RefChecks phase under captureChecking? */ def checkOverrides = new TreeTraverser: def traverse(t: Tree)(using Context) = diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 1b0fea9184d1..17e3ec352e7c 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -240,7 +240,7 @@ object Config { */ inline val printCaptureSetsAsPrefix = true - /** If true, allow mappping capture set variables under -Ycc with maps that are neither + /** If true, allow mappping capture set variables under captureChecking with maps that are neither * bijective nor idempotent. We currently do now know how to do this correctly in all * cases, though. */ diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 4a87f5b4a537..cc7d4ab0511e 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -28,6 +28,8 @@ object Feature: val symbolLiterals = deprecated("symbolLiterals") val fewerBraces = experimental("fewerBraces") val saferExceptions = experimental("saferExceptions") + val pureFunctions = experimental("pureFunctions") + val captureChecking = experimental("captureChecking") /** Is `feature` enabled by by a command-line setting? The enabling setting is * @@ -75,6 +77,11 @@ object Feature: def scala2ExperimentalMacroEnabled(using Context) = enabled(scala2macros) + def pureFunsEnabled(using Context) = + enabled(pureFunctions) || ccEnabled + + def ccEnabled(using Context) = enabled(captureChecking) + def sourceVersionSetting(using Context): SourceVersion = SourceVersion.valueOf(ctx.settings.source.value) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 81f1d4e2923c..a2dba94ad9fc 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -328,9 +328,8 @@ private sealed trait YSettings: val YcheckInit: Setting[Boolean] = BooleanSetting("-Ysafe-init", "Ensure safe initialization of objects") val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation") val YrecheckTest: Setting[Boolean] = BooleanSetting("-Yrecheck-test", "Run basic rechecking (internal test only)") - val Ycc: Setting[Boolean] = BooleanSetting("-Ycc", "Check captured references (warning: extremely experimental and unstable)") - val YccDebug: Setting[Boolean] = BooleanSetting("-Ycc-debug", "Used in conjunction with -Ycc, debug info for captured references") - val YccNoAbbrev: Setting[Boolean] = BooleanSetting("-Ycc-no-abbrev", "Used in conjunction with -Ycc, suppress type abbreviations") + val YccDebug: Setting[Boolean] = BooleanSetting("-Ycc-debug", "Used in conjunction with captureChecking language import, debug info for captured references") + val YccNoAbbrev: Setting[Boolean] = BooleanSetting("-Ycc-no-abbrev", "Used in conjunction with captureChecking language import, suppress type abbreviations") /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index fce3241bcc40..3016c35603f3 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -14,6 +14,7 @@ import typer.ImportInfo.RootRef import Comments.CommentsContext import Comments.Comment import util.Spans.NoSpan +import config.Feature import Symbols.requiredModuleRef import cc.{CapturingType, CaptureSet, EventuallyCapturingType} @@ -976,7 +977,6 @@ class Definitions { @tu lazy val BooleanBeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BooleanBeanProperty") @tu lazy val BodyAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Body") @tu lazy val CapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.capability") - @tu lazy val CaptureCheckedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.CaptureChecked") @tu lazy val ChildAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Child") @tu lazy val ContextResultCountAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ContextResultCount") @tu lazy val ProvisionalSuperClassAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ProvisionalSuperClass") @@ -1012,6 +1012,7 @@ class Definitions { @tu lazy val UncheckedStableAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedStable") @tu lazy val UncheckedVarianceAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedVariance") @tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") + @tu lazy val WithPureFunsAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WithPureFuns") @tu lazy val FieldMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.field") @tu lazy val GetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.getter") @tu lazy val ParamMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.param") @@ -1160,7 +1161,7 @@ class Definitions { /** Extractor for context function types representing by-name parameters, of the form * `() ?=> T`. - * Under -Ycc, this becomes `() ?-> T` or `{r1, ..., rN} () ?-> T`. + * Under purefunctions, this becomes `() ?-> T` or `{r1, ..., rN} () ?-> T`. */ object ByNameFunction: def apply(tp: Type)(using Context): Type = tp match @@ -1984,7 +1985,7 @@ class Definitions { if (!isInitialized) { // force initialization of every symbol that is synthesized or hijacked by the compiler val forced = syntheticCoreClasses ++ syntheticCoreMethods ++ ScalaValueClasses() - ++ (JavaEnumClass :: (if ctx.settings.Ycc.value then captureRoot :: Nil else Nil)) + ++ (JavaEnumClass :: (if Feature.ccEnabled then captureRoot :: Nil else Nil)) isInitialized = true } diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 47636e49e4fa..539ef31c8da5 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -8,6 +8,7 @@ import scala.io.Codec import Int.MaxValue import Names._, StdNames._, Contexts._, Symbols._, Flags._, NameKinds._, Types._ import util.Chars.{isOperatorPart, digit2int} +import config.Feature import Decorators.* import Definitions._ import nme._ @@ -208,14 +209,14 @@ object NameOps { if str == mustHave then found = true idx + str.length else idx - val start = if ctx.settings.Ycc.value then skip(0, "Impure") else 0 + val start = if Feature.pureFunsEnabled then skip(0, "Impure") else 0 skip(skip(start, "Erased"), "Context") == suffixStart && found } /** Same as `funArity`, except that it returns -1 if the prefix * is not one of a (possibly empty) concatenation of a subset of - * "Impure" (only under -Ycc), "Erased" and "Context" (in that order). + * "Impure" (only under pureFunctions), "Erased" and "Context" (in that order). */ private def checkedFunArity(suffixStart: Int)(using Context): Int = if isFunctionPrefix(suffixStart) then funArity(suffixStart) else -1 diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3bc779e25535..3243bb242a56 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4238,7 +4238,7 @@ object Types { final val Unknown: DependencyStatus = 0 // not yet computed final val NoDeps: DependencyStatus = 1 // no dependent parameters found final val FalseDeps: DependencyStatus = 2 // all dependent parameters are prefixes of non-depended alias types - final val CaptureDeps: DependencyStatus = 3 // dependencies in capture sets under -Ycc, otherwise only false dependencoes + final val CaptureDeps: DependencyStatus = 3 // dependencies in capture sets under captureChecking, otherwise only false dependencoes final val TrueDeps: DependencyStatus = 4 // some truly dependent parameters exist final val StatusMask: DependencyStatus = 7 // the bits indicating actual dependency status final val Provisional: DependencyStatus = 8 // set if dependency status can still change due to type variable instantiations diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index f5a1a5826bfb..6f27a1e24397 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -32,7 +32,7 @@ import ast.{Trees, tpd, untpd} import Trees._ import Decorators._ import transform.SymUtils._ -import cc.adaptFunctionTypeUnderCC +import cc.adaptFunctionTypeUnderPureFuns import dotty.tools.tasty.{TastyBuffer, TastyReader} import TastyBuffer._ @@ -87,8 +87,8 @@ class TreeUnpickler(reader: TastyReader, /** The root owner tree. See `OwnerTree` class definition. Set by `enterTopLevel`. */ private var ownerTree: OwnerTree = _ - /** Was unpickled class compiled with -Ycc? */ - private var wasCaptureChecked: Boolean = false + /** Was unpickled class compiled with pureFunctions? */ + private var knowsPureFuns: Boolean = false private def registerSym(addr: Addr, sym: Symbol) = symAtAddr(addr) = sym @@ -489,11 +489,11 @@ class TreeUnpickler(reader: TastyReader, def readTermRef()(using Context): TermRef = readType().asInstanceOf[TermRef] - /** Under -Ycc, map all function types to impure function types, - * unless the unpickled class was also compiled with -Ycc. + /** Under pureFunctions, map all function types to impure function types, + * unless the unpickled class was also compiled with pureFunctions. */ private def postProcessFunction(tp: Type)(using Context): Type = - if wasCaptureChecked then tp else tp.adaptFunctionTypeUnderCC + if knowsPureFuns then tp else tp.adaptFunctionTypeUnderPureFuns // ------ Reading definitions ----------------------------------------------------- @@ -642,8 +642,8 @@ class TreeUnpickler(reader: TastyReader, } registerSym(start, sym) if (isClass) { - if sym.owner.is(Package) && annots.exists(_.hasSymbol(defn.CaptureCheckedAnnot)) then - wasCaptureChecked = true + if sym.owner.is(Package) && annots.exists(_.hasSymbol(defn.WithPureFunsAnnot)) then + knowsPureFuns = true sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(using localContext(sym)) } diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index b5f95bb8b19c..b865908e6c2e 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -33,7 +33,7 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.annotation.switch import reporting._ -import cc.adaptFunctionTypeUnderCC +import cc.adaptFunctionTypeUnderPureFuns object Scala2Unpickler { @@ -826,7 +826,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas } else if args.nonEmpty then tycon.safeAppliedTo(EtaExpandIfHK(sym.typeParams, args.map(translateTempPoly))) - .adaptFunctionTypeUnderCC + .adaptFunctionTypeUnderPureFuns else if (sym.typeParams.nonEmpty) tycon.EtaExpand(sym.typeParams) else tycon case TYPEBOUNDStpe => diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index ab8f578b9ac4..1c4938a320ec 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -196,7 +196,7 @@ object Parsers { def isIdent = in.isIdent def isIdent(name: Name) = in.isIdent(name) - def isPureArrow(name: Name): Boolean = ctx.settings.Ycc.value && isIdent(name) + def isPureArrow(name: Name): Boolean = Feature.pureFunsEnabled && isIdent(name) def isPureArrow: Boolean = isPureArrow(nme.PUREARROW) || isPureArrow(nme.PURECTXARROW) def isErased = isIdent(nme.erased) && in.erasedEnabled def isSimpleLiteral = @@ -968,11 +968,11 @@ object Parsers { isArrowIndent() else false - /** Under -Ycc: is the following token sequuence a capture set `{ref1, ..., refN}` - * followed by a token that can start a type? + /** Under captureChecking language import: is the following token sequence a + * capture set `{ref1, ..., refN}` followed by a token that can start a type? */ def followingIsCaptureSet(): Boolean = - ctx.settings.Ycc.value && { + Feature.ccEnabled && { val lookahead = in.LookaheadScanner() def followingIsTypeStart() = lookahead.nextToken() @@ -1446,7 +1446,7 @@ object Parsers { def captureRef(): Tree = if in.token == THIS then simpleRef() else termIdent() - /** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under -Ycc + /** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under captureChecking */ def captureSet(): List[Tree] = inBraces { if in.token == RBRACE then Nil else commaSeparated(captureRef) @@ -1457,12 +1457,12 @@ object Parsers { * | FunParamClause ‘=>>’ Type * | MatchType * | InfixType - * | CaptureSet Type -- under -Ycc + * | CaptureSet Type -- under captureChecking * FunType ::= (MonoFunType | PolyFunType) * MonoFunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type - * | (‘->’ | ‘?->’ ) Type -- under -Ycc + * | (‘->’ | ‘?->’ ) Type -- under pureFunctions * PolyFunType ::= HKTypeParamClause '=>' Type - * | HKTypeParamClause ‘->’ Type -- under -Ycc + * | HKTypeParamClause ‘->’ Type -- under pureFunctions * FunTypeArgs ::= InfixType * | `(' [ [ ‘[using]’ ‘['erased'] FunArgType {`,' FunArgType } ] `)' * | '(' [ ‘[using]’ ‘['erased'] TypedFunParam {',' TypedFunParam } ')' @@ -1482,8 +1482,9 @@ object Parsers { if !imods.flags.isEmpty || params.isEmpty then syntaxError(em"illegal parameter list for type lambda", start) token = ARROW - else if ctx.settings.Ycc.value then - // `=>` means impure function under -Ycc whereas `->` is a regular function. + else if Feature.pureFunsEnabled then + // `=>` means impure function under pureFunctions or captureChecking + // language imports, whereas `->` is then a regular function. imods |= Impure if token == CTXARROW then @@ -1887,7 +1888,7 @@ object Parsers { if in.token == ARROW || isPureArrow(nme.PUREARROW) then val isImpure = in.token == ARROW val tp = atSpan(in.skipToken()) { ByNameTypeTree(core()) } - if isImpure && ctx.settings.Ycc.value then ImpureByNameTypeTree(tp) else tp + if isImpure && Feature.pureFunsEnabled then ImpureByNameTypeTree(tp) else tp else if in.token == LBRACE && followingIsCaptureSet() then val start = in.offset val cs = captureSet() diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index b428a117d6a6..698f9bb633fc 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -14,7 +14,7 @@ import Variances.varianceSign import util.SourcePosition import scala.util.control.NonFatal import scala.annotation.switch -import config.Config +import config.{Config, Feature} import cc.{CapturingType, EventuallyCapturingType, CaptureSet, isBoxed} class PlainPrinter(_ctx: Context) extends Printer { @@ -242,7 +242,7 @@ class PlainPrinter(_ctx: Context) extends Printer { else toText(CapturingType(ExprType(parent), refs)) case ExprType(restp) => changePrec(GlobalPrec) { - (if ctx.settings.Ycc.value then "-> " else "=> ") ~ toText(restp) + (if Feature.pureFunsEnabled then "-> " else "=> ") ~ toText(restp) } case tp: HKTypeLambda => changePrec(GlobalPrec) { diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 52218ac44155..27bd1affb167 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -24,7 +24,7 @@ import NameKinds.{WildcardParamName, DefaultGetterName} import util.Chars.isOperatorPart import transform.TypeUtils._ import transform.SymUtils._ -import config.Config +import config.{Config, Feature} import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} @@ -221,7 +221,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*" else if defn.isFunctionSymbol(tsym) then toTextFunction(args, tsym.name.isContextFunction, tsym.name.isErasedFunction, - isPure = ctx.settings.Ycc.value && !tsym.name.isImpureFunction) + isPure = Feature.pureFunsEnabled && !tsym.name.isImpureFunction) else if isInfixType(tp) then val l :: r :: Nil = args: @unchecked val opName = tyconName(tycon) @@ -248,7 +248,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toText(tycon) case tp: RefinedType if defn.isFunctionOrPolyType(tp) && !printDebug => toTextMethodAsFunction(tp.refinedInfo, - isPure = ctx.settings.Ycc.value && !tp.typeSymbol.name.isImpureFunction) + isPure = Feature.pureFunsEnabled && !tp.typeSymbol.name.isImpureFunction) case tp: TypeRef => if (tp.symbol.isAnonymousClass && !showUniqueIds) toText(tp.info) @@ -556,7 +556,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { (" <: " ~ toText(bound) provided !bound.isEmpty) } case ByNameTypeTree(tpt) => - (if ctx.settings.Ycc.value then "-> " else "=> ") + (if Feature.pureFunsEnabled then "-> " else "=> ") ~ toTextLocal(tpt) case TypeBoundsTree(lo, hi, alias) => if (lo eq hi) && alias.isEmpty then optText(lo)(" = " ~ _) @@ -618,7 +618,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { 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 + && Feature.ccEnabled && Config.printCaptureSetsAsPrefix && !printDebug then toTextRetainsAnnot else toTextAnnot case EmptyTree => @@ -664,7 +664,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { ~ ")" } val isPure = - ctx.settings.Ycc.value + Feature.pureFunsEnabled && tree.match case tree: FunctionWithMods => !tree.mods.is(Impure) case _ => true diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 6968eb271961..a7e0795ce195 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -17,6 +17,7 @@ import NameOps._ import NameKinds.OuterSelectName import StdNames._ import TypeUtils.isErasedValueType +import config.Feature object FirstTransform { val name: String = "firstTransform" @@ -102,7 +103,7 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => } /** Eliminate self in Template - * Under -Ycc, we keep the self type `S` around in a type definition + * Under captureChecking, we keep the self type `S` around in a type definition * * private[this] type $this = S * @@ -110,7 +111,7 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => */ override def transformTemplate(impl: Template)(using Context): Tree = impl.self match - case self: ValDef if !self.tpt.isEmpty && ctx.settings.Ycc.value => + case self: ValDef if !self.tpt.isEmpty && Feature.ccEnabled => val tsym = newSymbol(ctx.owner, tpnme.SELF, PrivateLocal, TypeAlias(self.tpt.tpe)) val tdef = untpd.cpy.TypeDef(self)(tpnme.SELF, self.tpt).withType(tsym.typeRef) cpy.Template(impl)(self = EmptyValDef, body = tdef :: impl.body) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 3db751df4145..0424b48751bc 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -14,6 +14,7 @@ import Decorators._ import Symbols._, SymUtils._, NameOps._ import ContextFunctionResults.annotateContextResults import config.Printers.typr +import config.Feature import util.SrcPos import reporting._ import NameKinds.WildcardParamName @@ -396,8 +397,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val reference = ctx.settings.sourceroot.value val relativePath = util.SourceFile.relativePath(ctx.compilationUnit.source, reference) sym.addAnnotation(Annotation.makeSourceFile(relativePath)) - if ctx.settings.Ycc.value && sym != defn.CaptureCheckedAnnot then - sym.addAnnotation(Annotation(defn.CaptureCheckedAnnot)) + if Feature.pureFunsEnabled && sym != defn.WithPureFunsAnnot then + sym.addAnnotation(Annotation(defn.WithPureFunsAnnot)) else if !sym.is(Param) && !sym.owner.isOneOf(AbstractOrTrait) then Checking.checkGoodBounds(tree.symbol) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 50cd6a8d6ae4..5707a79e3adb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1879,7 +1879,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val expr1 = typed(tree.expr, defn.ThrowableType) val cap = checkCanThrow(expr1.tpe.widen, tree.span) val res = Throw(expr1).withSpan(tree.span) - if ctx.settings.Ycc.value && !cap.isEmpty && !ctx.isAfterTyper then + if Feature.ccEnabled && !cap.isEmpty && !ctx.isAfterTyper then // Record access to the CanThrow capabulity recovered in `cap` by wrapping // the type of the `throw` (i.e. Nothing) in a `@requiresCapability` annotatoon. Typed(res, diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 8d7a16dad8a4..915e4e5f2e50 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -40,7 +40,7 @@ class CompilationTests { compileFilesInDir("tests/pos-special/isInstanceOf", allowDeepSubtypes.and("-Xfatal-warnings")), compileFilesInDir("tests/new", defaultOptions.and("-source", "3.2")), // just to see whether 3.2 works compileFilesInDir("tests/pos-scala2", scala2CompatMode), - compileFilesInDir("tests/pos-custom-args/captures", defaultOptions.and("-Ycc")), + compileFilesInDir("tests/pos-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking")), compileFilesInDir("tests/pos-custom-args/erased", defaultOptions.and("-language:experimental.erasedDefinitions")), compileFilesInDir("tests/pos", defaultOptions.and("-Ysafe-init")), compileFilesInDir("tests/pos-deep-subtype", allowDeepSubtypes), @@ -140,7 +140,7 @@ class CompilationTests { compileFilesInDir("tests/neg-custom-args/allow-double-bindings", allowDoubleBindings), compileFilesInDir("tests/neg-custom-args/allow-deep-subtypes", allowDeepSubtypes), compileFilesInDir("tests/neg-custom-args/no-experimental", defaultOptions.and("-Yno-experimental")), - compileFilesInDir("tests/neg-custom-args/captures", defaultOptions.and("-Ycc")), + compileFilesInDir("tests/neg-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking")), compileDir("tests/neg-custom-args/impl-conv", defaultOptions.and("-Xfatal-warnings", "-feature")), compileDir("tests/neg-custom-args/i13946", defaultOptions.and("-Xfatal-warnings", "-feature")), compileFile("tests/neg-custom-args/avoid-warn-deprecation.scala", defaultOptions.and("-Xfatal-warnings", "-feature")), @@ -185,7 +185,7 @@ class CompilationTests { compileFile("tests/neg-custom-args/deptypes.scala", defaultOptions.and("-language:experimental.dependent")), compileFile("tests/neg-custom-args/matchable.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")), compileFile("tests/neg-custom-args/i7314.scala", defaultOptions.and("-Xfatal-warnings", "-source", "future")), - compileFile("tests/neg-custom-args/capt-wf.scala", defaultOptions.and("-Ycc", "-Xfatal-warnings")), + compileFile("tests/neg-custom-args/capt-wf.scala", defaultOptions.and("-language:experimental.captureChecking", "-Xfatal-warnings")), compileFile("tests/neg-custom-args/feature-shadowing.scala", defaultOptions.and("-Xfatal-warnings", "-feature")), compileDir("tests/neg-custom-args/hidden-type-errors", defaultOptions.and("-explain")), compileFile("tests/neg-custom-args/i13026.scala", defaultOptions.and("-print-lines")), @@ -213,7 +213,7 @@ class CompilationTests { compileFilesInDir("tests/run-custom-args/fatal-warnings", defaultOptions.and("-Xfatal-warnings")), compileDir("tests/run-custom-args/Xmacro-settings/simple", defaultOptions.and("-Xmacro-settings:one,two,three")), compileDir("tests/run-custom-args/Xmacro-settings/compileTimeEnv", defaultOptions.and("-Xmacro-settings:a,b=1,c.b.a=x.y.z=1,myLogger.level=INFO")), - compileFilesInDir("tests/run-custom-args/captures", allowDeepSubtypes.and("-Ycc")), + compileFilesInDir("tests/run-custom-args/captures", allowDeepSubtypes.and("-language:experimental.captureChecking")), compileFilesInDir("tests/run-deep-subtype", allowDeepSubtypes), compileFilesInDir("tests/run", defaultOptions.and("-Ysafe-init")) ).checkRuns() diff --git a/docs/_docs/reference/experimental/cc.md b/docs/_docs/reference/experimental/cc.md index c6aa795cc09b..d5aa8f600d16 100644 --- a/docs/_docs/reference/experimental/cc.md +++ b/docs/_docs/reference/experimental/cc.md @@ -3,7 +3,10 @@ layout: doc-page title: "Capture Checking" --- -Capture checking is a research project that modifies the Scala type system to track references to capabilities in values. It can be enabled with a `-Ycc` compiler option. +Capture checking is a research project that modifies the Scala type system to track references to capabilities in values. It can be enabled by the language import +```scala +import language.experimental.captureChecking +``` At present, capture checking is still highly experimental and unstable. To get an idea what capture checking can do, let's start with a small example: @@ -77,10 +80,6 @@ The following sections explain in detail how capture checking works in Scala 3. The capture checker extension introduces a new kind of types and it enforces some rules for working with these types. -Capture checking is enabled by the compiler option `-Ycc`. If the option is not given, the new -type forms can still be written but they are not checked for consistency, because they are -treated simply as certain uninterpreted annotated types. - ## Capabilities and Capturing Types Capture checking is done in terms of _capturing types_ of the form @@ -128,7 +127,8 @@ any capturing type that adds a capture set to `T`. ## Function Types The usual function type `A => B` now stands for a function that can capture arbitrary capabilities. We call such functions -_impure_. By contrast, the new single arrow function type `A -> B` stands for a function that cannot capture any capabilities, or otherwise said, is _pure_. One can add a capture set in front of an otherwise pure function. +_impure_. By contrast, the new single arrow function type `A -> B` stands for a function that cannot capture any capabilities, or otherwise said, is _pure_. +One can add a capture set in front of an otherwise pure function. For instance, `{c, d} A -> B` would be a function that can capture capabilities `c` and `d`, but no others. The impure function type `A => B` is treated as an alias for `{*} A -> B`. That is, impure functions are functions that can capture anything. @@ -502,7 +502,7 @@ crasher() This code needs to be rejected since otherwise the call to `crasher()` would cause an unhandled `LimitExceeded` exception to be thrown. -Under `-Ycc`, the code is indeed rejected +Under the language import `language.experimental.captureChecking`, the code is indeed rejected ``` 14 | try () => xs.map(f).sum | ^ @@ -654,7 +654,6 @@ TBD The following options are relevant for capture checking. - - **-Ycc** Enables capture checking. - **-Xprint:cc** Prints the program with capturing types as inferred by capture checking. - **-Ycc-debug** Gives more detailed, implementation-oriented information about capture checking, as described in the next section. diff --git a/docs/_docs/reference/experimental/purefuns.md b/docs/_docs/reference/experimental/purefuns.md new file mode 100644 index 000000000000..7c369f85f010 --- /dev/null +++ b/docs/_docs/reference/experimental/purefuns.md @@ -0,0 +1,32 @@ +--- +layout: doc-page +title: "Pure Function Syntax" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/purefuns.html +--- + +Pure functions are an experimental feature that can be enabled by the language import +```scala +import language.experimental.pureFunctions +``` +Under that import the syntax `A -> B` is available with the intention that it should denote a pure, side effect-free function from `A` to `B`. Some other variants are also supported: +```scala + (A1, ..., An) -> B // a multi-argument pure function + (x1: A1, ..., xn: An) -> B // a dependent pure function + A ?-> B // a pure context function + (A1, ..., An) ?-> B // a multi-argument pure context function + (x1: A1, ..., xn: An) ?-> B // a dependent pure context function + -> B // a pure call-by-name parameter +``` +A function's purity can be checked by capture tracking, another experimental language feature which is presently in a very early stage. Until that second feature matures, the pure function syntax should be understood to be for documentation only. A pure function type is a requirement that all its instances should be side effect-free. This requirement currently needs to be checked manually, but checking might be automated in the future. + +## Why Enable It Now? + +There are at least three reasons why one might want to enable `pureFunctions` today: + + - to get better documentation since it makes the intent clear, + - to prepare the code base for a time when full effect checking is implemented, + - to have a common code base that can be compiled with or without capture checking enabled. + +## More info: + +TBD \ No newline at end of file diff --git a/docs/sidebar.yml b/docs/sidebar.yml index dfcc36cccf1b..e959e31e87de 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -152,6 +152,7 @@ subsection: - page: reference/experimental/explicit-nulls.md - page: reference/experimental/main-annotation.md - page: reference/experimental/cc.md + - page: reference/experimental/purefuns.md - page: reference/experimental/tupled-function.md - page: reference/syntax.md - title: Language Versions diff --git a/library/src/scala/annotation/internal/CaptureChecked.scala b/library/src/scala/annotation/internal/WithPureFuns.scala similarity index 52% rename from library/src/scala/annotation/internal/CaptureChecked.scala rename to library/src/scala/annotation/internal/WithPureFuns.scala index 8392189f11f7..f0fc45c7f584 100644 --- a/library/src/scala/annotation/internal/CaptureChecked.scala +++ b/library/src/scala/annotation/internal/WithPureFuns.scala @@ -3,7 +3,7 @@ package internal import annotation.experimental /** A marker annotation on a toplevel class that indicates - * that the class was checked under -Ycc + * that the class was typed with the pureFunctions language import. */ -@experimental class CaptureChecked extends StaticAnnotation +@experimental class WithPureFuns extends StaticAnnotation diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 2be4861b4cc2..ad773da10b2d 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -60,6 +60,17 @@ object language: @compileTimeOnly("`saferExceptions` can only be used at compile time in import statements") object saferExceptions + /** Experimental support for pure function type syntax + * + * @see [[https://dotty.epfl.ch/docs/reference/experimental/purefuns]] + */ + object pureFunctions + + /** Experimental support for capture checking; implies support for pureFunctions + * + * @see [[https://dotty.epfl.ch/docs/reference/experimental/cc]] + */ + object captureChecking end experimental /** The deprecated object contains features that are no longer officially suypported in Scala. diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index 22232ea55f69..33e45c9f29ed 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -48,7 +48,7 @@ val experimentalDefinitionInLibrary = Set( //// New feature: capture checking "scala.annotation.capability", - "scala.annotation.internal.CaptureChecked", + "scala.annotation.internal.WithPureFuns", "scala.annotation.internal.requiresCapability", "scala.annotation.retains", "scala.annotation.retainsUniversal", From f2c1b24fe02567459285ff33ff11fcf199f931cb Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 24 Oct 2022 13:35:12 +0200 Subject: [PATCH 2/7] Allow pureFunctions and captureChecking only at the toplevel --- .../src/dotty/tools/dotc/config/Feature.scala | 2 + .../dotty/tools/dotc/parsing/Parsers.scala | 38 ++++++++++--------- tests/neg/language-imports.scala | 6 +++ 3 files changed, 28 insertions(+), 18 deletions(-) create mode 100644 tests/neg/language-imports.scala diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index cc7d4ab0511e..d5629d41cd59 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -31,6 +31,8 @@ object Feature: val pureFunctions = experimental("pureFunctions") val captureChecking = experimental("captureChecking") + val globalOnlyImports: Set[TermName] = Set(pureFunctions, captureChecking) + /** Is `feature` enabled by by a command-line setting? The enabling setting is * * -language:feature diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 1c4938a320ec..4b95b81eb82b 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -15,7 +15,7 @@ import core._ import Flags._ import Contexts._ import Names._ -import NameKinds.WildcardParamName +import NameKinds.{WildcardParamName, QualifiedName} import NameOps._ import ast.{Positioned, Trees} import ast.Trees._ @@ -30,7 +30,7 @@ import scala.annotation.tailrec import rewrites.Rewrites.{patch, overlapsPatch} import reporting._ import config.Feature -import config.Feature.{sourceVersion, migrateTo3} +import config.Feature.{sourceVersion, migrateTo3, globalOnlyImports} import config.SourceVersion._ import config.SourceVersion @@ -3307,23 +3307,25 @@ object Parsers { in.languageImportContext = in.languageImportContext.importContext(imp, NoSymbol) for case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors - if allSourceVersionNames.contains(imported) do - if !outermost then - syntaxError(i"source version import is only allowed at the toplevel", id.span) - else if ctx.compilationUnit.sourceVersion.isDefined then - syntaxError(i"duplicate source version import", id.span) - else if illegalSourceVersionNames.contains(imported) then - val candidate = - val nonMigration = imported.toString.replace("-migration", "") - validSourceVersionNames.find(_.show == nonMigration) - val baseMsg = i"`$imported` is not a valid source version" - val msg = candidate match - case Some(member) => i"$baseMsg, did you mean language.`$member`?" - case _ => baseMsg - syntaxError(msg, id.span) - else - ctx.compilationUnit.sourceVersion = Some(SourceVersion.valueOf(imported.toString)) + if globalOnlyImports.contains(QualifiedName(prefix, imported.asTermName)) && !outermost then + syntaxError(i"this language import is only allowed at the toplevel", id.span) + if allSourceVersionNames.contains(imported) && prefix.isEmpty then + if !outermost then + syntaxError(i"source version import is only allowed at the toplevel", id.span) + else if ctx.compilationUnit.sourceVersion.isDefined then + syntaxError(i"duplicate source version import", id.span) + else if illegalSourceVersionNames.contains(imported) then + val candidate = + val nonMigration = imported.toString.replace("-migration", "") + validSourceVersionNames.find(_.show == nonMigration) + val baseMsg = i"`$imported` is not a valid source version" + val msg = candidate match + case Some(member) => i"$baseMsg, did you mean language.`$member`?" + case _ => baseMsg + syntaxError(msg, id.span) + else + ctx.compilationUnit.sourceVersion = Some(SourceVersion.valueOf(imported.toString)) case None => imp diff --git a/tests/neg/language-imports.scala b/tests/neg/language-imports.scala new file mode 100644 index 000000000000..82c54cd4bf57 --- /dev/null +++ b/tests/neg/language-imports.scala @@ -0,0 +1,6 @@ +def test = + import language.experimental.captureChecking // error + import language.experimental.pureFunctions // error + 1 + + From 88853fe1004caf8db671c29ed9aca8be3fa44197 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 24 Oct 2022 18:20:04 +0200 Subject: [PATCH 3/7] Update Mimafilters --- project/MiMaFilters.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 3d5541df4041..12ae3487cade 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -4,6 +4,10 @@ import com.typesafe.tools.mima.core._ object MiMaFilters { val Library: Seq[ProblemFilter] = Seq( ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.MappedAlternative"), + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.pureFunctions"), + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.captureChecking"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$pureFunctions$"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$captureChecking$"), ProblemFilters.exclude[MissingClassProblem]("scala.caps"), ) } From 8d729ad9c126f9f2835436bcac43e81617066c3e Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 24 Oct 2022 18:21:26 +0200 Subject: [PATCH 4/7] Make captureRoot an element of caps This avoids the problem that we do not want to make `*` visible under the scala package, even if pureFunctions or captureChecking is enabled. --- compiler/src/dotty/tools/dotc/ast/TreeInfo.scala | 4 ++-- compiler/src/dotty/tools/dotc/ast/untpd.scala | 3 +++ compiler/src/dotty/tools/dotc/core/Definitions.scala | 7 +++---- compiler/src/dotty/tools/dotc/core/StdNames.scala | 1 + compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 7 +++++-- .../src/dotty/tools/dotc/printing/PlainPrinter.scala | 1 + compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- library/src/scala/caps.scala | 3 +++ .../disabled/neg-custom-args/captures/capt-wf.scala | 4 ++-- tests/disabled/neg-custom-args/captures/try2.scala | 2 +- tests/neg-custom-args/captures/capt-depfun.scala | 2 +- tests/neg-custom-args/captures/capt-depfun2.scala | 2 +- tests/neg-custom-args/captures/capt-test.scala | 4 ++-- tests/neg-custom-args/captures/capt1.scala | 12 ++++++------ tests/neg-custom-args/captures/capt3.scala | 2 +- tests/neg-custom-args/captures/cc1.scala | 2 +- tests/neg-custom-args/captures/io.scala | 8 ++++---- tests/neg-custom-args/captures/try.scala | 4 ++-- tests/pos-custom-args/captures/boxmap.scala | 2 +- tests/pos-custom-args/captures/capt-depfun.scala | 4 ++-- tests/pos-custom-args/captures/capt-depfun2.scala | 2 +- tests/pos-custom-args/captures/capt0.scala | 2 +- tests/pos-custom-args/captures/capt2.scala | 2 +- tests/pos-custom-args/captures/cc-expand.scala | 2 +- tests/pos-custom-args/captures/try.scala | 2 +- 25 files changed, 48 insertions(+), 38 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index c0130715a1a0..ff59a795d818 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -408,10 +408,10 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] def apply(tp: ByNameTypeTree)(using Context): untpd.CapturingTypeTree = untpd.CapturingTypeTree( - Ident(nme.CAPTURE_ROOT).withSpan(tp.span.startPos) :: Nil, tp) + untpd.captureRoot.withSpan(tp.span.startPos) :: Nil, tp) def unapply(tp: Tree)(using Context): Option[ByNameTypeTree] = tp match - case untpd.CapturingTypeTree(id @ Ident(nme.CAPTURE_ROOT) :: Nil, bntp: ByNameTypeTree) + case untpd.CapturingTypeTree(id @ Select(_, nme.CAPTURE_ROOT) :: Nil, bntp: ByNameTypeTree) if id.span == bntp.span.startPos => Some(bntp) case _ => None end ImpureByNameTypeTree diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index da9f938cb1a6..9827d93d5054 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -492,6 +492,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def scalaAny(implicit src: SourceFile): Select = scalaDot(tpnme.Any) def javaDotLangDot(name: Name)(implicit src: SourceFile): Select = Select(Select(Ident(nme.java), nme.lang), name) + def captureRoot(using Context): Select = + Select(scalaDot(nme.caps), nme.CAPTURE_ROOT) + def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef = DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 3016c35603f3..4eda749f8e7b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -470,7 +470,6 @@ class Definitions { @tu lazy val andType: TypeSymbol = enterBinaryAlias(tpnme.AND, AndType(_, _)) @tu lazy val orType: TypeSymbol = enterBinaryAlias(tpnme.OR, OrType(_, _, soft = false)) - @tu lazy val captureRoot: TermSymbol = enterPermanentSymbol(nme.CAPTURE_ROOT, AnyType).asTerm /** Method representing a throw */ @tu lazy val throwMethod: TermSymbol = enterMethod(OpsPackageClass, nme.THROWkw, @@ -964,6 +963,7 @@ class Definitions { @tu lazy val CapsModule: Symbol = requiredModule("scala.caps") @tu lazy val Caps_unsafeBox: Symbol = CapsModule.requiredMethod("unsafeBox") @tu lazy val Caps_unsafeUnbox: Symbol = CapsModule.requiredMethod("unsafeUnbox") + @tu lazy val captureRoot: TermSymbol = CapsModule.requiredValue("*") // Annotation base classes @tu lazy val AnnotationClass: ClassSymbol = requiredClass("scala.annotation.Annotation") @@ -1984,9 +1984,8 @@ class Definitions { this.initCtx = ctx if (!isInitialized) { // force initialization of every symbol that is synthesized or hijacked by the compiler - val forced = syntheticCoreClasses ++ syntheticCoreMethods ++ ScalaValueClasses() - ++ (JavaEnumClass :: (if Feature.ccEnabled then captureRoot :: Nil else Nil)) - + val forced = + syntheticCoreClasses ++ syntheticCoreMethods ++ ScalaValueClasses() :+ JavaEnumClass isInitialized = true } addSyntheticSymbolsComments diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index a38331b71b05..6379a38013d9 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -425,6 +425,7 @@ object StdNames { val bytes: N = "bytes" val canEqual_ : N = "canEqual" val canEqualAny : N = "canEqualAny" + val caps: N = "caps" val checkInitialized: N = "checkInitialized" val classOf: N = "classOf" val classType: N = "classType" diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 4b95b81eb82b..6f9216d45f72 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -159,7 +159,7 @@ object Parsers { syntaxError(msg.toMessage, span) def unimplementedExpr(using Context): Select = - Select(Select(rootDot(nme.scala), nme.Predef), nme.???) + Select(scalaDot(nme.Predef), nme.???) } trait OutlineParserCommon extends ParserCommon { @@ -1444,7 +1444,10 @@ object Parsers { /** CaptureRef ::= ident | `this` */ def captureRef(): Tree = - if in.token == THIS then simpleRef() else termIdent() + if in.token == THIS then simpleRef() + else termIdent() match + case Ident(nme.CAPTURE_ROOT) => captureRoot + case id => id /** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under captureChecking */ diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 698f9bb633fc..0921ca9150c6 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -376,6 +376,7 @@ class PlainPrinter(_ctx: Context) extends Printer { def toTextCaptureRef(tp: Type): Text = homogenize(tp) match + case tp: TermRef if tp.symbol == defn.captureRoot => Str("*") case tp: SingletonType => toTextRef(tp) case _ => toText(tp) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5707a79e3adb..e61d03d5de21 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2682,7 +2682,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val arg1 = typed(tree.arg, pt) if (ctx.mode is Mode.Type) { val cls = annot1.symbol.maybeOwner - if ctx.settings.Ycc.value + if Feature.ccEnabled && (cls == defn.RetainsAnnot || cls == defn.RetainsByNameAnnot) then CheckCaptures.checkWellformed(annot1) diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala index 886cfbcfb057..21b2f7a4dece 100644 --- a/library/src/scala/caps.scala +++ b/library/src/scala/caps.scala @@ -4,6 +4,9 @@ import annotation.experimental @experimental object caps: + /** The universal capture reference */ + val `*`: Any = () + /** If argument is of type `cs T`, converts to type `box cs T`. This * avoids the error that would be raised when boxing `*`. */ diff --git a/tests/disabled/neg-custom-args/captures/capt-wf.scala b/tests/disabled/neg-custom-args/captures/capt-wf.scala index accbda0903e9..bfe349747776 100644 --- a/tests/disabled/neg-custom-args/captures/capt-wf.scala +++ b/tests/disabled/neg-custom-args/captures/capt-wf.scala @@ -1,7 +1,7 @@ // No longer valid class C -type Cap = C @retains(*) -type Top = Any @retains(*) +type Cap = C @retains(caps.*) +type Top = Any @retains(caps.*) type T = (x: Cap) => List[String @retains(x)] => Unit // error val x: (x: Cap) => Array[String @retains(x)] = ??? // error diff --git a/tests/disabled/neg-custom-args/captures/try2.scala b/tests/disabled/neg-custom-args/captures/try2.scala index dd3cc890a197..876dc1ec12f1 100644 --- a/tests/disabled/neg-custom-args/captures/try2.scala +++ b/tests/disabled/neg-custom-args/captures/try2.scala @@ -5,7 +5,7 @@ import annotation.ability @ability erased val canThrow: * = ??? class CanThrow[E <: Exception] extends Retains[canThrow.type] -type Top = Any @retains(*) +type Top = Any @retains(caps.*) infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R diff --git a/tests/neg-custom-args/captures/capt-depfun.scala b/tests/neg-custom-args/captures/capt-depfun.scala index 14f08f569725..a74764f432c7 100644 --- a/tests/neg-custom-args/captures/capt-depfun.scala +++ b/tests/neg-custom-args/captures/capt-depfun.scala @@ -1,6 +1,6 @@ import annotation.retains class C -type Cap = C @retains(*) +type Cap = C @retains(caps.*) def f(y: Cap, z: Cap) = def g(): C @retains(y, z) = ??? diff --git a/tests/neg-custom-args/captures/capt-depfun2.scala b/tests/neg-custom-args/captures/capt-depfun2.scala index 62c2381e01ad..74b9441593c1 100644 --- a/tests/neg-custom-args/captures/capt-depfun2.scala +++ b/tests/neg-custom-args/captures/capt-depfun2.scala @@ -1,6 +1,6 @@ import annotation.retains class C -type Cap = C @retains(*) +type Cap = C @retains(caps.*) def f(y: Cap, z: Cap) = def g(): C @retains(y, z) = ??? diff --git a/tests/neg-custom-args/captures/capt-test.scala b/tests/neg-custom-args/captures/capt-test.scala index 7080d6da67c6..1799fc5073ca 100644 --- a/tests/neg-custom-args/captures/capt-test.scala +++ b/tests/neg-custom-args/captures/capt-test.scala @@ -2,8 +2,8 @@ import annotation.retains import language.experimental.erasedDefinitions class CT[E <: Exception] -type CanThrow[E <: Exception] = CT[E] @retains(*) -type Top = Any @retains(*) +type CanThrow[E <: Exception] = CT[E] @retains(caps.*) +type Top = Any @retains(caps.*) infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R diff --git a/tests/neg-custom-args/captures/capt1.scala b/tests/neg-custom-args/captures/capt1.scala index ce69e77057e0..59ba874b02f5 100644 --- a/tests/neg-custom-args/captures/capt1.scala +++ b/tests/neg-custom-args/captures/capt1.scala @@ -1,21 +1,21 @@ import annotation.retains class C -def f(x: C @retains(*), y: C): () -> C = +def f(x: C @retains(caps.*), y: C): () -> C = () => if x == null then y else y // error -def g(x: C @retains(*), y: C): Matchable = +def g(x: C @retains(caps.*), y: C): Matchable = () => if x == null then y else y // error -def h1(x: C @retains(*), y: C): Any = +def h1(x: C @retains(caps.*), y: C): Any = def f() = if x == null then y else y () => f() // ok -def h2(x: C @retains(*)): Matchable = +def h2(x: C @retains(caps.*)): Matchable = def f(y: Int) = if x == null then y else y // error f class A -type Cap = C @retains(*) +type Cap = C @retains(caps.*) def h3(x: Cap): A = class F(y: Int) extends A: // error @@ -27,7 +27,7 @@ def h4(x: Cap, y: Int): A = def m() = if x == null then y else y def foo() = - val x: C @retains(*) = ??? + val x: C @retains(caps.*) = ??? def h[X](a: X)(b: X) = a val z2 = h[() -> Cap](() => x) // error (() => C()) // error diff --git a/tests/neg-custom-args/captures/capt3.scala b/tests/neg-custom-args/captures/capt3.scala index 4ffaf4a73c06..84164d433029 100644 --- a/tests/neg-custom-args/captures/capt3.scala +++ b/tests/neg-custom-args/captures/capt3.scala @@ -1,6 +1,6 @@ import annotation.retains class C -type Cap = C @retains(*) +type Cap = C @retains(caps.*) def test1() = val x: Cap = C() diff --git a/tests/neg-custom-args/captures/cc1.scala b/tests/neg-custom-args/captures/cc1.scala index 7f3cd784ef84..10a9793eabe8 100644 --- a/tests/neg-custom-args/captures/cc1.scala +++ b/tests/neg-custom-args/captures/cc1.scala @@ -1,5 +1,5 @@ import annotation.retains object Test: - def f[A <: Matchable @retains(*)](x: A): Matchable = x // error + def f[A <: Matchable @retains(caps.*)](x: A): Matchable = x // error diff --git a/tests/neg-custom-args/captures/io.scala b/tests/neg-custom-args/captures/io.scala index 91af0167c9f9..ae686d6b154e 100644 --- a/tests/neg-custom-args/captures/io.scala +++ b/tests/neg-custom-args/captures/io.scala @@ -3,17 +3,17 @@ sealed trait IO: def puts(msg: Any): Unit = println(msg) def test1 = - val IO : IO @retains(*) = new IO {} + val IO : IO @retains(caps.*) = new IO {} def foo = {IO; IO.puts("hello") } val x : () -> Unit = () => foo // error: Found: (() -> Unit) retains IO; Required: () -> Unit def test2 = - val IO : IO @retains(*) = new IO {} - def puts(msg: Any, io: IO @retains(*)) = println(msg) + val IO : IO @retains(caps.*) = new IO {} + def puts(msg: Any, io: IO @retains(caps.*)) = println(msg) def foo() = puts("hello", IO) val x : () -> Unit = () => foo() // error: Found: (() -> Unit) retains IO; Required: () -> Unit -type Capability[T] = T @retains(*) +type Capability[T] = T @retains(caps.*) def test3 = val IO : Capability[IO] = new IO {} diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index 35c7ea4829f2..df7930f76af8 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -2,8 +2,8 @@ import annotation.retains import language.experimental.erasedDefinitions class CT[E <: Exception] -type CanThrow[E <: Exception] = CT[E] @retains(*) -type Top = Any @retains(*) +type CanThrow[E <: Exception] = CT[E] @retains(caps.*) +type Top = Any @retains(caps.*) infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R diff --git a/tests/pos-custom-args/captures/boxmap.scala b/tests/pos-custom-args/captures/boxmap.scala index 7bd58df80235..18baabd4e584 100644 --- a/tests/pos-custom-args/captures/boxmap.scala +++ b/tests/pos-custom-args/captures/boxmap.scala @@ -1,5 +1,5 @@ import annotation.retains -type Top = Any @retains(*) +type Top = Any @retains(caps.*) type Box[+T <: Top] = ([K <: Top] -> (T => K) -> K) diff --git a/tests/pos-custom-args/captures/capt-depfun.scala b/tests/pos-custom-args/captures/capt-depfun.scala index 861f4a0d1c14..0e9786b2ee34 100644 --- a/tests/pos-custom-args/captures/capt-depfun.scala +++ b/tests/pos-custom-args/captures/capt-depfun.scala @@ -1,6 +1,6 @@ import annotation.retains class C -type Cap = C @retains(*) +type Cap = C @retains(caps.*) type T = (x: Cap) -> String @retains(x) @@ -8,7 +8,7 @@ type ID[X] = X val aa: ((x: Cap) -> String @retains(x)) = (x: Cap) => "" -def f(y: Cap, z: Cap): String @retains(*) = +def f(y: Cap, z: Cap): String @retains(caps.*) = val a: ((x: Cap) -> String @retains(x)) = (x: Cap) => "" val b = a(y) val c: String @retains(y) = b diff --git a/tests/pos-custom-args/captures/capt-depfun2.scala b/tests/pos-custom-args/captures/capt-depfun2.scala index 837d143d5141..1c747d5885e6 100644 --- a/tests/pos-custom-args/captures/capt-depfun2.scala +++ b/tests/pos-custom-args/captures/capt-depfun2.scala @@ -1,6 +1,6 @@ import annotation.retains class C -type Cap = C @retains(*) +type Cap = C @retains(caps.*) def f(y: Cap, z: Cap) = def g(): C @retains(y, z) = ??? diff --git a/tests/pos-custom-args/captures/capt0.scala b/tests/pos-custom-args/captures/capt0.scala index 2544e8abe5f1..52d6253af46b 100644 --- a/tests/pos-custom-args/captures/capt0.scala +++ b/tests/pos-custom-args/captures/capt0.scala @@ -3,5 +3,5 @@ object Test: def test() = val x: {*} Any = "abc" val y: Object @scala.annotation.retains(x) = ??? - val z: Object @scala.annotation.retains(x, *) = y: Object @annotation.retains(x) + val z: Object @scala.annotation.retains(x, caps.*) = y: Object @annotation.retains(x) diff --git a/tests/pos-custom-args/captures/capt2.scala b/tests/pos-custom-args/captures/capt2.scala index 204310d21ddf..77c0caaf0f1d 100644 --- a/tests/pos-custom-args/captures/capt2.scala +++ b/tests/pos-custom-args/captures/capt2.scala @@ -1,6 +1,6 @@ import annotation.retains class C -type Cap = C @retains(*) +type Cap = C @retains(caps.*) def test1() = val y: {*} String = "" diff --git a/tests/pos-custom-args/captures/cc-expand.scala b/tests/pos-custom-args/captures/cc-expand.scala index eba97f182385..87b2c34caf5f 100644 --- a/tests/pos-custom-args/captures/cc-expand.scala +++ b/tests/pos-custom-args/captures/cc-expand.scala @@ -5,7 +5,7 @@ object Test: class B class C class CTC - type CT = CTC @retains(*) + type CT = CTC @retains(caps.*) def test(ct: CT, dt: CT) = diff --git a/tests/pos-custom-args/captures/try.scala b/tests/pos-custom-args/captures/try.scala index dbc952cad3c0..b2dcf6f11dd0 100644 --- a/tests/pos-custom-args/captures/try.scala +++ b/tests/pos-custom-args/captures/try.scala @@ -2,7 +2,7 @@ import annotation.retains import language.experimental.erasedDefinitions class CT[E <: Exception] -type CanThrow[E <: Exception] = CT[E] @retains(*) +type CanThrow[E <: Exception] = CT[E] @retains(caps.*) infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?-> R From 010633bcdc4b6ad541e230a25d3e8ee47e1f4642 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 25 Oct 2022 12:30:35 +0200 Subject: [PATCH 5/7] Complete the language import functionality Complete the functionality of allowing language imports for pureFunctions and captureChecking: - make parser work with them - make printer to recognize them --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- compiler/src/dotty/tools/dotc/core/Mode.scala | 2 +- .../src/dotty/tools/dotc/core/NameOps.scala | 3 +- .../dotty/tools/dotc/parsing/Parsers.scala | 8 ++-- .../dotty/tools/dotc/parsing/Scanners.scala | 9 +++++ .../tools/dotc/printing/RefinedPrinter.scala | 16 ++++++-- .../test/dotc/pos-test-pickling.blacklist | 3 ++ .../runtime/stdLibPatches/language.scala | 2 + tests/neg/cc-only-defs.scala | 2 +- .../captures/caps-universal.scala | 4 +- tests/pos/boxmap-paper.scala | 38 +++++++++++++++++++ tests/pos/caps-universal.scala | 10 +++++ 12 files changed, 84 insertions(+), 15 deletions(-) create mode 100644 tests/pos/boxmap-paper.scala create mode 100644 tests/pos/caps-universal.scala diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 9827d93d5054..ec3eb4f05b79 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -145,7 +145,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case Floating } - /** {x1, ..., xN} T (only relevant under -Ycc) */ + /** {x1, ..., xN} T (only relevant under captureChecking) */ case class CapturingTypeTree(refs: List[Tree], parent: Tree)(implicit @constructorOnly src: SourceFile) extends TypTree /** Short-lived usage in typer, does not need copy/transform/fold infrastructure */ diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 0996321ddbd6..33ac3de70767 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -78,7 +78,7 @@ object Mode { /** Use Scala2 scheme for overloading and implicit resolution */ val OldOverloadingResolution: Mode = newMode(15, "OldOverloadingResolution") - /** Treat CapturingTypes as plain AnnotatedTypes even in phase =Ycc. + /** Treat CapturingTypes as plain AnnotatedTypes even in phase CheckCaptures. * Reuses the value of OldOverloadingResolution to save Mode bits. * This is OK since OldOverloadingResolution only affects implicit search, which * is done during phases Typer and Inlinig, and IgnoreCaptures only has an diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 539ef31c8da5..25712a98f24c 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -209,8 +209,7 @@ object NameOps { if str == mustHave then found = true idx + str.length else idx - val start = if Feature.pureFunsEnabled then skip(0, "Impure") else 0 - skip(skip(start, "Erased"), "Context") == suffixStart + skip(skip(skip(0, "Impure"), "Erased"), "Context") == suffixStart && found } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 6f9216d45f72..70b7f389a193 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -196,7 +196,7 @@ object Parsers { def isIdent = in.isIdent def isIdent(name: Name) = in.isIdent(name) - def isPureArrow(name: Name): Boolean = Feature.pureFunsEnabled && isIdent(name) + def isPureArrow(name: Name): Boolean = in.pureFunsEnabled && isIdent(name) def isPureArrow: Boolean = isPureArrow(nme.PUREARROW) || isPureArrow(nme.PURECTXARROW) def isErased = isIdent(nme.erased) && in.erasedEnabled def isSimpleLiteral = @@ -972,7 +972,7 @@ object Parsers { * capture set `{ref1, ..., refN}` followed by a token that can start a type? */ def followingIsCaptureSet(): Boolean = - Feature.ccEnabled && { + in.featureEnabled(Feature.captureChecking) && { val lookahead = in.LookaheadScanner() def followingIsTypeStart() = lookahead.nextToken() @@ -1485,7 +1485,7 @@ object Parsers { if !imods.flags.isEmpty || params.isEmpty then syntaxError(em"illegal parameter list for type lambda", start) token = ARROW - else if Feature.pureFunsEnabled then + else if in.pureFunsEnabled then // `=>` means impure function under pureFunctions or captureChecking // language imports, whereas `->` is then a regular function. imods |= Impure @@ -1891,7 +1891,7 @@ object Parsers { if in.token == ARROW || isPureArrow(nme.PUREARROW) then val isImpure = in.token == ARROW val tp = atSpan(in.skipToken()) { ByNameTypeTree(core()) } - if isImpure && Feature.pureFunsEnabled then ImpureByNameTypeTree(tp) else tp + if isImpure && in.pureFunsEnabled then ImpureByNameTypeTree(tp) else tp else if in.token == LBRACE && followingIsCaptureSet() then val start = in.offset val cs = captureSet() diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index a4eff045b4ac..e1fcc70994de 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -230,6 +230,15 @@ object Scanners { postfixOpsEnabledCtx = myLanguageImportContext postfixOpsEnabledCache + private var pureFunsEnabledCache = false + private var pureFunsEnabledCtx: Context = NoContext + + def pureFunsEnabled = + if pureFunsEnabledCtx ne myLanguageImportContext then + pureFunsEnabledCache = featureEnabled(Feature.pureFunctions) || featureEnabled(Feature.captureChecking) + pureFunsEnabledCtx = myLanguageImportContext + pureFunsEnabledCache + /** All doc comments kept by their end position in a `Map`. * * Note: the map is necessary since the comments are looked up after an diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 27bd1affb167..2a87ec9b4bbe 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -58,6 +58,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { try op finally myCtx = savedCtx } + inline def inContextBracket(inline op: Text): Text = + val savedCtx = myCtx + try op finally myCtx = savedCtx + def withoutPos(op: => Text): Text = { val savedPrintPos = printPos printPos = false @@ -308,7 +312,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { blockText(block.stats :+ block.expr) protected def blockText[T >: Untyped](trees: List[Tree[T]]): Text = - ("{" ~ toText(trees, "\n") ~ "}").close + inContextBracket { + ("{" ~ toText(trees, "\n") ~ "}").close + } protected def typeApplyText[T >: Untyped](tree: TypeApply[T]): Text = { val funText = toTextLocal(tree.fun) @@ -598,7 +604,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { typeDefText(tparamsTxt, optText(rhs)(" = " ~ _)) } recur(rhs, "", true) - case Import(expr, selectors) => + case tree @ Import(expr, selectors) => + myCtx = myCtx.importContext(tree, tree.symbol) keywordText("import ") ~ importText(expr, selectors) case Export(expr, selectors) => keywordText("export ") ~ importText(expr, selectors) @@ -965,7 +972,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } else impl.body - val bodyText = " {" ~~ selfText ~ toTextGlobal(primaryConstrs ::: body, "\n") ~ "}" + val bodyText = inContextBracket( + " {" ~~ selfText ~ toTextGlobal(primaryConstrs ::: body, "\n") ~ "}") prefix ~ keywordText(" extends").provided(!ofNew && impl.parents.nonEmpty) ~~ parentsText ~ @@ -988,7 +996,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def packageDefText(tree: PackageDef): Text = { val statsText = tree.stats match { case (pdef: PackageDef) :: Nil => toText(pdef) - case _ => toTextGlobal(tree.stats, "\n") + case _ => inContextBracket(toTextGlobal(tree.stats, "\n")) } val bodyText = if (currentPrecedence == TopLevelPrec) "\n" ~ statsText else " {" ~ statsText ~ "}" diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 7abbbe02e857..92c2777eecab 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -78,6 +78,9 @@ i2797a # allows to simplify a type that was already computed i13842.scala +# Position change under captureChecking +boxmap-paper.scala + # GADT cast applied to singleton type difference i4176-gadt.scala diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index ad773da10b2d..5c01f66ffd46 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -64,12 +64,14 @@ object language: * * @see [[https://dotty.epfl.ch/docs/reference/experimental/purefuns]] */ + @compileTimeOnly("`pureFunctions` can only be used at compile time in import statements") object pureFunctions /** Experimental support for capture checking; implies support for pureFunctions * * @see [[https://dotty.epfl.ch/docs/reference/experimental/cc]] */ + @compileTimeOnly("`captureChecking` can only be used at compile time in import statements") object captureChecking end experimental diff --git a/tests/neg/cc-only-defs.scala b/tests/neg/cc-only-defs.scala index a9b480f9f590..43ac025f203a 100644 --- a/tests/neg/cc-only-defs.scala +++ b/tests/neg/cc-only-defs.scala @@ -5,7 +5,7 @@ trait Test { val z: *.type // error - val b: ImpureFunction1[Int, Int] // error + val b: ImpureFunction1[Int, Int] // now OK val a: {z} String // error } // error diff --git a/tests/pos-custom-args/captures/caps-universal.scala b/tests/pos-custom-args/captures/caps-universal.scala index 44b902d82197..d84f2b7b2584 100644 --- a/tests/pos-custom-args/captures/caps-universal.scala +++ b/tests/pos-custom-args/captures/caps-universal.scala @@ -1,7 +1,7 @@ -import annotation.retainsUniversal +import annotation.retains val foo: Int => Int = x => x -val bar: (Int -> Int) @retainsUniversal = foo +val bar: (Int -> Int) @retains(caps.*) = foo val baz: {*} Int -> Int = bar diff --git a/tests/pos/boxmap-paper.scala b/tests/pos/boxmap-paper.scala new file mode 100644 index 000000000000..eb6e5f48d81c --- /dev/null +++ b/tests/pos/boxmap-paper.scala @@ -0,0 +1,38 @@ +import language.experimental.captureChecking + +type Cell[+T] = [K] -> (T => K) -> K + +def cell[T](x: T): Cell[T] = + [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] + = c[Cell[B]]((x: A) => cell(f(x))) + +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] + = () => c[Cell[B]]((x: A) => cell(f(x))) + +trait IO: + def print(s: String): Unit + +def test(io: {*} IO) = + + val loggedOne: {io} () -> Int = () => { io.print("1"); 1 } + + val c: Cell[{io} () -> Int] + = cell[{io} () -> Int](loggedOne) + + 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 r3 = lazyMap(c)(g) + val _ = r() + val _ = r2() + val _ = r3() diff --git a/tests/pos/caps-universal.scala b/tests/pos/caps-universal.scala new file mode 100644 index 000000000000..014955caaa87 --- /dev/null +++ b/tests/pos/caps-universal.scala @@ -0,0 +1,10 @@ +import language.experimental.pureFunctions +import annotation.retains + +val id: Int -> Int = (x: Int) => x +val foo: Int => Int = id +val bar: (Int -> Int) @retains(caps.*) = foo + + + + From 49dc1f457482074f78b42e8a2e070ab88eb2ffdc Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 25 Oct 2022 16:49:51 +0200 Subject: [PATCH 6/7] Drop retainsUniversal annotation It can now be expressed as `@retains(caps.*)`. --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 1 - compiler/src/dotty/tools/dotc/typer/Typer.scala | 8 +------- library/src/scala/annotation/retains.scala | 3 --- .../tasty-inspector/stdlibExperimentalDefinitions.scala | 1 - 4 files changed, 1 insertion(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 4eda749f8e7b..b88e7c88d449 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1025,7 +1025,6 @@ class Definitions { @tu lazy val RequiresCapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.internal.requiresCapability") @tu lazy val RetainsAnnot: ClassSymbol = requiredClass("scala.annotation.retains") @tu lazy val RetainsByNameAnnot: ClassSymbol = requiredClass("scala.annotation.retainsByName") - @tu lazy val RetainsUniversalAnnot: ClassSymbol = requiredClass("scala.annotation.retainsUniversal") @tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable") diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e61d03d5de21..d1c55c603968 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2669,16 +2669,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer end typedPackageDef def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = { - var annot1 = typedExpr(tree.annot, defn.AnnotationClass.typeRef) + val annot1 = typedExpr(tree.annot, defn.AnnotationClass.typeRef) val annotCls = Annotations.annotClass(annot1) if annotCls == defn.NowarnAnnot then registerNowarn(annot1, tree) - else if annotCls == defn.RetainsUniversalAnnot then - annot1 = typedExpr( - untpd.New( - untpd.TypeTree(defn.RetainsAnnot.typeRef), - (untpd.ref(defn.captureRoot) :: Nil) :: Nil).withSpan(tree.annot.span), - defn.AnnotationClass.typeRef) val arg1 = typed(tree.arg, pt) if (ctx.mode is Mode.Type) { val cls = annot1.symbol.maybeOwner diff --git a/library/src/scala/annotation/retains.scala b/library/src/scala/annotation/retains.scala index 0e4e35a14b97..0387840ea8bd 100644 --- a/library/src/scala/annotation/retains.scala +++ b/library/src/scala/annotation/retains.scala @@ -13,6 +13,3 @@ package scala.annotation */ @experimental class retains(xs: Any*) extends annotation.StaticAnnotation - -@experimental -class retainsUniversal extends annotation.StaticAnnotation diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index 33e45c9f29ed..d6c08f78ee85 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -51,7 +51,6 @@ val experimentalDefinitionInLibrary = Set( "scala.annotation.internal.WithPureFuns", "scala.annotation.internal.requiresCapability", "scala.annotation.retains", - "scala.annotation.retainsUniversal", "scala.annotation.retainsByName", "scala.caps", "scala.caps$", From fc5c0ab068fbea14df31975886c51296101332ad Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 25 Oct 2022 17:07:43 +0200 Subject: [PATCH 7/7] Drop unused import --- compiler/src/dotty/tools/dotc/core/NameOps.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 25712a98f24c..7c1073852681 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -8,7 +8,6 @@ import scala.io.Codec import Int.MaxValue import Names._, StdNames._, Contexts._, Symbols._, Flags._, NameKinds._, Types._ import util.Chars.{isOperatorPart, digit2int} -import config.Feature import Decorators.* import Definitions._ import nme._