diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala index 0fd96fe2462c..fd89159e2076 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala @@ -39,8 +39,7 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte override def symbol(using Context) = cls - override def derivedAnnotation(tree: Tree)(using Context): Annotation = - unsupported(i"derivedAnnotation(Tree), $tree, $refs") + override def derivedAnnotation(tree: Tree)(using Context): Annotation = this def derivedAnnotation(refs: CaptureSet, boxed: Boolean)(using Context): Annotation = if (this.refs eq refs) && (this.boxed == boxed) then this diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index f0c613cb2e70..69b0567b30c5 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -96,6 +96,19 @@ extension (tp: Type) /** Is the boxedCaptureSet of this type nonempty? */ def isBoxedCapturing(using Context) = !tp.boxedCaptureSet.isAlwaysEmpty + /** If this type is a capturing type, the version with boxed statues as given by `boxed`. + * If it is a TermRef of a capturing type, and the box status flips, widen to a capturing + * type that captures the TermRef. + */ + def forceBoxStatus(boxed: Boolean)(using Context): Type = tp.widenDealias match + case tp @ CapturingType(parent, refs) if tp.isBoxed != boxed => + val refs1 = tp match + case ref: CaptureRef if ref.isTracked => ref.singletonCaptureSet + case _ => refs + CapturingType(parent, refs1, boxed) + case _ => + tp + /** Map capturing type to their parents. Capturing types accessible * via dealising are also stripped. */ @@ -155,6 +168,8 @@ extension (sym: Symbol) case _ => false containsEnclTypeParam(sym.info.finalResultType) && !sym.allowsRootCapture + && sym != defn.Caps_unsafeBox + && sym != defn.Caps_unsafeUnbox extension (tp: AnnotatedType) /** Is this a boxed capturing type? */ diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index 05e813793a63..e9862f1f20b8 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -41,7 +41,10 @@ object CapturingType: * returned separately by CaptureOps.isBoxed. */ def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet)] = - if ctx.phase == Phases.checkCapturesPhase && tp.annot.symbol == defn.RetainsAnnot then + if ctx.phase == Phases.checkCapturesPhase + && tp.annot.symbol == defn.RetainsAnnot + && !ctx.mode.is(Mode.IgnoreCaptures) + then EventuallyCapturingType.unapply(tp) else None diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 1ecf42a90aaa..851da2c965a3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -5,7 +5,7 @@ package cc import core.* import Phases.*, DenotTransformers.*, SymDenotations.* import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.* -import Types.*, StdNames.* +import Types.*, StdNames.*, Denotations.* import config.Printers.{capt, recheckr} import config.Config import ast.{tpd, untpd, Trees} @@ -89,7 +89,7 @@ object CheckCaptures: elem.tpe match case ref: CaptureRef => if !ref.canBeTracked then - report.error(em"$elem cannot be tracked since it is not a parameter or a local variable", elem.srcPos) + report.error(em"$elem cannot be tracked since it is not a parameter or local value", elem.srcPos) case tpe => report.error(em"$elem: $tpe is not a legal element of a capture set", elem.srcPos) @@ -288,16 +288,34 @@ class CheckCaptures extends Recheck, SymTransformer: * outcome of a `mightSubcapture` test. It picks `{f}` if this might subcapture Cr * and Cr otherwise. */ - override def recheckSelection(tree: Select, qualType: Type, name: Name)(using Context) = { - val selType = super.recheckSelection(tree, qualType, name) + override def recheckSelection(tree: Select, qualType: Type, name: Name, pt: Type)(using Context) = { + def disambiguate(denot: Denotation): Denotation = denot match + case MultiDenotation(denot1, denot2) => + // This case can arise when we try to merge multiple types that have different + // capture sets on some part. For instance an asSeenFrom might produce + // a bi-mapped capture set arising from a substition. Applying the same substitution + // to the same type twice will nevertheless produce different capture setsw which can + // lead to a failure in disambiguation since neither alternative is better than the + // other in a frozen constraint. An example test case is disambiguate-select.scala. + // We address the problem by disambiguating while ignoring all capture sets as a fallback. + withMode(Mode.IgnoreCaptures) { + disambiguate(denot1).meet(disambiguate(denot2), qualType) + } + case _ => denot + + val selType = recheckSelection(tree, qualType, name, disambiguate) val selCs = selType.widen.captureSet if selCs.isAlwaysEmpty || selType.widen.isBoxedCapturing || qualType.isBoxedCapturing then selType else val qualCs = qualType.captureSet capt.println(i"intersect $qualType, ${selType.widen}, $qualCs, $selCs in $tree") - if qualCs.mightSubcapture(selCs) then + if qualCs.mightSubcapture(selCs) + && !selCs.mightSubcapture(qualCs) + && !pt.stripCapturing.isInstanceOf[SingletonType] + then selType.widen.stripCapturing.capturing(qualCs) + .showing(i"alternate type for select $tree: $selType --> $result, $qualCs / $selCs", capt) else selType }//.showing(i"recheck sel $tree, $qualType = $result") @@ -314,23 +332,32 @@ class CheckCaptures extends Recheck, SymTransformer: * and Cr otherwise. */ override def recheckApply(tree: Apply, pt: Type)(using Context): Type = - includeCallCaptures(tree.symbol, tree.srcPos) - super.recheckApply(tree, pt) match - case appType @ CapturingType(appType1, refs) => - tree.fun match - case Select(qual, _) - if !tree.fun.symbol.isConstructor - && !qual.tpe.isBoxedCapturing - && !tree.args.exists(_.tpe.isBoxedCapturing) - && qual.tpe.captureSet.mightSubcapture(refs) - && tree.args.forall(_.tpe.captureSet.mightSubcapture(refs)) - => - val callCaptures = tree.args.foldLeft(qual.tpe.captureSet)((cs, arg) => - cs ++ arg.tpe.captureSet) - appType.derivedCapturingType(appType1, callCaptures) - .showing(i"narrow $tree: $appType, refs = $refs, qual = ${qual.tpe.captureSet} --> $result", capt) - case _ => appType - case appType => appType + val meth = tree.fun.symbol + includeCallCaptures(meth, tree.srcPos) + if meth == defn.Caps_unsafeBox || meth == defn.Caps_unsafeUnbox then + val arg :: Nil = tree.args: @unchecked + val argType0 = recheckStart(arg, pt) + .forceBoxStatus(boxed = meth == defn.Caps_unsafeBox) + val argType = super.recheckFinish(argType0, arg, pt) + super.recheckFinish(argType, tree, pt) + else + super.recheckApply(tree, pt) match + case appType @ CapturingType(appType1, refs) => + tree.fun match + case Select(qual, _) + if !tree.fun.symbol.isConstructor + && !qual.tpe.isBoxedCapturing + && !tree.args.exists(_.tpe.isBoxedCapturing) + && qual.tpe.captureSet.mightSubcapture(refs) + && tree.args.forall(_.tpe.captureSet.mightSubcapture(refs)) + => + val callCaptures = tree.args.foldLeft(qual.tpe.captureSet)((cs, arg) => + cs ++ arg.tpe.captureSet) + appType.derivedCapturingType(appType1, callCaptures) + .showing(i"narrow $tree: $appType, refs = $refs, qual = ${qual.tpe.captureSet} --> $result", capt) + case _ => appType + case appType => appType + end recheckApply /** Handle an application of method `sym` with type `mt` to arguments of types `argTypes`. * This means: @@ -435,10 +462,25 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => super.recheckBlock(block, pt) + /** If `rhsProto` has `*` as its capture set, wrap `rhs` in a `unsafeBox`. + * Used to infer `unsafeBox` for expressions that get assigned to variables + * that have universal capture set. + */ + def maybeBox(rhs: Tree, rhsProto: Type)(using Context): Tree = + if rhsProto.captureSet.isUniversal then + ref(defn.Caps_unsafeBox).appliedToType(rhsProto).appliedTo(rhs) + else rhs + + override def recheckAssign(tree: Assign)(using Context): Type = + val rhsProto = recheck(tree.lhs).widen + recheck(maybeBox(tree.rhs, rhsProto), rhsProto) + defn.UnitType + override def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Unit = try if !sym.is(Module) then // Modules are checked by checking the module class - super.recheckValDef(tree, sym) + if sym.is(Mutable) then recheck(maybeBox(tree.rhs, sym.info), sym.info) + else super.recheckValDef(tree, sym) finally if !sym.is(Param) then // Parameters with inferred types belong to anonymous methods. We need to wait @@ -535,8 +577,6 @@ class CheckCaptures extends Recheck, SymTransformer: tpe case _: Try => tpe - case _: ValDef if tree.symbol.is(Mutable) => - tree.symbol.info case _ => NoType def checkNotUniversal(tp: Type): Unit = tp.widenDealias match @@ -665,7 +705,7 @@ class CheckCaptures extends Recheck, SymTransformer: /** Destruct a capturing type `tp` to a tuple (cs, tp0, boxed), * where `tp0` is not a capturing type. - * + * * If `tp` is a nested capturing type, the return tuple always represents * the innermost capturing type. The outer capture annotations can be * reconstructed with the returned function. @@ -721,7 +761,7 @@ class CheckCaptures extends Recheck, SymTransformer: val criticalSet = // the set which is not allowed to have `*` if covariant then cs1 // can't box with `*` else expected.captureSet // can't unbox with `*` - if criticalSet.isUniversal then + if criticalSet.isUniversal && expected.isValueType then // We can't box/unbox the universal capability. Leave `actual` as it is // so we get an error in checkConforms. This tends to give better error // messages than disallowing the root capability in `criticalSet`. @@ -742,7 +782,6 @@ class CheckCaptures extends Recheck, SymTransformer: recon(CapturingType(parent1, cs1, actualIsBoxed)) } - var actualw = actual.widenDealias actual match case ref: CaptureRef if ref.isTracked => @@ -762,6 +801,7 @@ class CheckCaptures extends Recheck, SymTransformer: override def checkUnit(unit: CompilationUnit)(using Context): Unit = Setup(preRecheckPhase, thisPhase, recheckDef) .traverse(ctx.compilationUnit.tpdTree) + //println(i"SETUP:\n${Recheck.addRecheckedTypes.transform(ctx.compilationUnit.tpdTree)}") withCaptureSetsExplained { super.checkUnit(unit) checkSelfTypes(unit.tpdTree) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index a3e88699e424..42c80e524a6e 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -331,11 +331,12 @@ extends tpd.TreeTraverser: else expandAbbreviations(tp1) /** Transform type of type tree, and remember the transformed type as the type the tree */ - private def transformTT(tree: TypeTree, boxed: Boolean)(using Context): Unit = - tree.rememberType( - if tree.isInstanceOf[InferredTypeTree] - then transformInferredType(tree.tpe, boxed) - else transformExplicitType(tree.tpe, boxed)) + private def transformTT(tree: TypeTree, boxed: Boolean, exact: Boolean)(using Context): Unit = + if !tree.hasRememberedType then + tree.rememberType( + if tree.isInstanceOf[InferredTypeTree] && !exact + then transformInferredType(tree.tpe, boxed) + else transformExplicitType(tree.tpe, boxed)) /** Substitute parameter symbols in `from` to paramRefs in corresponding * method or poly types `to`. We use a single BiTypeMap to do everything. @@ -376,20 +377,34 @@ extends tpd.TreeTraverser: def traverse(tree: Tree)(using Context): Unit = tree match - case tree: DefDef if isExcluded(tree.symbol) => - return - case tree @ ValDef(_, tpt: TypeTree, _) if tree.symbol.is(Mutable) => - transformTT(tpt, boxed = true) // types of mutable variables are boxed - traverse(tree.rhs) + case tree: DefDef => + if isExcluded(tree.symbol) then + return + tree.tpt match + case tpt: TypeTree if tree.symbol.allOverriddenSymbols.hasNext => + transformTT(tpt, boxed = false, exact = true) + //println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}") + case _ => + traverseChildren(tree) + case tree @ ValDef(_, tpt: TypeTree, _) => + val isVar = tree.symbol.is(Mutable) + val overrides = tree.symbol.allOverriddenSymbols.hasNext + //if overrides then println(i"transforming overriding ${tree.symbol}") + if isVar || overrides then + transformTT(tpt, + boxed = isVar, // types of mutable variables are boxed + exact = overrides // types of symbols that override a parent don't get a capture set + ) + traverseChildren(tree) case tree @ TypeApply(fn, args) => traverse(fn) for case arg: TypeTree <- args do - transformTT(arg, boxed = true) // type arguments in type applications are boxed + transformTT(arg, boxed = true, exact = false) // type arguments in type applications are boxed case _ => traverseChildren(tree) tree match case tree: TypeTree => - transformTT(tree, boxed = false) // other types are not boxed + transformTT(tree, boxed = false, exact = false) // other types are not boxed case tree: ValOrDefDef => val sym = tree.symbol diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index afbcfe556c58..fce3241bcc40 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -943,23 +943,27 @@ class Definitions { @tu lazy val RuntimeTuplesModule: Symbol = requiredModule("scala.runtime.Tuples") @tu lazy val RuntimeTuplesModuleClass: Symbol = RuntimeTuplesModule.moduleClass - lazy val RuntimeTuples_consIterator: Symbol = RuntimeTuplesModule.requiredMethod("consIterator") - lazy val RuntimeTuples_concatIterator: Symbol = RuntimeTuplesModule.requiredMethod("concatIterator") - lazy val RuntimeTuples_apply: Symbol = RuntimeTuplesModule.requiredMethod("apply") - lazy val RuntimeTuples_cons: Symbol = RuntimeTuplesModule.requiredMethod("cons") - lazy val RuntimeTuples_size: Symbol = RuntimeTuplesModule.requiredMethod("size") - lazy val RuntimeTuples_tail: Symbol = RuntimeTuplesModule.requiredMethod("tail") - lazy val RuntimeTuples_concat: Symbol = RuntimeTuplesModule.requiredMethod("concat") - lazy val RuntimeTuples_toArray: Symbol = RuntimeTuplesModule.requiredMethod("toArray") - lazy val RuntimeTuples_productToArray: Symbol = RuntimeTuplesModule.requiredMethod("productToArray") - lazy val RuntimeTuples_isInstanceOfTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfTuple") - lazy val RuntimeTuples_isInstanceOfEmptyTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfEmptyTuple") - lazy val RuntimeTuples_isInstanceOfNonEmptyTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfNonEmptyTuple") + @tu lazy val RuntimeTuples_consIterator: Symbol = RuntimeTuplesModule.requiredMethod("consIterator") + @tu lazy val RuntimeTuples_concatIterator: Symbol = RuntimeTuplesModule.requiredMethod("concatIterator") + @tu lazy val RuntimeTuples_apply: Symbol = RuntimeTuplesModule.requiredMethod("apply") + @tu lazy val RuntimeTuples_cons: Symbol = RuntimeTuplesModule.requiredMethod("cons") + @tu lazy val RuntimeTuples_size: Symbol = RuntimeTuplesModule.requiredMethod("size") + @tu lazy val RuntimeTuples_tail: Symbol = RuntimeTuplesModule.requiredMethod("tail") + @tu lazy val RuntimeTuples_concat: Symbol = RuntimeTuplesModule.requiredMethod("concat") + @tu lazy val RuntimeTuples_toArray: Symbol = RuntimeTuplesModule.requiredMethod("toArray") + @tu lazy val RuntimeTuples_productToArray: Symbol = RuntimeTuplesModule.requiredMethod("productToArray") + @tu lazy val RuntimeTuples_isInstanceOfTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfTuple") + @tu lazy val RuntimeTuples_isInstanceOfEmptyTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfEmptyTuple") + @tu lazy val RuntimeTuples_isInstanceOfNonEmptyTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfNonEmptyTuple") @tu lazy val TupledFunctionTypeRef: TypeRef = requiredClassRef("scala.util.TupledFunction") def TupledFunctionClass(using Context): ClassSymbol = TupledFunctionTypeRef.symbol.asClass def RuntimeTupleFunctionsModule(using Context): Symbol = requiredModule("scala.runtime.TupledFunctions") + @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") + // Annotation base classes @tu lazy val AnnotationClass: ClassSymbol = requiredClass("scala.annotation.Annotation") @tu lazy val ClassfileAnnotationClass: ClassSymbol = requiredClass("scala.annotation.ClassfileAnnotation") @@ -1020,6 +1024,7 @@ 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/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index d141cf7032ee..0996321ddbd6 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -78,6 +78,14 @@ 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. + * 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 + * effect during phase CheckCaptures. + */ + val IgnoreCaptures = OldOverloadingResolution + /** Allow hk applications of type lambdas to wildcard arguments; * used for checking that such applications do not normally arise */ diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index dc1f02c8a0fd..3bc779e25535 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1872,7 +1872,10 @@ object Types { def dropRepeatedAnnot(using Context): Type = dropAnnot(defn.RepeatedAnnot) def annotatedToRepeated(using Context): Type = this match { - case tp @ ExprType(tp1) => tp.derivedExprType(tp1.annotatedToRepeated) + case tp @ ExprType(tp1) => + tp.derivedExprType(tp1.annotatedToRepeated) + case self @ AnnotatedType(tp, annot) if annot matches defn.RetainsByNameAnnot => + self.derivedAnnotatedType(tp.annotatedToRepeated, annot) case AnnotatedType(tp, annot) if annot matches defn.RepeatedAnnot => val typeSym = tp.typeSymbol.asClass assert(typeSym == defn.SeqClass || typeSym == defn.ArrayClass) @@ -2787,7 +2790,7 @@ object Types { ((prefix eq NoPrefix) || symbol.is(ParamAccessor) && (prefix eq symbol.owner.thisType) || isRootCapability - ) && !symbol.is(Method) + ) && !symbol.isOneOf(UnstableValueFlags) override def isRootCapability(using Context): Boolean = name == nme.CAPTURE_ROOT && symbol == defn.captureRoot diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 619bfafeb775..52218ac44155 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -272,6 +272,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case tp: LazyRef if !printDebug => try toText(tp.ref) catch case ex: Throwable => "..." + case AnySelectionProto => + "a type that can be selected or applied" case tp: SelectionProto => "?{ " ~ toText(tp.name) ~ (Str(" ") provided !tp.name.toSimpleName.last.isLetterOrDigit) ~ diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 36044e6bcb91..6d783854ae35 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -4,7 +4,7 @@ package transform import core.* import Symbols.*, Contexts.*, Types.*, ContextOps.*, Decorators.*, SymDenotations.* -import Flags.*, SymUtils.*, NameKinds.* +import Flags.*, SymUtils.*, NameKinds.*, Denotations.Denotation import ast.* import Names.Name import Phases.Phase @@ -21,9 +21,10 @@ import util.Property import StdNames.nme import reporting.trace import annotation.constructorOnly +import cc.CaptureSet.IdempotentCaptRefMap object Recheck: - import tpd.Tree + import tpd.* /** A flag used to indicate that a ParamAccessor has been temporarily made not-private * Only used at the start of the Recheck phase, reset at its end. @@ -36,6 +37,13 @@ object Recheck: /** Attachment key for rechecked types of TypeTrees */ val RecheckedType = Property.Key[Type] + val addRecheckedTypes = new TreeMap: + override def transform(tree: Tree)(using Context): Tree = + val tree1 = super.transform(tree) + tree.getAttachment(RecheckedType) match + case Some(tpe) => tree1.withType(tpe) + case None => tree1 + extension (sym: Symbol) /** Update symbol's info to newInfo from prevPhase.next to lastPhase. @@ -129,7 +137,7 @@ abstract class Recheck extends Phase, SymTransformer: def keepType(tree: Tree): Boolean = keepAllTypes /** Constant-folded rechecked type `tp` of tree `tree` */ - private def constFold(tree: Tree, tp: Type)(using Context): Type = + protected def constFold(tree: Tree, tp: Type)(using Context): Type = val tree1 = tree.withType(tp) val tree2 = ConstFold(tree1) if tree2 ne tree1 then tree2.tpe else tp @@ -137,21 +145,27 @@ abstract class Recheck extends Phase, SymTransformer: def recheckIdent(tree: Ident)(using Context): Type = tree.tpe - def recheckSelect(tree: Select)(using Context): Type = + def recheckSelect(tree: Select, pt: Type)(using Context): Type = val Select(qual, name) = tree - recheckSelection(tree, recheck(qual).widenIfUnstable, name) + recheckSelection(tree, recheck(qual, AnySelectionProto).widenIfUnstable, name, pt) - /** Keep the symbol of the `select` but re-infer its type */ - def recheckSelection(tree: Select, qualType: Type, name: Name)(using Context) = + def recheckSelection(tree: Select, qualType: Type, name: Name, + sharpen: Denotation => Denotation)(using Context): Type = if name.is(OuterSelectName) then tree.tpe else //val pre = ta.maybeSkolemizePrefix(qualType, name) - val mbr = qualType.findMember(name, qualType, - excluded = if tree.symbol.is(Private) then EmptyFlags else Private - ).suchThat(tree.symbol == _) + val mbr = sharpen( + qualType.findMember(name, qualType, + excluded = if tree.symbol.is(Private) then EmptyFlags else Private + )).suchThat(tree.symbol == _) constFold(tree, qualType.select(name, mbr)) //.showing(i"recheck select $qualType . $name : ${mbr.info} = $result") + + /** Keep the symbol of the `select` but re-infer its type */ + def recheckSelection(tree: Select, qualType: Type, name: Name, pt: Type)(using Context): Type = + recheckSelection(tree, qualType, name, sharpen = identity[Denotation]) + def recheckBind(tree: Bind, pt: Type)(using Context): Type = tree match case Bind(name, body) => recheck(body, pt) @@ -187,7 +201,7 @@ abstract class Recheck extends Phase, SymTransformer: * to FromJavaObject since it got lost in ElimRepeated */ private def mapJavaArgs(formals: List[Type])(using Context): List[Type] = - val tm = new TypeMap: + val tm = new TypeMap with IdempotentCaptRefMap: def apply(t: Type) = t match case t: TypeRef if t.symbol == defn.ObjectClass => defn.FromJavaObjectType case _ => mapOver(t) @@ -344,7 +358,7 @@ abstract class Recheck extends Phase, SymTransformer: val sym = tree.symbol tree match case tree: Ident => recheckIdent(tree) - case tree: Select => recheckSelect(tree) + case tree: Select => recheckSelect(tree, pt) case tree: Bind => recheckBind(tree, pt) case tree: ValOrDefDef => if tree.isEmpty then NoType @@ -444,12 +458,6 @@ abstract class Recheck extends Phase, SymTransformer: /** Show tree with rechecked types instead of the types stored in the `.tpe` field */ override def show(tree: untpd.Tree)(using Context): String = - val addRecheckedTypes = new TreeMap: - override def transform(tree: Tree)(using Context): Tree = - val tree1 = super.transform(tree) - tree.getAttachment(RecheckedType) match - case Some(tpe) => tree1.withType(tpe) - case None => tree1 atPhase(thisPhase) { super.show(addRecheckedTypes.transform(tree.asInstanceOf[tpd.Tree])) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 49a8d44134d2..50cd6a8d6ae4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2669,13 +2669,22 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer end typedPackageDef def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = { - val annot1 = typedExpr(tree.annot, defn.AnnotationClass.typeRef) - if Annotations.annotClass(annot1) == defn.NowarnAnnot then + var 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 - if cls == defn.RetainsAnnot || cls == defn.RetainsByNameAnnot then + if ctx.settings.Ycc.value + && (cls == defn.RetainsAnnot || cls == defn.RetainsByNameAnnot) + then CheckCaptures.checkWellformed(annot1) if arg1.isType then assignType(cpy.Annotated(tree)(arg1, annot1), arg1, annot1) diff --git a/library/src-bootstrapped/scala/internal/requiresCapability.scala b/library/src/scala/annotation/internal/requiresCapability.scala similarity index 100% rename from library/src-bootstrapped/scala/internal/requiresCapability.scala rename to library/src/scala/annotation/internal/requiresCapability.scala diff --git a/library/src-bootstrapped/scala/annotation/retains.scala b/library/src/scala/annotation/retains.scala similarity index 59% rename from library/src-bootstrapped/scala/annotation/retains.scala rename to library/src/scala/annotation/retains.scala index 0d0099de75fb..0a06eecde604 100644 --- a/library/src-bootstrapped/scala/annotation/retains.scala +++ b/library/src/scala/annotation/retains.scala @@ -11,5 +11,8 @@ package scala.annotation * The annotation can also be written explicitly if one wants to avoid the * non-standard capturing type syntax. */ -@experimental class retains(xs: Any*) extends annotation.StaticAnnotation +// @experimental // suppressed so we can use in compiler +class retains(xs: Any*) extends annotation.StaticAnnotation +// @experimental // suppressed so we can use in compiler +class retainsUniversal extends annotation.StaticAnnotation diff --git a/library/src-bootstrapped/scala/annotation/retainsByName.scala b/library/src/scala/annotation/retainsByName.scala similarity index 100% rename from library/src-bootstrapped/scala/annotation/retainsByName.scala rename to library/src/scala/annotation/retainsByName.scala diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala new file mode 100644 index 000000000000..c33cb6fa98df --- /dev/null +++ b/library/src/scala/caps.scala @@ -0,0 +1,16 @@ +package scala + +import annotation.experimental + +// @experimental , suppress @experimental so we can use in compiler itself +object caps: + + /** If argument is of type `cs T`, converts to type `box cs T`. This + * avoids the error that would be raised when boxing `*`. + */ + extension [T](x: T) def unsafeBox: T = x + + /** If argument is of type `box cs T`, converts to type `cs T`. This + * avoids the error that would be raised when unboxing `*`. + */ + extension [T](x: T) def unsafeUnbox: T = x diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 491ef19d70cf..ff4e1f7ebce2 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -4,5 +4,9 @@ import com.typesafe.tools.mima.core._ object MiMaFilters { val Library: Seq[ProblemFilter] = Seq( ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.MappedAlternative"), + ProblemFilters.exclude[MissingClassProblem]("scala.caps"), + ProblemFilters.exclude[MissingClassProblem]("scala.caps$"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.retains"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.retainsUniversal"), ) } diff --git a/tests/disabled/neg-custom-args/captures/capt-wf.scala b/tests/disabled/neg-custom-args/captures/capt-wf.scala index 54fe545f443b..accbda0903e9 100644 --- a/tests/disabled/neg-custom-args/captures/capt-wf.scala +++ b/tests/disabled/neg-custom-args/captures/capt-wf.scala @@ -17,3 +17,5 @@ def test: Unit = val y = f(C()) // ok val y2 = f2(C()) // ok () + var x11 = f + val x12: {x11} Any = x11 diff --git a/tests/neg-custom-args/captures/capt-wf2.scala b/tests/neg-custom-args/captures/capt-wf2.scala new file mode 100644 index 000000000000..ddde535fcab0 --- /dev/null +++ b/tests/neg-custom-args/captures/capt-wf2.scala @@ -0,0 +1,5 @@ +@annotation.capability class C + +def test(c: C) = + var x: {c} Any = ??? + val y: {x} Any = x // error diff --git a/tests/neg-custom-args/captures/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index 5ae5ec5ecc34..beb7ac23ed44 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -8,11 +8,11 @@ | ^^^^^^^^ | The expression's type box {*} () -> Unit is not allowed to capture the root capability `*`. | This usually means that a capability persists longer than its allowed lifetime. --- Error: tests/neg-custom-args/captures/usingLogFile.scala:31:6 ------------------------------------------------------- -31 | var later3: () => Unit = () => () // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | The mutable variable's type box {*} () -> Unit is not allowed to capture the root capability `*`. - | This usually means that a capability persists longer than its allowed lifetime. +-- Error: tests/neg-custom-args/captures/usingLogFile.scala:33:2 ------------------------------------------------------- +33 | later3() // error + | ^^^^^^ + | box {*} () -> Unit cannot be box-converted to a type that can be selected or applied + | since one of their capture sets contains the root capability `*` -- Error: tests/neg-custom-args/captures/usingLogFile.scala:37:9 ------------------------------------------------------- 37 | later4.x() // error | ^^^^^^^^ diff --git a/tests/neg-custom-args/captures/usingLogFile.scala b/tests/neg-custom-args/captures/usingLogFile.scala index fb35b673d46e..8b367239050d 100644 --- a/tests/neg-custom-args/captures/usingLogFile.scala +++ b/tests/neg-custom-args/captures/usingLogFile.scala @@ -28,9 +28,9 @@ object Test2: private val later2 = usingLogFile { f => Cell(() => f.write(0)) } later2.x() // error - var later3: () => Unit = () => () // error + var later3: () => Unit = () => () usingLogFile { f => later3 = () => f.write(0) } - later3() + later3() // error var later4: Cell[() => Unit] = Cell(() => ()) usingLogFile { f => later4 = Cell(() => f.write(0)) } diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index 20de642d42a7..e4f28fd45e93 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -5,21 +5,28 @@ | Required: () -> Unit | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/vars.scala:13:6 --------------------------------------------------------------- -13 | var a: String => String = f // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | The mutable variable's type box {*} String -> String is not allowed to capture the root capability `*`. - | This usually means that a capability persists longer than its allowed lifetime. --- Error: tests/neg-custom-args/captures/vars.scala:15:4 --------------------------------------------------------------- -15 | b.head // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:15:10 ----------------------------------------- +15 | val u = a // error + | ^ + | Found: (a : box {*} String -> String) + | Required: {*} (x$0: ? String) -> ? String + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/vars.scala:16:2 --------------------------------------------------------------- +16 | a("") // error + | ^ + | box {*} String -> String cannot be box-converted to a type that can be selected or applied + | since one of their capture sets contains the root capability `*` +-- Error: tests/neg-custom-args/captures/vars.scala:17:4 --------------------------------------------------------------- +17 | b.head // error | ^^^^^^ | The expression's type box {*} String -> String is not allowed to capture the root capability `*`. | This usually means that a capability persists longer than its allowed lifetime. --- Error: tests/neg-custom-args/captures/vars.scala:30:8 --------------------------------------------------------------- -30 | local { cap3 => // error +-- Error: tests/neg-custom-args/captures/vars.scala:32:8 --------------------------------------------------------------- +32 | local { cap3 => // error | ^ | The expression's type box {*} (x$0: ? String) -> ? String is not allowed to capture the root capability `*`. | This usually means that a capability persists longer than its allowed lifetime. -31 | def g(x: String): String = if cap3 == cap3 then "" else "a" -32 | g -33 | } +33 | def g(x: String): String = if cap3 == cap3 then "" else "a" +34 | g +35 | } diff --git a/tests/neg-custom-args/captures/vars.scala b/tests/neg-custom-args/captures/vars.scala index 5e413b7ea3fb..2ad8fec53619 100644 --- a/tests/neg-custom-args/captures/vars.scala +++ b/tests/neg-custom-args/captures/vars.scala @@ -10,8 +10,10 @@ def test(cap1: Cap, cap2: Cap) = val z2 = () => { x = identity } val z2c: () -> Unit = z2 // error - var a: String => String = f // error + var a: String => String = f // was error, now OK var b: List[String => String] = Nil // was error, now OK + val u = a // error + a("") // error b.head // error def scope = diff --git a/tests/pos-custom-args/captures/byname-varargs.scala b/tests/pos-custom-args/captures/byname-varargs.scala new file mode 100644 index 000000000000..219565a65d1c --- /dev/null +++ b/tests/pos-custom-args/captures/byname-varargs.scala @@ -0,0 +1,9 @@ +def typeMismatch(addenda: => String*) = ??? +class TypeMismatch(addenda: => String*) + +def test = + typeMismatch("foo") + typeMismatch("foo", "bar") + TypeMismatch("foo") + TypeMismatch("foo", "bar") + diff --git a/tests/pos-custom-args/captures/caps-universal.scala b/tests/pos-custom-args/captures/caps-universal.scala new file mode 100644 index 000000000000..44b902d82197 --- /dev/null +++ b/tests/pos-custom-args/captures/caps-universal.scala @@ -0,0 +1,7 @@ +import annotation.retainsUniversal + +val foo: Int => Int = x => x +val bar: (Int -> Int) @retainsUniversal = foo +val baz: {*} Int -> Int = bar + + diff --git a/tests/pos-custom-args/captures/disambiguate-select.scala b/tests/pos-custom-args/captures/disambiguate-select.scala new file mode 100644 index 000000000000..b8da302acfd9 --- /dev/null +++ b/tests/pos-custom-args/captures/disambiguate-select.scala @@ -0,0 +1,8 @@ +import collection.mutable +class Suppression: + def matches(f: SourceFile): Boolean = ??? +class SourceFile +private val mySuppressions: mutable.LinkedHashMap[SourceFile, mutable.ListBuffer[Suppression]] = mutable.LinkedHashMap.empty + +def test(f: SourceFile) = + mySuppressions.getOrElse(f, Nil).find(_.matches(f)) \ No newline at end of file diff --git a/tests/pos-custom-args/captures/enum-extends.scala b/tests/pos-custom-args/captures/enum-extends.scala new file mode 100644 index 000000000000..0835956ae89a --- /dev/null +++ b/tests/pos-custom-args/captures/enum-extends.scala @@ -0,0 +1,4 @@ +enum E: + + case A extends E + diff --git a/tests/pos-custom-args/captures/overrides.scala b/tests/pos-custom-args/captures/overrides.scala new file mode 100644 index 000000000000..66f19726ffa7 --- /dev/null +++ b/tests/pos-custom-args/captures/overrides.scala @@ -0,0 +1,26 @@ +import caps.* + +abstract class Foo: + def foo: () => Unit = () => () + def bar: String = "" + +class Bar extends Foo: + override def foo = () => println("bar") + override def bar = "bar" + override def toString = bar + +class Baz extends Bar: + override def foo = () => println("baz") + override def bar = "baz" + //override def toString = bar + +abstract class Message: + protected def msg: String + override def toString = msg + +abstract class SyntaxMsg extends Message + +class CyclicInheritance extends SyntaxMsg: + def msg = "cyclic" + + diff --git a/tests/pos-custom-args/captures/unsafe-unbox.scala b/tests/pos-custom-args/captures/unsafe-unbox.scala new file mode 100644 index 000000000000..e846a7db1b69 --- /dev/null +++ b/tests/pos-custom-args/captures/unsafe-unbox.scala @@ -0,0 +1,4 @@ +import caps.* +def test = + var finalizeActions = collection.mutable.ListBuffer[() => Unit]() + val action = finalizeActions.remove(0).unsafeUnbox diff --git a/tests/pos-custom-args/captures/vars1.scala b/tests/pos-custom-args/captures/vars1.scala new file mode 100644 index 000000000000..8c2f2cb8b5d5 --- /dev/null +++ b/tests/pos-custom-args/captures/vars1.scala @@ -0,0 +1,25 @@ +import caps.* + +object Test: + type ErrorHandler = (Int, String) => Unit + + var defaultIncompleteHandler: ErrorHandler = ??? + var incompleteHandler: ErrorHandler = defaultIncompleteHandler + val x = incompleteHandler.unsafeUnbox + val _ : ErrorHandler = x + val _ = x(1, "a") + + def defaultIncompleteHandler1(): ErrorHandler = ??? + val defaultIncompleteHandler2: ErrorHandler = ??? + var incompleteHandler1: ErrorHandler = defaultIncompleteHandler1() + var incompleteHandler2: ErrorHandler = defaultIncompleteHandler2 + var incompleteHandler3: ErrorHandler = defaultIncompleteHandler1().unsafeBox + var incompleteHandler4: ErrorHandler = defaultIncompleteHandler2.unsafeBox + private var incompleteHandler5 = defaultIncompleteHandler1() + private var incompleteHandler6 = defaultIncompleteHandler2 + private var incompleteHandler7 = defaultIncompleteHandler1().unsafeBox + private var incompleteHandler8 = defaultIncompleteHandler2.unsafeBox + + incompleteHandler1 = defaultIncompleteHandler2 + incompleteHandler1 = defaultIncompleteHandler2.unsafeBox + val saved = incompleteHandler1.unsafeUnbox diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index e8e2f0c08179..261c305b53d7 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -50,7 +50,7 @@ val experimentalDefinitionInLibrary = Set( "scala.annotation.capability", "scala.annotation.internal.CaptureChecked", "scala.annotation.internal.requiresCapability", - "scala.annotation.retains", + //"scala.annotation.retains", "scala.annotation.retainsByName", //// New APIs: Mirror