Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,8 @@ object Trees {
*/
class InferredTypeTree[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends TypeTree[T]

class SimplifiedTypeTree[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends TypeTree[T]

/** ref.type */
case class SingletonTypeTree[-T >: Untyped] private[ast] (ref: Tree[T])(implicit @constructorOnly src: SourceFile)
extends DenotingTree[T] with TypTree[T] {
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/config/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,6 @@ object Config {
* reduces the number of allocated denotations by ~50%.
*/
inline val reuseSymDenotations = true

inline val printCaptureSetsAsPrefix = true
}
13 changes: 12 additions & 1 deletion compiler/src/dotty/tools/dotc/core/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import reporting.trace
import printing.{Showable, Printer}
import printing.Texts.*

case class CaptureSet private (elems: CaptureSet.Refs) extends Showable:
case class CaptureSet(elems: CaptureSet.Refs) extends Showable:
import CaptureSet.*

def isEmpty: Boolean = elems.isEmpty
Expand All @@ -26,6 +26,9 @@ case class CaptureSet private (elems: CaptureSet.Refs) extends Showable:
if elems.contains(ref) then this
else CaptureSet(elems + ref)

def -- (that: CaptureSet)(using Context) =
CaptureSet(elems.filter(!that.accountsFor(_)))

def intersect (that: CaptureSet): CaptureSet =
CaptureSet(this.elems.intersect(that.elems))

Expand All @@ -46,6 +49,10 @@ case class CaptureSet private (elems: CaptureSet.Refs) extends Showable:
case ref => ref.singletonCaptureSet
}

def toRetainsTypeArg(using Context): Type =
((NoType: Type) /: elems) ((tp, ref) =>
if tp.exists then OrType(tp, ref, soft = false) else ref)

override def toString = elems.toString

override def toText(printer: Printer): Text =
Expand Down Expand Up @@ -106,3 +113,7 @@ object CaptureSet:
recur(tp)
.showing(i"capture set of $tp = $result", capt)

def fromRetainsTypeArg(tp: Type)(using Context): CaptureSet = tp match
case tp: CaptureRef if tp.isTracked => tp.singletonCaptureSet
case OrType(tp1, tp2) => fromRetainsTypeArg(tp1) ++ fromRetainsTypeArg(tp2)
case _ => empty
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ class Definitions {
@tu lazy val Predef_identity : Symbol = ScalaPredefModule.requiredMethod(nme.identity)
@tu lazy val Predef_undefined: Symbol = ScalaPredefModule.requiredMethod(nme.???)
@tu lazy val Predef_retainsType: Symbol = ScalaPredefModule.requiredType(tpnme.retains)
@tu lazy val Predef_capturing: Symbol = ScalaPredefModule.requiredType(tpnme.CAPTURING)
@tu lazy val ScalaPredefModuleClass: ClassSymbol = ScalaPredefModule.moduleClass.asClass

@tu lazy val SubTypeClass: ClassSymbol = requiredClass("scala.<:<")
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ object StdNames {
val BITMAP_TRANSIENT: N = s"${BITMAP_PREFIX}trans$$" // initialization bitmap for transient lazy vals
val BITMAP_CHECKINIT: N = s"${BITMAP_PREFIX}init$$" // initialization bitmap for checkinit values
val BITMAP_CHECKINIT_TRANSIENT: N = s"${BITMAP_PREFIX}inittrans$$" // initialization bitmap for transient checkinit values
val CAPTURING = "|>"
val DEFAULT_GETTER: N = str.DEFAULT_GETTER
val DEFAULT_GETTER_INIT: N = "$lessinit$greater"
val DO_WHILE_PREFIX: N = "doWhile$"
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4639,9 +4639,7 @@ object Types {
&& !linkedVar.inst.isExactlyNothing
&& linkedVar.inst <:< this
else
inst.isExactlyAny
&& !linkedVar.inst.isExactlyAny
&& this <:< linkedVar.inst
inst.isExactlyAny && !linkedVar.inst.isExactlyAny && this <:< linkedVar.inst
if needsOldInstance then
inst = linkedVar.inst
.showing(i"avoid extremal instance for $this be instantiating with old $inst", refinr)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,9 @@ class TreePickler(pickler: TastyPickler) {
case tp: CapturingType =>
writeByte(APPLIEDtype)
withLength {
pickleType(defn.Predef_retainsType.typeRef)
pickleType(tp.parent)
pickleType(defn.Predef_capturing.typeRef)
pickleType(tp.ref)
pickleType(tp.parent)
}
case tpe: PolyType if richTypes =>
pickleMethodic(POLYtype, tpe, EmptyFlags)
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -360,9 +360,9 @@ class TreeUnpickler(reader: TastyReader,
val tycon = readType()
val args = until(end)(readType())
tycon match
case tycon: TypeRef if tycon.symbol == defn.Predef_retainsType =>
if ctx.settings.Ycc.value then CapturingType.checked(args(0), args(1))
else args(0)
case tycon: TypeRef if tycon.symbol == defn.Predef_capturing =>
if ctx.settings.Ycc.value then CapturingType.checked(args(1), args(0))
else args(1)
case _ =>
tycon.appliedTo(args)
case TYPEBOUNDS =>
Expand Down
37 changes: 35 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,24 @@ object Parsers {
}
}

def followingIsCaptureSet(): Boolean =
val lookahead = in.LookaheadScanner()
def recur(): Boolean =
lookahead.isIdent && {
lookahead.nextToken()
if lookahead.token == COMMA then
lookahead.nextToken()
recur()
else
lookahead.token == RBRACE && {
lookahead.nextToken()
canStartInfixTypeTokens.contains(lookahead.token)
|| lookahead.token == LBRACKET
}
}
lookahead.nextToken()
recur()

/* --------- OPERAND/OPERATOR STACK --------------------------------------- */

var opStack: List[OpInfo] = Nil
Expand Down Expand Up @@ -1329,17 +1347,27 @@ object Parsers {
case _ => false
}

def captureRef(): Tree =
atSpan(in.offset) {
val name = ident()
if name.isVarPattern then SingletonTypeTree(Ident(name))
else Ident(name.toTypeName)
}

/** Type ::= FunType
* | HkTypeParamClause ‘=>>’ Type
* | FunParamClause ‘=>>’ Type
* | MatchType
* | InfixType
* | CaptureSet Type
* FunType ::= (MonoFunType | PolyFunType)
* MonoFunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type
* PolyFunType ::= HKTypeParamClause '=>' Type
* FunTypeArgs ::= InfixType
* | `(' [ [ ‘[using]’ ‘['erased'] FunArgType {`,' FunArgType } ] `)'
* | '(' [ ‘[using]’ ‘['erased'] TypedFunParam {',' TypedFunParam } ')'
* CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}`
* CaptureRef ::= Ident
*/
def typ(): Tree = {
val start = in.offset
Expand Down Expand Up @@ -1438,6 +1466,11 @@ object Parsers {
}
else { accept(TLARROW); typ() }
}
else if in.token == LBRACE && followingIsCaptureSet() then
val refs = inBraces { commaSeparated(captureRef) }
val t = typ()
val captured = refs.reduce(InfixOp(_, Ident(tpnme.raw.BAR), _))
AppliedTypeTree(TypeTree(defn.Predef_capturing.typeRef), captured :: t :: Nil)
else if (in.token == INDENT) enclosed(INDENT, typ())
else infixType()

Expand Down Expand Up @@ -1506,7 +1539,7 @@ object Parsers {
def infixType(): Tree = infixTypeRest(refinedType())

def infixTypeRest(t: Tree): Tree =
infixOps(t, canStartTypeTokens, refinedTypeFn, Location.ElseWhere,
infixOps(t, canStartInfixTypeTokens, refinedTypeFn, Location.ElseWhere,
isType = true,
isOperator = !followingIsVararg())

Expand Down Expand Up @@ -3142,7 +3175,7 @@ object Parsers {
ImportSelector(
atSpan(in.skipToken()) { Ident(nme.EMPTY) },
bound =
if canStartTypeTokens.contains(in.token) then rejectWildcardType(infixType())
if canStartInfixTypeTokens.contains(in.token) then rejectWildcardType(infixType())
else EmptyTree)

/** id [‘as’ (id | ‘_’) */
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Tokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ object Tokens extends TokensCommon {

final val canStartExprTokens2: TokenSet = canStartExprTokens3 | BitSet(DO)

final val canStartTypeTokens: TokenSet = literalTokens | identifierTokens | BitSet(
THIS, SUPER, USCORE, LPAREN, AT)
final val canStartInfixTypeTokens: TokenSet = literalTokens | identifierTokens | BitSet(
THIS, SUPER, USCORE, LPAREN, LBRACE, AT)

final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT)

Expand Down
10 changes: 7 additions & 3 deletions compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import util.SourcePosition
import java.lang.Integer.toOctalString
import scala.util.control.NonFatal
import scala.annotation.switch
import config.Config

class PlainPrinter(_ctx: Context) extends Printer {
/** The context of all public methods in Printer and subclasses.
Expand Down Expand Up @@ -188,7 +189,10 @@ class PlainPrinter(_ctx: Context) extends Printer {
(" <: " ~ toText(bound) provided !bound.isAny)
}.close
case CapturingType(parent, ref) =>
changePrec(InfixPrec)(toText(parent) ~ " retains " ~ toTextCaptureRef(ref))
if Config.printCaptureSetsAsPrefix then
changePrec(GlobalPrec)("{" ~ toTextCaptureRef(ref) ~ "} " ~ toText(parent))
else
changePrec(InfixPrec)(toText(parent) ~ " retains " ~ toTextCaptureRef(ref))
case tp: PreviousErrorType if ctx.settings.XprintTypes.value =>
"<error>" // do not print previously reported error message because they may try to print this error type again recuresevely
case tp: ErrorType =>
Expand Down Expand Up @@ -276,7 +280,7 @@ class PlainPrinter(_ctx: Context) extends Printer {

/** If -uniqid is set, the unique id of symbol, after a # */
protected def idString(sym: Symbol): String =
if (showUniqueIds || Printer.debugPrintUnique) "#" + sym.id else ""
if showUniqueIds then "#" + sym.id else ""

def nameString(sym: Symbol): String =
simpleNameString(sym) + idString(sym) // + "<" + (if (sym.exists) sym.owner else "") + ">"
Expand Down Expand Up @@ -316,7 +320,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
case tp @ ConstantType(value) =>
toText(value)
case pref: TermParamRef =>
nameString(pref.binder.paramNames(pref.paramNum))
nameString(pref.binder.paramNames(pref.paramNum)) ~ lambdaHash(pref.binder)
case tp: RecThis =>
val idx = openRecs.reverse.indexOf(tp.binder)
if (idx >= 0) selfRecName(idx + 1)
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
changePrec(AndTypePrec) { toText(args(0)) ~ " & " ~ atPrec(AndTypePrec + 1) { toText(args(1)) } }
else if tpt.symbol == defn.Predef_retainsType && args.length == 2 then
changePrec(InfixPrec) { toText(args(0)) ~ " retains " ~ toText(args(1)) }
else if tpt.symbol == defn.Predef_capturing && args.length == 2 then
changePrec(GlobalPrec) { "{" ~ toText(args(0)) ~ "}" ~ toText(args(1)) }
else if defn.isFunctionClass(tpt.symbol)
&& tpt.isInstanceOf[TypeTree] && tree.hasType && !printDebug
then
Expand Down
106 changes: 3 additions & 103 deletions compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ class CheckCaptures extends RefineTypes:
|The inferred arguments are: [$args%, %]"""
case _ => s"type argument$notAllowed"
report.error(msg, arg.srcPos)
case tree: SimplifiedTypeTree[_] =>
checkWellFormed(tree.typeOpt, tree.srcPos)
case tree: TypeTree =>
// it's inferred, no need to check
case _: TypTree | _: Closure =>
Expand All @@ -151,106 +153,4 @@ class CheckCaptures extends RefineTypes:

def postRefinerCheck(tree: tpd.Tree)(using Context): Unit =
PostRefinerCheck.traverse(tree)


object CheckCaptures:
import ast.tpd.*

def expandFunctionTypes(using Context) =
ctx.settings.Ycc.value && !ctx.settings.YccNoAbbrev.value && !ctx.isAfterTyper

object FunctionTypeTree:
def unapply(tree: Tree)(using Context): Option[(List[Type], Type)] =
if defn.isFunctionType(tree.tpe) then
tree match
case AppliedTypeTree(tycon: TypeTree, args) =>
Some((args.init.tpes, args.last.tpe))
case RefinedTypeTree(_, (appDef: DefDef) :: Nil) if appDef.span == tree.span =>
appDef.symbol.info match
case mt: MethodType => Some((mt.paramInfos, mt.resultType))
case _ => None
case _ =>
None
else None

object CapturingTypeTree:
def unapply(tree: Tree)(using Context): Option[(Tree, Tree, CaptureRef)] = tree match
case AppliedTypeTree(tycon, parent :: _ :: Nil)
if tycon.symbol == defn.Predef_retainsType =>
tree.tpe match
case CapturingType(_, ref) => Some((tycon, parent, ref))
case _ => None
case _ => None

def addRetains(tree: Tree, ref: CaptureRef)(using Context): Tree =
untpd.AppliedTypeTree(
TypeTree(defn.Predef_retainsType.typeRef), List(tree, TypeTree(ref)))
.withType(CapturingType(tree.tpe, ref))
.showing(i"add inferred capturing $result", capt)

/** Under -Ycc but not -Ycc-no-abbrev, if `tree` represents a function type
* `(ARGS) => T` where T is tracked and all ARGS are pure, expand it to
* `(ARGS) => T retains CS` where CS is the capture set of `T`. These synthesized
* additions will be removed again if the function type is wrapped in an
* explicit `retains` type.
*/
def addResultCaptures(tree: Tree)(using Context): Tree =
if expandFunctionTypes then
tree match
case FunctionTypeTree(argTypes, resType) =>
val cs = resType.captureSet
if cs.nonEmpty && argTypes.forall(_.captureSet.isEmpty)
then (tree /: cs.elems)(addRetains)
else tree
case _ =>
tree
else tree

private def addCaptures(tp: Type, refs: Type)(using Context): Type = refs match
case ref: CaptureRef => CapturingType(tp, ref)
case OrType(refs1, refs2) => addCaptures(addCaptures(tp, refs1), refs2)
case _ => tp

/** @pre: `tree is a tree of the form `T retains REFS`.
* Return the same tree with `parent1` instead of `T` with its type
* recomputed accordingly.
*/
private def derivedCapturingTree(tree: AppliedTypeTree, parent1: Tree)(using Context): AppliedTypeTree =
tree match
case AppliedTypeTree(tycon, parent :: (rest @ (refs :: Nil))) if parent ne parent1 =>
cpy.AppliedTypeTree(tree)(tycon, parent1 :: rest)
.withType(addCaptures(parent1.tpe, refs.tpe))
case _ =>
tree

private def stripCaptures(tree: Tree, ref: CaptureRef)(using Context): Tree = tree match
case tree @ AppliedTypeTree(tycon, parent :: refs :: Nil) if tycon.symbol == defn.Predef_retainsType =>
val parent1 = stripCaptures(parent, ref)
val isSynthetic = tycon.isInstanceOf[TypeTree]
if isSynthetic then
parent1.showing(i"drop inferred capturing $tree => $result", capt)
else
if parent1.tpe.captureSet.accountsFor(ref) then
report.warning(
em"redundant capture: $parent1 already contains $ref with capture set ${ref.captureSet} in its capture set ${parent1.tpe.captureSet}",
tree.srcPos)
derivedCapturingTree(tree, parent1)
case _ => tree

private def stripCaptures(tree: Tree, refs: Type)(using Context): Tree = refs match
case ref: CaptureRef => stripCaptures(tree, ref)
case OrType(refs1, refs2) => stripCaptures(stripCaptures(tree, refs1), refs2)
case _ => tree

/** If this is a tree of the form `T retains REFS`,
* - strip any synthesized captures directly in T;
* - warn if a reference in REFS is accounted for by the capture set of the remaining type
*/
def refineNestedCaptures(tree: AppliedTypeTree)(using Context): AppliedTypeTree = tree match
case AppliedTypeTree(tycon, parent :: (rest @ (refs :: Nil))) if tycon.symbol == defn.Predef_retainsType =>
derivedCapturingTree(tree, stripCaptures(parent, refs.tpe))
case _ =>
tree

end CheckCaptures

end CheckCaptures
Loading