diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 145c61584fcc..35a459dba67c 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -236,6 +236,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Tracked()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Tracked) + case class CaptureParam()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.CaptureParam) + /** Used under pureFunctions to mark impure function types `A => B` in `FunctionWithMods` */ case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure) } @@ -534,12 +536,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { TypeApply(capsInternalDot(nme.capsOf), tp :: Nil) // Capture set variable `[C^]` becomes: `[C >: CapSet <: CapSet^{cap}]` - def makeCapsBound()(using Context): TypeBoundsTree = - TypeBoundsTree( - Select(scalaDot(nme.caps), tpnme.CapSet), - makeRetaining( - Select(scalaDot(nme.caps), tpnme.CapSet), - Nil, tpnme.retainsCap)) + def makeCapsBound(refsL: List[Tree] = Nil, refsU: List[Tree] = Nil)(using Context): TypeBoundsTree = + val lower = refsL match + case Nil => Select(scalaDot(nme.caps), tpnme.CapSet) + case refsL => makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refsL, tpnme.retains) + val upper = + makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refsU, if refsU.isEmpty then tpnme.retainsCap else tpnme.retains) + TypeBoundsTree(lower, upper) 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/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 57bf870c6b64..9b34bb8fc8e3 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -380,6 +380,9 @@ object Flags { /** Tracked modifier for class parameter / a class with some tracked parameters */ val (Tracked @ _, _, Dependent @ _) = newFlags(46, "tracked") + /** Cap modifier for capture-set parameters and capture-set members */ + val (_, _, CaptureParam @ _) = newFlags(47, "cap") + // ------------ Flags following this one are not pickled ---------------------------------- /** Symbol is not a member of its owner */ diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 6fd76e37977d..571a786e9106 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -91,6 +91,14 @@ object Mode { */ val ImplicitExploration: Mode = newMode(12, "ImplicitExploration") + /** We are currently inside a capture set. + * A term name could be a capture variable, so we need to + * check that it is valid to use as type name. + * Since this mode is only used during annotation typing, + * we can reuse the value of `ImplicitExploration` to save bits. + */ + val InCaptureSet: Mode = ImplicitExploration + /** We are currently unpickling Scala2 info */ val Scala2Unpickling: Mode = newMode(13, "Scala2Unpickling") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index c33c795571e6..29f843a64cb2 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -444,6 +444,7 @@ object StdNames { val bytes: N = "bytes" val canEqual_ : N = "canEqual" val canEqualAny : N = "canEqualAny" + val cap: N = "cap" val caps: N = "caps" val capsOf: N = "capsOf" val captureChecking: N = "captureChecking" diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index c24dbce1b6ac..c74975ccae20 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -220,6 +220,26 @@ object Parsers { def isErased = isIdent(nme.erased) && in.erasedEnabled // Are we seeing an `erased` soft keyword that will not be an identifier? def isErasedKw = isErased && in.isSoftModifierInParamModifierPosition + // Are we seeing a `cap` soft keyword for declaring a capture-set member or a capture-variable parameter? + def isCapKw = Feature.ccEnabled && isIdent(nme.cap) + // Are we seeing 'cap type' ? + def isCapTypeKw = isCapKw && in.lookahead.token == TYPE + // Are the next two tokens after the current 'cap type'? + def isCapTypeKwNext = { + if Feature.ccEnabled then + val lookahead = in.LookaheadScanner() + lookahead.nextToken() + lookahead.isIdent(nme.cap) && lookahead.lookahead.token == TYPE + else false + } + // Are we seeing a named cap parameter assignment? `'cap' id '='` (under namedTypeArguments) + def isCapAssignmentNext = { + if isCapKw then + val lookahead = in.LookaheadScanner() + lookahead.nextToken() + lookahead.isIdent && lookahead.lookahead.token == EQUALS + else false + } def isSimpleLiteral = simpleLiteralTokens.contains(in.token) || isIdent(nme.raw.MINUS) && numericLitTokens.contains(in.lookahead.token) @@ -227,6 +247,7 @@ object Parsers { def isNumericLit = numericLitTokens contains in.token def isTemplateIntro = templateIntroTokens contains in.token def isDclIntro = dclIntroTokens contains in.token + def isDclIntroNext = dclIntroTokens contains in.lookahead.token def isStatSeqEnd = in.isNestedEnd || in.token == EOF || in.token == RPAREN def mustStartStat = mustStartStatTokens contains in.token @@ -1600,8 +1621,8 @@ object Parsers { case _ => None } - /** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] [`.` rd] - * | [ { SimpleRef `.` } SimpleRef `.` ] id `^` + /** CaptureRef ::= { SimpleRef ‘.’ } SimpleRef [‘*’] [‘.’ ‘rd’] -- under captureChecking + * | [ { SimpleRef ‘.’ } SimpleRef ‘.’ ] id */ def captureRef(): Tree = @@ -1620,22 +1641,20 @@ object Parsers { in.nextToken() derived(reachRef, nme.CC_READONLY) else reachRef - else if isIdent(nme.UPARROW) then - in.nextToken() - atSpan(startOffset(ref)): - convertToTypeId(ref) match - case ref: RefTree => makeCapsOf(ref) - case ref => ref else ref recur(simpleRef()) end captureRef - /** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under captureChecking + /** CaptureSet ::= ‘{’ CaptureRef {‘,’ CaptureRef} ‘}’ -- under captureChecking */ - def captureSet(): List[Tree] = inBraces { - if in.token == RBRACE then Nil else commaSeparated(captureRef) - } + def captureSet(): List[Tree] = + if in.token != LBRACE then + syntaxError(em"expected '{' to start capture set", in.offset) + Nil + else inBraces { + if in.token == RBRACE then Nil else commaSeparated(captureRef) + } def capturesAndResult(core: () => Tree): Tree = if Feature.ccEnabled && in.token == LBRACE && canStartCaptureSetContentsTokens.contains(in.lookahead.token) @@ -1649,9 +1668,9 @@ object Parsers { * | InfixType * FunType ::= (MonoFunType | PolyFunType) * MonoFunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type - * | (‘->’ | ‘?->’ ) [CaptureSet] Type -- under pureFunctions + * | (‘->’ | ‘?->’ ) [CaptureSet] Type -- under pureFunctions and captureChecking * PolyFunType ::= TypTypeParamClause '=>' Type - * | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions + * | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions and captureChecking * FunTypeArgs ::= InfixType * | `(' [ FunArgType {`,' FunArgType } ] `)' * | '(' [ TypedFunParam {',' TypedFunParam } ')' @@ -1886,7 +1905,7 @@ object Parsers { if in.token == LPAREN then funParamClause() :: funParamClauses() else Nil /** InfixType ::= RefinedType {id [nl] RefinedType} - * | RefinedType `^` // under capture checking + * | RefinedType `^` -- under captureChecking */ def infixType(inContextBound: Boolean = false): Tree = infixTypeRest(inContextBound)(refinedType()) @@ -1978,7 +1997,8 @@ object Parsers { def typeBlockStats(): List[Tree] = val tdefs = new ListBuffer[Tree] - while in.token == TYPE do tdefs += typeBlockStat() + while (in.token == TYPE) do + tdefs += typeBlockStat() tdefs.toList /** TypeBlockStat ::= ‘type’ {nl} TypeDef @@ -2173,35 +2193,47 @@ object Parsers { atSpan(startOffset(t), startOffset(id)) { Select(t, id.name) } } - /** ArgTypes ::= Type {`,' Type} - * | NamedTypeArg {`,' NamedTypeArg} - * NamedTypeArg ::= id `=' Type + /** ArgTypes ::= TypeArg {‘,’ TypeArg} + * | NamedTypeArg {‘,’ NamedTypeArg} + * TypeArg ::= Type + * | CaptureSet -- under captureChecking + * NamedTypeArg ::= id ‘=’ Type -- under namedTypeArguments + * | ‘cap’ id ‘=’ CaptureSet -- under namedTypeArguments and captureChecking * NamesAndTypes ::= NameAndType {‘,’ NameAndType} - * NameAndType ::= id ':' Type + * NameAndType ::= id ‘:’ Type */ def argTypes(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): List[Tree] = - def argType() = - val t = typ() + inline def wildCardCheck(inline gen: Tree): Tree = + val t = gen if wildOK then t else rejectWildcardType(t) - def namedArgType() = + def argType() = wildCardCheck(typ()) + + def typeArg() = wildCardCheck: + if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext && !isCapTypeKwNext then // is this a capture set and not a refinement type? + // This case is ambiguous w.r.t. an Object literal {}. But since CC is enabled, we probably expect it to designate the empty set + concreteCapsType(captureSet()) + else typ() + + def namedTypeArg() = atSpan(in.offset): + val isCap = if isCapKw then { in.nextToken(); true } else false val name = ident() accept(EQUALS) - NamedArg(name.toTypeName, argType()) + NamedArg(name.toTypeName, if isCap then concreteCapsType(captureSet()) else argType()) - def namedElem() = + def nameAndType() = atSpan(in.offset): val name = ident() acceptColon() NamedArg(name, argType()) - if namedOK && isIdent && in.lookahead.token == EQUALS then - commaSeparated(() => namedArgType()) + if namedOK && (isIdent && in.lookahead.token == EQUALS || isCapAssignmentNext) then + commaSeparated(() => namedTypeArg()) else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.enablesNamedTuples then - commaSeparated(() => namedElem()) + commaSeparated(() => nameAndType()) else - commaSeparated(() => argType()) + commaSeparated(() => typeArg()) end argTypes def paramTypeOf(core: () => Tree): Tree = @@ -2245,7 +2277,7 @@ object Parsers { PostfixOp(t, Ident(tpnme.raw.STAR)) else t - /** TypeArgs ::= `[' Type {`,' Type} `]' + /** TypeArgs ::= `[' TypeArg {`,' TypeArg} `]' * NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]' */ def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] = @@ -2260,20 +2292,34 @@ object Parsers { inBraces(refineStatSeq()) /** TypeBounds ::= [`>:' Type] [`<:' Type] - * | `^` -- under captureChecking */ def typeBounds(): TypeBoundsTree = atSpan(in.offset): - if in.isIdent(nme.UPARROW) && Feature.ccEnabled then - in.nextToken() - makeCapsBound() - else - TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE)) + TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE)) + + /** CaptureSetBounds ::= [`>:' CaptureSet ] [`<:' CaptureSet ] --- under captureChecking + */ + def captureSetBounds(): TypeBoundsTree = + atSpan(in.offset): + TypeBoundsTree(capsBound(SUPERTYPE), capsBound(SUBTYPE)) private def bound(tok: Int): Tree = if (in.token == tok) { in.nextToken(); toplevelTyp() } else EmptyTree + private def capsBound(refs: List[Tree], isLowerBound: Boolean = false): Tree = + if isLowerBound && refs.isEmpty then // lower bounds with empty capture sets become a pure CapSet + Select(scalaDot(nme.caps), tpnme.CapSet) + else + makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, if refs.isEmpty then tpnme.retainsCap else tpnme.retains) + + private def capsBound(tok: Int): Tree = + if (in.token == tok) then + in.nextToken() + capsBound(captureSet(), isLowerBound = tok == SUPERTYPE) + else + capsBound(Nil, isLowerBound = tok == SUPERTYPE) + /** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds] */ def typeAndCtxBounds(pname: TypeName): Tree = { @@ -2283,6 +2329,15 @@ object Parsers { else atSpan((t.span union cbs.head.span).start) { ContextBounds(t, cbs) } } + /** CaptureSetAndCtxBounds ::= CaptureSetBounds [`:` ContextBounds] -- under captureChecking + */ + def captureSetAndCtxBounds(pname: TypeName): Tree = { + val t = captureSetBounds() + val cbs = contextBounds(pname) + if (cbs.isEmpty) t + else atSpan((t.span union cbs.head.span).start) { ContextBounds(t, cbs) } + } + /** ContextBound ::= Type [`as` id] */ def contextBound(pname: TypeName): Tree = val t = toplevelTyp(inContextBound = true) @@ -2802,7 +2857,10 @@ object Parsers { in.nextToken() simpleExprRest(selectorOrMatch(t), location, canApply = true) case LBRACKET => - val tapp = atSpan(startOffset(t), in.offset) { TypeApply(t, typeArgs(namedOK = true, wildOK = false)) } + val tapp = atSpan(startOffset(t), in.offset) { + val args = typeArgs(namedOK = true, wildOK = false) + TypeApply(t, args) + } simpleExprRest(tapp, location, canApply = true) case LPAREN | LBRACE | INDENT if canApply => val app = atSpan(startOffset(t), in.offset) { mkApply(t, argumentExprs()) } @@ -3326,6 +3384,7 @@ object Parsers { case nme.tracked => Mod.Tracked() case nme.erased if in.erasedEnabled => Mod.Erased() case nme.mut if Feature.ccEnabled => Mod.Mut() + case nme.cap => Mod.CaptureParam() } } @@ -3394,7 +3453,7 @@ object Parsers { * | opaque * LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased | * inline | transparent | infix | - * mut -- under cc + * mut | cap -- under captureChecking */ def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = { @tailrec @@ -3483,22 +3542,25 @@ object Parsers { recur(numLeadParams, firstClause = true, prevIsTypeClause = false) end typeOrTermParamClauses - /** ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ - * ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] - * id [HkTypeParamClause] TypeAndCtxBounds + * ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] + * id [HkTypeParamClause] TypeAndCtxBounds + * | {Annotation} ‘cap’ id CaptureSetAndCtxBounds -- under captureChecking * * DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ - * DefTypeParam ::= {Annotation} - * id [HkTypeParamClause] TypeAndCtxBounds + * DefTypeParam ::= {Annotation} + * id [HkTypeParamClause] TypeAndCtxBounds + * | {Annotation} ‘cap’ id CaptureSetAndCtxBounds -- under captureChecking * * TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ - * TypTypeParam ::= {Annotation} - * (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds + * TypTypeParam ::= {Annotation} + * (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds + * | {Annotation} ‘cap’ (id | ‘_’) CaptureSetAndCtxBounds -- under captureChecking * * HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ - * HkTypeParam ::= {Annotation} [‘+’ | ‘-’] - * (id | ‘_’) [HkTypePamClause] TypeBounds + * HkTypeParam ::= {Annotation} [‘+’ | ‘-’] + * (id | ‘_’) [HkTypePamClause] TypeBounds + * | {Annotation} ‘cap’ (id | ‘_’) CaptureSetBounds -- under captureChecking */ def typeParamClause(paramOwner: ParamOwner): List[TypeDef] = inBracketsWithCommas { @@ -3508,9 +3570,49 @@ object Parsers { in.nextToken() ok - def typeParam(): TypeDef = { + def ensureNoHKParams() = // for cap params + if in.token == LBRACKET then + syntaxError(em"'cap' parameters cannot have type parameters") + in.nextToken() + + def ensureNoVariance() = // for cap params + if isIdent(nme.raw.PLUS) || isIdent(nme.raw.MINUS) then + syntaxError(em"no `+/-` variance annotation allowed here") + in.nextToken() + + def typeOrCapParam(): TypeDef = val start = in.offset var mods = annotsAsMods() | Param + if isCapKw then + mods |= CaptureParam + in.nextToken() + capParam(start, mods) + else typeParam(start, mods) + + def capParam(startOffset: Int, mods0: Modifiers): TypeDef = { + val start = startOffset + var mods = mods0 + if paramOwner.isClass then + mods |= PrivateLocal + ensureNoVariance() // TODO: in the future, we might want to support variances on capture params, ruled out for now + atSpan(start, nameStart) { + val name = + if paramOwner.acceptsWildcard && in.token == USCORE then + in.nextToken() + WildcardParamName.fresh().toTypeName + else ident().toTypeName + ensureNoHKParams() + val bounds = + if paramOwner.acceptsCtxBounds then captureSetAndCtxBounds(name) + else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then captureSetAndCtxBounds(name) + else captureSetBounds() + TypeDef(name, bounds).withMods(mods) + } + } + + def typeParam(startOffset: Int, mods0: Modifiers): TypeDef = { + val start = startOffset + var mods = mods0 if paramOwner.isClass then mods |= PrivateLocal if isIdent(nme.raw.PLUS) && checkVarianceOK() then @@ -3531,11 +3633,14 @@ object Parsers { TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods) } } - commaSeparated(() => typeParam()) + commaSeparated(() => typeOrCapParam()) } def typeParamClauseOpt(paramOwner: ParamOwner): List[TypeDef] = - if (in.token == LBRACKET) typeParamClause(paramOwner) else Nil + if (in.token == LBRACKET) + typeParamClause(paramOwner) + else + Nil /** ContextTypes ::= FunArgType {‘,’ FunArgType} */ @@ -3873,29 +3978,33 @@ object Parsers { () => atSpan(in.offset) { importSelection(simpleRef()) } end importExpr - /** Def ::= val PatDef - * | var VarDef - * | def DefDef - * | type {nl} TypeDef + /** Def ::= ‘val’ PatDef + * | ‘var’ VarDef + * | ‘def’ DefDef + * | ‘type’ {nl} TypeDef + * | ‘cap’ ‘type’ {nl} CapDef -- under captureChecking * | TmplDef - * EnumCase ::= `case' (id ClassConstr [`extends' ConstrApps]] | ids) + * EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] | ids) */ - def defOrDcl(start: Int, mods: Modifiers): Tree = in.token match { - case VAL => - in.nextToken() - patDefOrDcl(start, mods) - case VAR => - val mod = atSpan(in.skipToken()) { Mod.Var() } - val mod1 = addMod(mods, mod) - patDefOrDcl(start, mod1) - case DEF => - defDefOrDcl(start, in.skipToken(mods)) - case TYPE => - typeDefOrDcl(start, in.skipToken(mods)) - case CASE if inEnum => - enumCase(start, mods) - case _ => - tmplDef(start, mods) + def defOrDcl(start: Int, mods: Modifiers): Tree = + in.token match { + case VAL => + in.nextToken() + patDefOrDcl(start, mods) + case VAR => + val mod = atSpan(in.skipToken()) { Mod.Var() } + val mod1 = addMod(mods, mod) + patDefOrDcl(start, mod1) + case DEF => + defDefOrDcl(start, in.skipToken(mods)) + case TYPE if mods.is(CaptureParam) => + capDefOrDcl(start, in.skipToken(mods)) + case TYPE => + typeDefOrDcl(start, in.skipToken(mods)) + case CASE if inEnum => + enumCase(start, mods) + case _ => + tmplDef(start, mods) } /** PatDef ::= ids [‘:’ Type] [‘=’ Expr] @@ -4104,6 +4213,43 @@ object Parsers { } } + private def concreteCapsType(refs: List[Tree]): Tree = + makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains) + + /** CapDef ::= id CaptureSetAndCtxBounds [‘=’ CaptureSet] -- under captureChecking + */ + def capDefOrDcl(start: Offset, mods: Modifiers): Tree = + newLinesOpt() + atSpan(start, nameStart) { + val nameIdent = typeIdent() + val tname = nameIdent.name.asTypeName + if in.token == LBRACKET then syntaxError(em"'cap type' declarations cannot have type parameters") + + def makeCapDef(refs: List[Tree] | Tree): Tree = { + val tdef = TypeDef(nameIdent.name.toTypeName, + refs.match + case refs: List[Tree] => concreteCapsType(refs) + case bounds: Tree => bounds) + + if (nameIdent.isBackquoted) + tdef.pushAttachment(Backquoted, ()) + finalizeDef(tdef, mods, start) + } + + in.token.match + case EQUALS => + in.nextToken() + makeCapDef(captureSet()) + case SUBTYPE | SUPERTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF => + makeCapDef(captureSetAndCtxBounds(tname)) + case _ if (staged & StageKind.QuotedPattern) != 0 + || sourceVersion.enablesNewGivens && in.isColon => + makeCapDef(captureSetAndCtxBounds(tname)) + case _ => + syntaxErrorOrIncomplete(ExpectedCaptureBoundOrEquals(in.token)) + return EmptyTree // return to avoid setting the span to EmptyTree + } + /** TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef * | [‘case’] ‘object’ ObjectDef * | ‘enum’ EnumDef @@ -4697,6 +4843,7 @@ object Parsers { * | ‘var’ VarDef * | ‘def’ DefDef * | ‘type’ {nl} TypeDef + * | ‘cap’ ‘type’ {nl} CapDef -- under captureChecking * (in reality we admit class defs and vars and filter them out afterwards in `checkLegal`) */ def refineStatSeq(): List[Tree] = { @@ -4722,9 +4869,14 @@ object Parsers { fail(em"this kind of definition cannot be a refinement") while + val mods = + if isCapTypeKw then // allow `cap type` in refinements + in.nextToken() + addMod(Modifiers(), Mod.CaptureParam()) + else Modifiers() val dclFound = isDclIntro if dclFound then - stats ++= checkLegal(defOrDcl(in.offset, Modifiers())) + stats ++= checkLegal(defOrDcl(in.offset, mods)) var what = "declaration" if inFunReturnType then what += " (possible cause: missing `=` in front of current method body)" statSepOrEnd(stats, noPrevStat = !dclFound, what) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index f7050cec41fd..a1969fc2e9c1 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -1212,7 +1212,8 @@ object Scanners { && (softModifierNames.contains(name) || name == nme.erased && erasedEnabled || name == nme.tracked && trackedEnabled - || name == nme.mut && Feature.ccEnabled) + || name == nme.mut && Feature.ccEnabled + || name == nme.cap && Feature.ccEnabled) def isSoftModifierInModifierPosition: Boolean = isSoftModifier && inModifierPosition() diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 8f8f4676f43b..264ce47cb8ef 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -222,6 +222,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case EnumMayNotBeValueClassesID // errorNumber: 206 case IllegalUnrollPlacementID // errorNumber: 207 case ExtensionHasDefaultID // errorNumber: 208 + case ExpectedCaptureBoundOrEqualsID // errorNumber: 209 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index dcd7ed10987b..291ee431b34b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1917,6 +1917,23 @@ class ExpectedTypeBoundOrEquals(found: Token)(using Context) |""" } +class ExpectedCaptureBoundOrEquals(found: Token)(using Context) + extends SyntaxMsg(ExpectedCaptureBoundOrEqualsID) { + def msg(using Context) = i"${hl("=")}, ${hl(">:")}, or ${hl("<:")} expected, but ${Tokens.showToken(found)} found" + + def explain(using Context) = + i"""Capture parameters and abstract captures may be constrained by a capture bound. + |Such capture bounds limit the concrete values of the capture variables and possibly + |reveal more information about the members of such captures. + | + |A lower capture bound ${hl("B >: A")} expresses that the capture variable ${hl("B")} + |refers to a super capture of capture ${hl("A")}. + | + |An upper capture bound ${hl("T <: A")} declares that capture variable ${hl("T")} + |refers to a subcapture of ${hl("A")}. + |""" +} + class ClassAndCompanionNameClash(cls: Symbol, other: Symbol)(using Context) extends NamingMsg(ClassAndCompanionNameClashID) { def msg(using Context) = diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 71fc250d0710..381643a1cfbd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -36,6 +36,7 @@ import annotation.threadUnsafe import scala.util.control.NonFatal import dotty.tools.dotc.inlines.Inlines +import dotty.tools.dotc.cc.isRetains object Applications { import tpd.* @@ -1115,7 +1116,9 @@ trait Applications extends Compatibility { val fun2 = Applications.retypeSignaturePolymorphicFn(fun1, methType) simpleApply(fun2, proto) case funRef: TermRef => - val app = ApplyTo(tree, fun1, funRef, proto, pt) + // println(i"typedApply: $funRef, ${tree.args}, ${funRef.symbol.maybeOwner.isRetains}") + val applyCtx = if funRef.symbol.maybeOwner.isRetains then ctx.addMode(Mode.InCaptureSet) else ctx + val app = ApplyTo(tree, fun1, funRef, proto, pt)(using applyCtx) convertNewGenericArray( widenEnumCase( postProcessByNameArgs(funRef, app).computeNullable(), diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6b7b840e7606..6ecc9d9cdaa5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -715,6 +715,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer && ctx.owner.owner.unforcedDecls.lookup(tree.name).exists then // we are in the arguments of a this(...) constructor call errorTree(tree, em"$tree is not accessible from constructor arguments") + else if name.isTermName && ctx.mode.is(Mode.InCaptureSet) then + // If we are in a capture set and the identifier is not a term name, + // try to type it with the same name but as a type + typed(untpd.makeCapsOf(untpd.cpy.Ident(tree)(name.toTypeName)), pt) else errorTree(tree, MissingIdent(tree, kind, name, pt)) end typedIdent @@ -920,6 +924,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer typedCBSelect(tree0, pt, qual) else EmptyTree + // Otherwise, if we are in a capture set, try to type it as a capture variable + // reference (as selecting a type name). + def trySelectTypeInCaptureSet() = + if tree0.name.isTermName && ctx.mode.is(Mode.InCaptureSet) then + typedSelectWithAdapt(untpd.cpy.Select(tree0)(qual, tree0.name.toTypeName), pt, qual) + else EmptyTree + // Otherwise, report an error def reportAnError() = assignType(tree, @@ -941,6 +952,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer .orElse(tryDynamic()) .orElse(trySelectable()) .orElse(tryCBCompanion()) + .orElse(trySelectTypeInCaptureSet()) .orElse(reportAnError()) end typedSelectWithAdapt diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 665b4f5144ba..d99c169fe7b7 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -141,7 +141,7 @@ type val var while with yield ### Soft keywords ``` -as derives end erased extension infix inline opaque open throws tracked transparent using | * + - +as cap derives end erased extension infix inline mut opaque open throws tracked transparent using | * + - ``` See the [separate section on soft keywords](../reference/soft-modifier.md) for additional @@ -182,7 +182,9 @@ Type ::= FunType | MatchType | InfixType FunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type Function(ts, t) | FunctionWithMods(ts, t, mods, erasedParams) - | TypTypeParamClause '=>' Type PolyFunction(ps, t) + | FunTypeArgs (‘->’ | ‘?->’) [CaptureSet] Type -- under pureFunctions and captureChecking + | TypTypeParamClause ‘=>’ Type PolyFunction(ps, t) + | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions and captureChecking FunTypeArgs ::= InfixType | ‘(’ [ FunArgTypes ] ‘)’ | FunParamClause @@ -190,7 +192,9 @@ FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ TypedFunParam ::= [`erased`] id ‘:’ Type MatchType ::= InfixType `match` <<< TypeCaseClauses >>> InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2) + | RefinedType ‘^’ -- under captureChecking RefinedType ::= AnnotType {[nl] Refinement} RefinedTypeTree(t, ds) + | AnnotType {[nl] Refinement} ‘^’ CaptureSet -- under captureChecking AnnotType ::= SimpleType {Annotation} Annotated(t, annot) AnnotType1 ::= SimpleType1 {Annotation} Annotated(t, annot) @@ -210,8 +214,10 @@ Singleton ::= SimpleRef | Singleton ‘.’ id FunArgType ::= Type | ‘=>’ Type PrefixOp(=>, t) + | ‘->’ [CaptureSet] Type -- under captureChecking FunArgTypes ::= FunArgType { ‘,’ FunArgType } ParamType ::= [‘=>’] ParamValueType + | ‘->’ [CaptureSet] ParamValueType -- under captureChecking ParamValueType ::= Type [‘*’] PostfixOp(t, "*") | IntoType | ‘(’ IntoType ‘)’ ‘*’ PostfixOp(t, "*") @@ -219,7 +225,7 @@ IntoType ::= [‘into’] IntoTargetType | ‘(’ IntoType ‘)’ IntoTargetType ::= Type | FunTypeArgs (‘=>’ | ‘?=>’) IntoType -TypeArgs ::= ‘[’ Types ‘]’ ts +TypeArgs ::= ‘[’ TypeArg {‘,’ TypeArg} ‘]’ ts Refinement ::= :<<< [RefineDcl] {semi [RefineDcl]} >>> ds TypeBounds ::= [‘>:’ Type] [‘<:’ Type] TypeBoundsTree(lo, hi) TypeAndCtxBounds ::= TypeBounds [‘:’ ContextBounds] ContextBounds(typeBounds, tps) @@ -228,8 +234,15 @@ ContextBounds ::= ContextBound | '{' ContextBound {',' ContextBound} '}' ContextBound ::= Type ['as' id] Types ::= Type {‘,’ Type} +TypeArg ::= Type + | CaptureSet -- under captureChecking NamesAndTypes ::= NameAndType {‘,’ NameAndType} NameAndType ::= id ':' Type +CaptureSet ::= ‘{’ CaptureRef {‘,’ CaptureRef} ‘}’ -- under captureChecking +CaptureRef ::= { SimpleRef ‘.’ } SimpleRef [‘*’] [‘.’ ‘rd’] -- under captureChecking + | [ { SimpleRef ‘.’ } SimpleRef ‘.’ ] id -- under captureChecking +CaptureSetBounds ::= [‘>:’ CaptureSet ] [‘<:’ CaptureSet ] -- under captureChecking +CaptureSetAndCtxBounds ::= CaptureSetBounds [‘:’ ContextBounds] -- under captureChecking ``` ### Expressions @@ -365,16 +378,20 @@ ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] TypeDef(Modifiers, name, tparams, bounds) id [HkTypeParamClause] TypeAndCtxBounds Bound(below, above, context) + | {Annotation} ‘cap’ id CaptureSetAndCtxBounds -- under captureChecking DefTypeParamClause::= [nl] ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeAndCtxBounds + | {Annotation} ‘cap’ id CaptureSetAndCtxBounds -- under captureChecking TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ TypTypeParam ::= {Annotation} (id | ‘_’) [HkTypeParamClause] TypeBounds + | {Annotation} ‘cap’ id CaptureSetAndCtxBounds -- under captureChecking HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (id | ‘_’) [HkTypeParamClause] TypeBounds + | {Annotation} ‘cap’ id CaptureSetAndCtxBounds -- under captureChecking ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’] ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’ @@ -419,6 +436,8 @@ LocalModifier ::= ‘abstract’ | ‘infix’ | ‘erased’ | ‘tracked’ + | ‘mut’ -- under captureChecking + | ‘cap’ -- under captureChecking AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ id ‘]’ @@ -448,6 +467,7 @@ RefineDcl ::= ‘val’ ValDcl | ‘var’ ValDcl | ‘def’ DefDcl | ‘type’ {nl} TypeDef + | ‘cap’ ‘type’ {nl} CapDef -- under captureChecking ValDcl ::= ids ‘:’ Type DefDcl ::= DefSig ‘:’ Type @@ -455,6 +475,7 @@ Def ::= ‘val’ PatDef | ‘var’ PatDef | ‘def’ DefDef | ‘type’ {nl} TypeDef + | ‘cap’ ‘type’ {nl} CapDef -- under captureChecking | TmplDef PatDef ::= ids [‘:’ Type] [‘=’ Expr] | Pattern2 [‘:’ Type] [‘=’ Expr] PatDef(_, pats, tpe?, expr) @@ -463,6 +484,7 @@ DefDef ::= DefSig [‘:’ Type] [‘=’ Expr] DefSig ::= id [DefParamClauses] [DefImplicitClause] TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds TypeDefTree(_, name, tparams, bound [‘=’ Type] +CapDef ::= id CaptureSetAndCtxBounds [‘=’ CaptureSet] -- under captureChecking TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef diff --git a/docs/_docs/reference/experimental/cc.md b/docs/_docs/reference/experimental/cc.md index ff480ffb638b..cfb3097205cf 100644 --- a/docs/_docs/reference/experimental/cc.md +++ b/docs/_docs/reference/experimental/cc.md @@ -726,34 +726,45 @@ Reach capabilities take the form `x*` where `x` is syntactically a regular capab It is sometimes convenient to write operations that are parameterized with a capture set of capabilities. For instance consider a type of event sources `Source` on which `Listener`s can be registered. Listeners can hold certain capabilities, which show up as a parameter to `Source`: ```scala - class Source[X^]: - private var listeners: Set[Listener^{X^}] = Set.empty - def register(x: Listener^{X^}): Unit = - listeners += x +class Source[cap X]: + private var listeners: Set[Listener^{X}] = Set.empty + def register(x: Listener^{X}): Unit = + listeners += x - def allListeners: Set[Listener^{X^}] = listeners + def allListeners: Set[Listener^{X}] = listeners ``` -The type variable `X^` can be instantiated with a set of capabilities. It can occur in capture sets in its scope. For instance, in the example above -we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X^`. The `register` method takes a listener of this type +The type variable `cap X` (with `cap` being a soft modifier) can be instantiated with a set of capabilities. It can occur in capture sets in its scope. For instance, in the example above +we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X`. The `register` method takes a listener of this type and assigns it to the variable. -Capture set variables `X^` are represented as regular type variables with a -special upper bound `CapSet`. For instance, `Source` could be equivalently +Capture-set variables `cap X` without user-annotated bounds by default range over the interval `>: {} <: {caps.cap}` which is the universe of capture sets instead of regular types. + +Under the hood, such capture-set variables are represented as regular type variables within the special interval + `>: CapSet <: CapSet^`. +For instance, `Source` from above could be equivalently defined as follows: ```scala - class Source[X <: CapSet^]: - ... +class Source[X >: CapSet <: CapSet^]: + ... ``` -`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only purpose is to identify capture set type variables and types. Capture set variables can be inferred like regular type variables. When they should be instantiated explicitly one uses a capturing -type `CapSet`. For instance: +`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only +purpose is to identify type variables which are capture sets. In non-capture-checked +usage contexts, the type system will treat `CapSet^{a}` and `CapSet^{a,b}` as the type `CapSet`, whereas +with capture checking enabled, it will take the annotated capture sets into account, +so that `CapSet^{a}` and `CapSet^{a,b}` are distinct. +This representation based on `CapSet` is subject to change and +its direct use is discouraged. + +Capture-set variables can be inferred like regular type variables. When they should be instantiated +explicitly one supplies a concrete capture set. For instance: ```scala - class Async extends caps.Capability +class Async extends caps.Capability - def listener(async: Async): Listener^{async} = ??? +def listener(async: Async): Listener^{async} = ??? - def test1(async1: Async, others: List[Async]) = - val src = Source[CapSet^{async1, others*}] - ... +def test1(async1: Async, others: List[Async]) = + val src = Source[{async1, others*}] + ... ``` Here, `src` is created as a `Source` on which listeners can be registered that refer to the `async` capability or to any of the capabilities in list `others`. So we can continue the example code above as follows: ```scala @@ -761,6 +772,107 @@ Here, `src` is created as a `Source` on which listeners can be registered that r others.map(listener).foreach(src.register) val ls: Set[Listener^{async, others*}] = src.allListeners ``` +A common use-case for explicit capture parameters is describing changes to the captures of mutable fields, such as concatenating +effectful iterators: +```scala +class ConcatIterator[A, cap C](var iterators: mutable.List[IterableOnce[A]^{C}]): + def concat(it: IterableOnce[A]^): ConcatIterator[A, {this.C, it}]^{this, it} = + iterators ++= it // ^ + this // track contents of `it` in the result +``` +In such a scenario, we also should ensure that any pre-existing alias of a `ConcatIterator` object should become +inaccessible after invoking its `concat` method. This is achieved with mutation and separation tracking which are +currently in development. + +Finally, analogously to type parameters, we can lower- and upper-bound capability parameters where the bounds consist of concrete capture sets: +```scala +def main() = + // We can close over anything branded by the 'trusted' capability, but nothing else + def runSecure[cap C >: {trusted} <: {trusted}](block: () ->{C} Unit): Unit = ... + + // This is a 'brand" capability to mark what can be mentioned in trusted code + object trusted extends caps.Capability + + // These capabilities are trusted: + val trustedLogger: Logger^{trusted} + val trustedChannel: Channel[String]^{trusted} + // These aren't: + val untrustedLogger: Logger^ + val untrustedChannel: Channel[String]^ + + runSecure: () => + trustedLogger.log("Hello from trusted code") // ok + + runSecure: () => + trustedChannel.send("I can send") // ok + trustedLogger.log(trustedChannel.recv()) // ok + + runSecure: () => "I am pure and that's ok" // ok + + runSecure: () => + untrustedLogger.log("I can't be used") // error + untrustedChannel.send("I can't be used") // error +``` +The idea is that every capability derived from the marker capability `trusted` (and only those) are eligible to be used in the `block` closure +passed to `runSecure`. We can enforce this by an explicit capability parameter `C` constraining the possible captures of `block` to the interval `>: {trusted} <: {trusted}`. + +Note that since capabilities of function types are covariant, we could have equivalently specified `runSecure`'s signature using implicit capture polymorphism to achieve the same behavior: +```scala +def runSecure(block: () ->{trusted} Unit): Unit +``` + +## Capability Members + +Just as parametrization by types can be equally expressed with type members, we could +also define the `Source[cap X]` class above could using a _capability member_: +```scala +class Source: + cap type X + private var listeners: Set[Listener^{this.X}] = Set.empty + ... // as before +``` +Here, we can refer to capability members using paths in capture sets (such as `{this.X}`). Similarly to type members, +capability members can be upper- and lower-bounded with capture sets: +```scala +trait Thread: + cap type Cap + def run(block: () ->{this.Cap} -> Unit): Unit + +trait GPUThread extends Thread: + cap type Cap >: {cudaMalloc, cudaFree} <: {caps.cap} +``` +Since `caps.cap` is the top element for subcapturing, we could have also left out the +upper bound: `cap type Cap >: {cudaMalloc, cudaFree}`. + +We conclude with a more advanced example, showing how capability members and paths to these members can prevent leakage +of labels for lexically-delimited control operators: +```scala +trait Label extends Capability: + cap type Fv // the capability set occurring freely in the `block` passed to `boundary` below. + +def boundary[T, cap C](block: Label{cap type Fv = {C} } ->{C} T): T = ??? // ensure free caps of label and block match +def suspend[U](label: Label)[cap D <: {label.Fv}](handler: () ->{D} U): U = ??? // may only capture the free capabilities of label + +def test = + val x = 1 + boundary: outer => + val y = 2 + boundary: inner => + val z = 3 + val w = suspend(outer) {() => z} // ok + val v = suspend(inner) {() => y} // ok + val u = suspend(inner): () => + suspend(outer) {() => w + v} // ok + y + suspend(outer): () => + println(inner) // error (would leak the inner label) + x + y + z +``` +A key property is that `suspend` (think `shift` from delimited continuations) targeting a specific label (such as `outer`) should not accidentally close over labels from a nested `boundary` (such as `inner`), because they would escape their defining scope this way. +By leveraging capability polymorphism, capability members, and path-dependent capabilities, we can prevent such leaks from occurring at compile time: + +* `Label`s store the free capabilities `C` of the `block` passed to `boundary` in their capability member `Fv`. +* When suspending on a given label, the suspension handler can capture at most the capabilities that occur freely at the `boundary` that introduced the label. That prevents mentioning nested bound labels. ## Compilation Options diff --git a/tests/neg-custom-args/captures/branding.scala b/tests/neg-custom-args/captures/branding.scala new file mode 100644 index 000000000000..dcdac7a4ee82 --- /dev/null +++ b/tests/neg-custom-args/captures/branding.scala @@ -0,0 +1,39 @@ +import language.experimental.captureChecking +import caps.* + + +def main() = + trait Channel[T] extends caps.Capability: + def send(msg: T): Unit + def recv(): T + + trait Logger extends caps.Capability: + def log(msg: String): Unit + + // we can close over anything subsumed by the 'trusted' brand capability, but nothing else + def runSecure[cap C >: {trusted} <: {trusted}](block: () ->{C} Unit): Unit = block() + + // This is a 'brand" capability to mark what can be mentioned in trusted code + object trusted extends caps.Capability + + val trustedLogger: Logger^{trusted} = ??? + val trustedChannel: Channel[String]^{trusted} = ??? + + val untrustedLogger: Logger^ = ??? + val untrustedChannel: Channel[String]^ = ??? + + runSecure: () => + trustedLogger.log("Hello from trusted code") // ok + + runSecure: () => + trustedChannel.send("I can send") + trustedLogger.log(trustedChannel.recv()) // ok + + runSecure: () => + "I am pure" // ok + + runSecure: () => // error + untrustedLogger.log("I can't be used here") + + runSecure: () => // error + untrustedChannel.send("I can't be used here") \ No newline at end of file diff --git a/tests/neg-custom-args/captures/branding2.scala b/tests/neg-custom-args/captures/branding2.scala new file mode 100644 index 000000000000..eb27d863c745 --- /dev/null +++ b/tests/neg-custom-args/captures/branding2.scala @@ -0,0 +1,39 @@ +import language.experimental.captureChecking +import caps.* + + +def main() = + trait Channel[T] extends caps.Capability: + def send(msg: T): Unit + def recv(): T + + trait Logger extends caps.Capability: + def log(msg: String): Unit + + // we can close over anything subsumed by the 'trusted' brand capability, but nothing else + def runSecure(block: () ->{trusted} Unit): Unit = block() + + // This is a 'brand" capability to mark what can be mentioned in trusted code + object trusted extends caps.Capability + + val trustedLogger: Logger^{trusted} = ??? + val trustedChannel: Channel[String]^{trusted} = ??? + + val untrustedLogger: Logger^ = ??? + val untrustedChannel: Channel[String]^ = ??? + + runSecure: () => + trustedLogger.log("Hello from trusted code") // ok + + runSecure: () => + trustedChannel.send("I can send") + trustedLogger.log(trustedChannel.recv()) // ok + + runSecure: () => + "I am pure" : Unit // ok + + runSecure: () => + untrustedLogger.log("I can't be used here") // error + + runSecure: () => + untrustedChannel.send("I can't be used here") // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-bound.scala b/tests/neg-custom-args/captures/capset-bound.scala index c00f61240dea..d20847729a77 100644 --- a/tests/neg-custom-args/captures/capset-bound.scala +++ b/tests/neg-custom-args/captures/capset-bound.scala @@ -5,14 +5,14 @@ class IO case class File(io: IO^) def test(io1: IO^, io2: IO^) = - def f[C >: CapSet^{io1} <: CapSet^](file: File^{C^}) = ??? + def f[cap C >: {io1}](file: File^{C}) = ??? val f1: File^{io1} = ??? val f2: File^{io2} = ??? val f3: File^{io1, io2} = ??? - f[CapSet^{io1}](f1) - f[CapSet^{io1}](f2) // error - f[CapSet^{io1}](f3) // error - f[CapSet^{io2}](f2) // error - f[CapSet^{io1, io2}](f1) - f[CapSet^{io1, io2}](f2) - f[CapSet^{io1, io2}](f3) \ No newline at end of file + f[{io1}](f1) + f[{io1}](f2) // error + f[{io1}](f3) // error + f[{io2}](f2) // error + f[{io1, io2}](f1) + f[{io1, io2}](f2) + f[{io1, io2}](f3) \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-bound2.scala b/tests/neg-custom-args/captures/capset-bound2.scala index 679606f0e43c..5148fdf748b2 100644 --- a/tests/neg-custom-args/captures/capset-bound2.scala +++ b/tests/neg-custom-args/captures/capset-bound2.scala @@ -2,12 +2,11 @@ import caps.* class IO -def f[C^](io: IO^{C^}) = ??? +def f[cap C](io: IO^{C}) = ??? def test = - f[CapSet](???) - f[CapSet^{}](???) - f[CapSet^](???) + f[{}](???) + f[{}](???) + f[{cap}](???) f[Nothing](???) // error f[String](???) // error - \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members.scala b/tests/neg-custom-args/captures/capset-members.scala index 540216852a43..9d197a206e95 100644 --- a/tests/neg-custom-args/captures/capset-members.scala +++ b/tests/neg-custom-args/captures/capset-members.scala @@ -1,30 +1,29 @@ import caps.* -trait Abstract[X^]: - type C >: X <: CapSet^ +trait Abstract[cap X]: + cap type C >: {X} // Don't test the return type using Unit, because it is a pure type. - def boom(): AnyRef^{C^} + def boom(): AnyRef^{C} -class Concrete extends Abstract[CapSet^{}]: - type C = CapSet^{} +class Concrete extends Abstract[{}]: + cap type C = {} // TODO: Why do we get error without the return type here? def boom(): AnyRef = new Object -class Concrete2 extends Abstract[CapSet^{}]: - type C = CapSet^{} +class Concrete2 extends Abstract[{}]: + cap type C = {} def boom(): AnyRef^ = new Object // error -class Concrete3 extends Abstract[CapSet^{}]: +class Concrete3 extends Abstract[{}]: def boom(): AnyRef = new Object -class Concrete4(a: AnyRef^) extends Abstract[CapSet^{a}]: - type C = CapSet // error +class Concrete4(a: AnyRef^) extends Abstract[{a}]: + cap type C = {} // error def boom(): AnyRef^{a} = a // error -class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: - type C = CapSet^{a} +class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]: + cap type C = {a} def boom(): AnyRef^{b} = b // error -class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]: - def boom(): AnyRef^{b} = b // error - \ No newline at end of file +class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]: + def boom(): AnyRef^{b} = b // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capset-members2.check b/tests/neg-custom-args/captures/capset-members2.check new file mode 100644 index 000000000000..6e67ce3d39a3 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members2.check @@ -0,0 +1,4 @@ +-- Error: tests/neg-custom-args/captures/capset-members2.scala:4:12 ---------------------------------------------------- +4 | cap type C[T] // error + | ^ + | 'cap type' declarations cannot have type parameters diff --git a/tests/neg-custom-args/captures/capset-members2.scala b/tests/neg-custom-args/captures/capset-members2.scala new file mode 100644 index 000000000000..ab8fab3716d8 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members2.scala @@ -0,0 +1,5 @@ +import caps.* + +trait Foo: + cap type C[T] // error + diff --git a/tests/neg-custom-args/captures/capset-members3.check b/tests/neg-custom-args/captures/capset-members3.check new file mode 100644 index 000000000000..74d3cf82f78e --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members3.check @@ -0,0 +1,6 @@ +-- [E209] Syntax Error: tests/neg-custom-args/captures/capset-members3.scala:4:13 -------------------------------------- +4 | cap type C _ // error + | ^ + | =, >:, or <: expected, but '_' found + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/capset-members3.scala b/tests/neg-custom-args/captures/capset-members3.scala new file mode 100644 index 000000000000..02e377a8d553 --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members3.scala @@ -0,0 +1,5 @@ +import caps.* + +trait Foo: + cap type C _ // error + diff --git a/tests/neg-custom-args/captures/capset-members4.scala b/tests/neg-custom-args/captures/capset-members4.scala new file mode 100644 index 000000000000..0da33199fbcb --- /dev/null +++ b/tests/neg-custom-args/captures/capset-members4.scala @@ -0,0 +1,27 @@ +import language.experimental.captureChecking +import language.experimental.modularity +import caps.* + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + val z: Any^ = ??? + def onlyWithZ[cap C](using c: Contains[C, z.type]) = ??? + + trait IncludesZ[cap C]: + val c: Contains[C, z.type] + + trait Foo: + cap type C >: {x} <: {x,y,z} : IncludesZ + + val foo: Foo = ??? +/* new Foo { + override given IncludesZ[C]: // FIXME: doesn't work yet + val c: Contains[C, z.type] = summon + cap type C = {x,z} + } */ + onlyWithZ(using foo.C.c) + onlyWithZ[{z}] + onlyWithZ[{x,z}] + onlyWithZ[{x,y,z}] + onlyWithZ[{x,y}] // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/capture-parameters.scala b/tests/neg-custom-args/captures/capture-parameters.scala index d59305ae0cb8..ba01ae55d3a8 100644 --- a/tests/neg-custom-args/captures/capture-parameters.scala +++ b/tests/neg-custom-args/captures/capture-parameters.scala @@ -2,8 +2,7 @@ import caps.* class C -def test[X^, Y^, Z >: X <: Y](x: C^{X^}, y: C^{Y^}, z: C^{Z^}) = - val x2z: C^{Z^} = x - val z2y: C^{Y^} = z - val x2y: C^{Y^} = x // error - \ No newline at end of file +def test[cap X, cap Y, cap Z >: {X} <: {Y}](x: C^{X}, y: C^{Y}, z: C^{Z}) = + val x2z: C^{Z} = x + val z2y: C^{Y} = z + val x2y: C^{Y} = x // error diff --git a/tests/neg-custom-args/captures/capture-poly.scala b/tests/neg-custom-args/captures/capture-poly.scala index 88989b418726..ef4860d8090a 100644 --- a/tests/neg-custom-args/captures/capture-poly.scala +++ b/tests/neg-custom-args/captures/capture-poly.scala @@ -3,20 +3,20 @@ import caps.* trait Foo extends Capability trait CaptureSet: - type C >: CapSet <: CapSet^ + cap type C -def capturePoly[C^](a: Foo^{C^}): Foo^{C^} = a -def capturePoly2(c: CaptureSet)(a: Foo^{c.C^}): Foo^{c.C^} = a +def capturePoly[cap C](a: Foo^{C}): Foo^{C} = a +def capturePoly2(c: CaptureSet)(a: Foo^{c.C}): Foo^{c.C} = a def test = val x: Foo^ = ??? val y: Foo^ = ??? object X extends CaptureSet: - type C = CapSet^{x} + cap type C = {x} - val z1: Foo^{X.C^} = x - val z2: Foo^{X.C^} = y // error + val z1: Foo^{X.C} = x + val z2: Foo^{X.C} = y // error val z3: Foo^{x} = capturePoly(x) val z4: Foo^{x} = capturePoly(y) // error diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping.scala b/tests/neg-custom-args/captures/capture-vars-subtyping.scala index 1986a0aa33fc..fc3e9fe27ff5 100644 --- a/tests/neg-custom-args/captures/capture-vars-subtyping.scala +++ b/tests/neg-custom-args/captures/capture-vars-subtyping.scala @@ -1,48 +1,46 @@ import language.experimental.captureChecking import caps.* -def test[C^] = +def test[cap C] = val a: C = ??? - val b: CapSet^{C^} = a + val b: CapSet^{C} = a val c: C = b - val d: CapSet^{C^, c} = a + val d: CapSet^{C, c} = a -// TODO: make "CapSet-ness" of type variables somehow contagious? -// Then we don't have to spell out the bounds explicitly... -def testTrans[C^, D >: CapSet <: C, E >: CapSet <: D, F >: C <: CapSet^] = +def testTrans[cap C, cap D <: {C}, cap E <: {D}, cap F >: {C}] = val d1: D = ??? - val d2: CapSet^{D^} = d1 + val d2: CapSet^{D} = d1 val d3: D = d2 val e1: E = ??? - val e2: CapSet^{E^} = e1 + val e2: CapSet^{E} = e1 val e3: E = e2 val d4: D = e1 val c1: C = d1 val c2: C = e1 val f1: F = c1 - val d_e_f1: CapSet^{D^,E^,F^} = d1 - val d_e_f2: CapSet^{D^,E^,F^} = e1 - val d_e_f3: CapSet^{D^,E^,F^} = f1 + val d_e_f1: CapSet^{D,E,F} = d1 + val d_e_f2: CapSet^{D,E,F} = e1 + val d_e_f3: CapSet^{D,E,F} = f1 val f2: F = d_e_f1 val c3: C = d_e_f1 // error val c4: C = f1 // error val e4: E = f1 // error val e5: E = d1 // error - val c5: CapSet^{C^} = e1 + val c5: CapSet^{C} = e1 trait A[+T] trait B[-C] -def testCong[C^, D^] = +def testCong[cap C, cap D] = val a: A[C] = ??? - val b: A[CapSet^{C^}] = a - val c: A[CapSet^{D^}] = a // error - val d: A[CapSet^{C^,D^}] = a + val b: A[{C}] = a + val c: A[{D}] = a // error + val d: A[{C,D}] = a val e: A[C] = d // error val f: B[C] = ??? - val g: B[CapSet^{C^}] = f + val g: B[{C}] = f val h: B[C] = g - val i: B[CapSet^{C^,D^}] = h // error + val i: B[{C,D}] = h // error val j: B[C] = i diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala index 205451ee41ed..93d080e83a70 100644 --- a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala +++ b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala @@ -8,37 +8,37 @@ trait BoundsTest: val b: Bar^ = ??? - def testTransMixed[A^, - B >: CapSet <: A, - C >: CapSet <: CapSet^{B^}, - D >: CapSet <: C, - E >: CapSet <: CapSet^{D^}, - F >: CapSet <: CapSet^{A^,b}, - X >: CapSet <: CapSet^{F^,D^}, - Y >: CapSet^{F^} <: CapSet^{F^,A^,b}, - Z >: CapSet^{b} <: CapSet^{b,Y^}] = + def testTransMixed[cap A, + cap B <: {A}, + cap C <: {B}, + cap D <: {C}, + cap E <: {D}, + cap F <: {A,b}, + cap X <: {F,D}, + cap Y >: {F} <: {F,A,b}, + cap Z >: {b} <: {b,Y}, T <: List[Bar^{Z}]] = val e: E = ??? - val e2: CapSet^{E^} = e + val e2: CapSet^{E} = e val ed: D = e - val ed2: CapSet^{D^} = e + val ed2: CapSet^{D} = e val ec: C = e - val ec2: CapSet^{C^} = e + val ec2: CapSet^{C} = e val eb: B = e - val eb2: CapSet^{B^} = e + val eb2: CapSet^{B} = e val ea: A = e - val ea2: CapSet^{A^} = e + val ea2: CapSet^{A} = e val ex: X = e // error - val ex2: CapSet^{X^} = e // error + val ex2: CapSet^{X} = e // error val f: F = ??? - val f2: CapSet^{F^} = f + val f2: CapSet^{F} = f val y: Y = f - val y2: CapSet^{Y^} = f + val y2: CapSet^{Y} = f val cb: CapSet^{b} = ??? val z: Z = cb - val z2: CapSet^{Z^} = cb + val z2: CapSet^{Z} = cb def callTransMixed = val x, y, z: Bar^ = ??? - testTransMixed[CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{b,x,y,z}] - testTransMixed[CapSet^{x,y,z}, CapSet^{x,y}, CapSet^{x,y}, CapSet^{x}, CapSet^{}, CapSet^{b,x}, CapSet^{b}, CapSet^{b,x}, CapSet^{b}] - testTransMixed[CapSet^{x,y,z}, CapSet^{x,y}, CapSet^{x,y}, CapSet^{x}, CapSet^{}, CapSet^{b,x}, CapSet^{b}, CapSet^{b,x}, CapSet^{b,x,y,z}] // error + testTransMixed[{x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {x,y,z}, {b,x,y,z}, List[Bar^{b}]] + testTransMixed[{x,y,z}, {x,y}, {x,y}, {x}, {}, {b,x}, {b}, {b,x}, {b}, List[Bar^{b}]] + testTransMixed[{x,y,z}, {x,y}, {x,y}, {x}, {}, {b,x}, {b}, {b,x}, {b,x,y,z}, List[Bar^{}]] // error diff --git a/tests/neg-custom-args/captures/cc-poly-1.scala b/tests/neg-custom-args/captures/cc-poly-1.scala index 580b124bc8f3..fa05a447af0b 100644 --- a/tests/neg-custom-args/captures/cc-poly-1.scala +++ b/tests/neg-custom-args/captures/cc-poly-1.scala @@ -6,7 +6,7 @@ object Test: class C extends Capability class D - def f[X^](x: D^{X^}): D^{X^} = x + def f[cap X](x: D^{X}): D^{X} = x def test(c1: C, c2: C) = f[Any](D()) // error diff --git a/tests/neg-custom-args/captures/cc-poly-2.check b/tests/neg-custom-args/captures/cc-poly-2.check index 90568de385b3..f2f31ab85f39 100644 --- a/tests/neg-custom-args/captures/cc-poly-2.check +++ b/tests/neg-custom-args/captures/cc-poly-2.check @@ -1,8 +1,8 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-2.scala:14:19 ------------------------------------ -14 | f[CapSet^{c1}](d) // error - | ^ - | Found: (d : Test.D^) - | Required: Test.D^{c1} +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-2.scala:14:12 ------------------------------------ +14 | f[{c1}](d) // error + | ^ + | Found: (d : Test.D^) + | Required: Test.D^{c1} | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-2.scala:16:20 ------------------------------------ diff --git a/tests/neg-custom-args/captures/cc-poly-2.scala b/tests/neg-custom-args/captures/cc-poly-2.scala index c9249ba59437..323e2d1333a2 100644 --- a/tests/neg-custom-args/captures/cc-poly-2.scala +++ b/tests/neg-custom-args/captures/cc-poly-2.scala @@ -6,11 +6,11 @@ object Test: class C extends Capability class D - def f[X^](x: D^{X^}): D^{X^} = x + def f[cap X](x: D^{X}): D^{X} = x def test(c1: C, c2: C) = val d: D^ = D() // f[Nothing](d) // already ruled out at typer - f[CapSet^{c1}](d) // error + f[{c1}](d) // error val x = f(d) val _: D^{c1} = x // error diff --git a/tests/neg-custom-args/captures/cc-poly-source.scala b/tests/neg-custom-args/captures/cc-poly-source.scala index e08ea36a6fc9..0a42b7bbba8f 100644 --- a/tests/neg-custom-args/captures/cc-poly-source.scala +++ b/tests/neg-custom-args/captures/cc-poly-source.scala @@ -9,15 +9,15 @@ import caps.use class Listener - class Source[X^]: - private var listeners: Set[Listener^{X^}] = Set.empty - def register(x: Listener^{X^}): Unit = + class Source[cap X]: + private var listeners: Set[Listener^{X}] = Set.empty + def register(x: Listener^{X}): Unit = listeners += x - def allListeners: Set[Listener^{X^}] = listeners + def allListeners: Set[Listener^{X}] = listeners def test1(lbl1: Label^, lbl2: Label^) = - val src = Source[CapSet^{lbl1, lbl2}] + val src = Source[{lbl1, lbl2}] def l1: Listener^{lbl1} = ??? val l2: Listener^{lbl2} = ??? src.register{l1} @@ -31,7 +31,7 @@ import caps.use // we get an error here because we no longer allow contravariant cap // to subsume other capabilities. The problem can be solved by declaring // Label a SharedCapability, see cc-poly-source-capability.scala - val src = Source[CapSet^{lbls*}] + val src = Source[{lbls*}] for l <- listeners do src.register(l) val ls = src.allListeners diff --git a/tests/neg-custom-args/captures/i21313.scala b/tests/neg-custom-args/captures/i21313.scala index 01bedb10aefd..e81790b60dff 100644 --- a/tests/neg-custom-args/captures/i21313.scala +++ b/tests/neg-custom-args/captures/i21313.scala @@ -1,15 +1,15 @@ import caps.CapSet trait Async: - def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, Cap]^): T + def await[T, cap Cap](using caps.Contains[Cap, this.type])(src: Source[T, {Cap}]^): T def foo(x: Async) = x.await(???) // error -trait Source[+T, Cap^]: - final def await(using ac: Async^{Cap^}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap. +trait Source[+T, cap Cap]: + final def await(using ac: Async^{Cap}) = ac.await[T, {Cap}](this) // Contains[Cap, ac] is assured because {ac} <: Cap. def test(using ac1: Async^, ac2: Async^, x: String) = - val src1 = new Source[Int, CapSet^{ac1}] {} + val src1 = new Source[Int, {ac1}] {} ac1.await(src1) // ok - val src2 = new Source[Int, CapSet^{ac2}] {} + val src2 = new Source[Int, {ac2}] {} ac1.await(src2) // error diff --git a/tests/neg-custom-args/captures/i21347.scala b/tests/neg-custom-args/captures/i21347.scala index 54fe859caedd..9328130963ee 100644 --- a/tests/neg-custom-args/captures/i21347.scala +++ b/tests/neg-custom-args/captures/i21347.scala @@ -1,6 +1,6 @@ import language.experimental.captureChecking -def runOps[C^](ops: List[() ->{C^} Unit]): Unit = +def runOps[cap C](ops: List[() ->{C} Unit]): Unit = ops.foreach: op => // error op() diff --git a/tests/neg-custom-args/captures/i21868.scala b/tests/neg-custom-args/captures/i21868.scala index 876b68ac90a4..d726fb1fc716 100644 --- a/tests/neg-custom-args/captures/i21868.scala +++ b/tests/neg-custom-args/captures/i21868.scala @@ -2,11 +2,11 @@ import caps.* trait AbstractWrong: type C <: CapSet - def f(): Unit^{C^} // error + def f(): Unit^{C} // error trait Abstract1: - type C >: CapSet <: CapSet^ - def f(): Unit^{C^} + cap type C + def f(): Unit^{C} // class Abstract2: // type C^ diff --git a/tests/neg-custom-args/captures/i21868b.scala b/tests/neg-custom-args/captures/i21868b.scala index 70f4e9c9d59c..2c4420321c74 100644 --- a/tests/neg-custom-args/captures/i21868b.scala +++ b/tests/neg-custom-args/captures/i21868b.scala @@ -6,44 +6,44 @@ class IO class File trait Abstract: - type C >: CapSet <: CapSet^ - def f(file: File^{C^}): Unit + cap type C + def f(file: File^{C}): Unit class Concrete1 extends Abstract: - type C = CapSet + cap type C = {} def f(file: File) = () class Concrete2(io: IO^) extends Abstract: - type C = CapSet^{io} + cap type C = {io} def f(file: File^{io}) = () class Concrete3(io: IO^) extends Abstract: - type C = CapSet^{io} + cap type C = {io} def f(file: File) = () // error trait Abstract2(tracked val io: IO^): - type C >: CapSet <: CapSet^{io} - def f(file: File^{C^}): Unit + cap type C <: {io} + def f(file: File^{C}): Unit class Concrete4(io: IO^) extends Abstract2(io): - type C = CapSet + cap type C = {} def f(file: File) = () class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1): - type C = CapSet^{io2} // error + cap type C = {io2} // error def f(file: File^{io2}) = () -trait Abstract3[X^]: - type C >: CapSet <: X - def f(file: File^{C^}): Unit +trait Abstract3[cap X]: + cap type C <: {X} + def f(file: File^{C}): Unit -class Concrete6(io: IO^) extends Abstract3[CapSet^{io}]: - type C = CapSet +class Concrete6(io: IO^) extends Abstract3[{io}]: + cap type C = {} def f(file: File) = () -class Concrete7(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]: - type C = CapSet^{io2} // error +class Concrete7(io1: IO^, io2: IO^) extends Abstract3[{io1}]: + cap type C = {io2} // error def f(file: File^{io2}) = () -class Concrete8(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]: +class Concrete8(io1: IO^, io2: IO^) extends Abstract3[{io1}]: def f(file: File^{io2}) = () // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i22005.scala b/tests/neg-custom-args/captures/i22005.scala index 689246d6f835..4d70badcaf5e 100644 --- a/tests/neg-custom-args/captures/i22005.scala +++ b/tests/neg-custom-args/captures/i22005.scala @@ -4,6 +4,6 @@ import caps.* class IO class File(io: IO^) -class Handler[C^]: - def f(file: File^): File^{C^} = file // error - def g(@consume file: File^{C^}): File^ = file // ok +class Handler[cap C]: + def f(file: File^): File^{C} = file // error + def g(@consume file: File^{C}): File^ = file // ok diff --git a/tests/neg-custom-args/captures/lexical-control.scala b/tests/neg-custom-args/captures/lexical-control.scala new file mode 100644 index 000000000000..ecbae49d4542 --- /dev/null +++ b/tests/neg-custom-args/captures/lexical-control.scala @@ -0,0 +1,27 @@ +import language.experimental.captureChecking +import caps.* + +trait Label extends Capability: + cap type Fv // the capability set occurring freely in the `block` passed to `boundary` below. + +def boundary[T, cap C](block: Label{cap type Fv = {C} } ->{C} T): T = ??? // link label and block capture set +def suspend[U](label: Label)[cap D <: {label.Fv}](handler: () ->{D} U): U = ??? // note the path + +def test = + val x = 1 + boundary: outer => + val y = 2 + boundary: inner => + val z = 3 + val w = suspend(outer) {() => z} // ok + val v = suspend(inner) {() => y} // ok + val u = suspend(inner): () => + suspend(outer) {() => y} // ok + suspend(outer) {() => y} // ok + y + suspend(outer) { () => // error + suspend(outer) {() => y } + } + suspend(outer): () => // error (leaks the inner label) + println(inner) + x + y + z \ No newline at end of file diff --git a/tests/neg-custom-args/captures/polyCaptures.check b/tests/neg-custom-args/captures/polyCaptures.check index 8173828b7bc8..57812e629e70 100644 --- a/tests/neg-custom-args/captures/polyCaptures.check +++ b/tests/neg-custom-args/captures/polyCaptures.check @@ -1,8 +1,8 @@ --- Error: tests/neg-custom-args/captures/polyCaptures.scala:4:22 ------------------------------------------------------- -4 |val runOpsCheck: [C^] -> (ops: List[() ->{C^} Unit]) ->{C^} Unit = runOps // error - | ^ +-- Error: tests/neg-custom-args/captures/polyCaptures.scala:4:25 ------------------------------------------------------- +4 |val runOpsCheck: [cap C] -> (ops: List[() ->{C} Unit]) ->{C} Unit = runOps // error + | ^ | Implementation restriction: polymorphic function types cannot wrap function types that have capture sets --- Error: tests/neg-custom-args/captures/polyCaptures.scala:5:23 ------------------------------------------------------- -5 |val runOpsCheck2: [C^] => (ops: List[() ->{C^} Unit]) ->{C^} Unit = runOps // error - | ^ +-- Error: tests/neg-custom-args/captures/polyCaptures.scala:5:26 ------------------------------------------------------- +5 |val runOpsCheck2: [cap C] => (ops: List[() ->{C} Unit]) ->{C} Unit = runOps // error + | ^ | Implementation restriction: polymorphic function types cannot wrap function types that have capture sets diff --git a/tests/neg-custom-args/captures/polyCaptures.scala b/tests/neg-custom-args/captures/polyCaptures.scala index 776af95e5dcf..d92ac36c9987 100644 --- a/tests/neg-custom-args/captures/polyCaptures.scala +++ b/tests/neg-custom-args/captures/polyCaptures.scala @@ -1,7 +1,7 @@ class Box[X](val elem: X) -val runOps = [C^] => (b: Box[() ->{C^} Unit]) => b.elem() -val runOpsCheck: [C^] -> (ops: List[() ->{C^} Unit]) ->{C^} Unit = runOps // error -val runOpsCheck2: [C^] => (ops: List[() ->{C^} Unit]) ->{C^} Unit = runOps // error +val runOps = [cap C] => (b: Box[() ->{C} Unit]) => b.elem() +val runOpsCheck: [cap C] -> (ops: List[() ->{C} Unit]) ->{C} Unit = runOps // error +val runOpsCheck2: [cap C] => (ops: List[() ->{C} Unit]) ->{C} Unit = runOps // error diff --git a/tests/neg-custom-args/captures/use-capset.check b/tests/neg-custom-args/captures/use-capset.check index 4897698e336d..0e25f526f12f 100644 --- a/tests/neg-custom-args/captures/use-capset.check +++ b/tests/neg-custom-args/captures/use-capset.check @@ -1,17 +1,17 @@ --- Error: tests/neg-custom-args/captures/use-capset.scala:5:50 --------------------------------------------------------- -5 |private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error - | ^^^^^^^ +-- Error: tests/neg-custom-args/captures/use-capset.scala:6:52 --------------------------------------------------------- +6 |private def g[cap C] = (xs: List[Object^{C}]) => xs.head // error + | ^^^^^^^ | Capture set parameter C leaks into capture scope of method g. | To allow this, the type C should be declared with a @use annotation --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:11:22 ----------------------------------- -11 | val _: () -> Unit = h // error: should be ->{io} +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:12:22 ----------------------------------- +12 | val _: () -> Unit = h // error: should be ->{io} | ^ | Found: (h : () ->{io} Unit) | Required: () -> Unit | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:13:50 ----------------------------------- -13 | val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:14:50 ----------------------------------- +14 | val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} | ^^ | Found: (h2 : () ->? List[box Object^{io}]^{} ->{io} Object^{io}) | Required: () -> List[box Object^{io}] -> Object^{io} diff --git a/tests/neg-custom-args/captures/use-capset.scala b/tests/neg-custom-args/captures/use-capset.scala index 74288d616396..80a93d861219 100644 --- a/tests/neg-custom-args/captures/use-capset.scala +++ b/tests/neg-custom-args/captures/use-capset.scala @@ -1,14 +1,15 @@ -import caps.{use, CapSet} +import language.experimental.captureChecking +import caps.use -def f[C^](@use xs: List[Object^{C^}]): Unit = ??? +def f[cap C](@use xs: List[Object^{C}]): Unit = ??? -private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error +private def g[cap C] = (xs: List[Object^{C}]) => xs.head // error -private def g2[@use C^] = (xs: List[Object^{C^}]) => xs.head // ok +private def g2[@use cap C] = (xs: List[Object^{C}]) => xs.head // ok def test(io: Object^)(@use xs: List[Object^{io}]): Unit = val h = () => f(xs) val _: () -> Unit = h // error: should be ->{io} - val h2 = () => g[CapSet^{io}] + val h2 = () => g[{io}] val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} diff --git a/tests/pending/cap-paramlists8.scala b/tests/pending/cap-paramlists8.scala new file mode 100644 index 000000000000..5151629283f6 --- /dev/null +++ b/tests/pending/cap-paramlists8.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val baz3 = (i: Int) => [cap C, cap D <: {C}, cap E <: {C,x}] => () => [cap F >: {x,y} <: {C,E} : Ctx] => (x: Int) => 1 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists.scala b/tests/pos-custom-args/captures/cap-paramlists.scala new file mode 100644 index 000000000000..c81bcd5b5440 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists.scala @@ -0,0 +1,23 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + def foo[cap A >: {y} <: {x}, + cap B, + cap C <: {x}, + cap D : Ctx, + cap E <: {C}, + cap F <: {C}, + cap G <: {x, y}, + cap H >: {x} <: {x,y} : Ctx, T, U]()[cap I <: {y, G, H}, + cap J <: {O.z}, + cap K <: {x, O.z}, + cap L <: {x, y, O.z}, + cap M >: {x, y, O.z} <: {C} : Ctx, + cap N >: {x} <: {x}, + cap O >: {O.z} <: {O.z}] = ??? \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists2.scala b/tests/pos-custom-args/captures/cap-paramlists2.scala new file mode 100644 index 000000000000..284fa9c6b3d9 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists2.scala @@ -0,0 +1,6 @@ +import language.experimental.captureChecking + +trait Bar: + cap type C + +def useFoo[cap D](x: Bar { cap type C = {D} } ): Any^{x.C} = ??? \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists3.scala b/tests/pos-custom-args/captures/cap-paramlists3.scala new file mode 100644 index 000000000000..ebd3bc171a57 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists3.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val bar = [cap C, cap D <: {C}, cap E <: {C,x}, cap F >: {x,y} <: {C,E}] => (x: Int) => 1 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists4.scala b/tests/pos-custom-args/captures/cap-paramlists4.scala new file mode 100644 index 000000000000..16ef9b2ee935 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists4.scala @@ -0,0 +1,6 @@ +import language.experimental.captureChecking + +trait Foo[cap U, cap V, cap W]: + cap type C = {caps.cap} + cap type D = {caps.cap} + cap type E >: {V,W} <: {U} \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists5.scala b/tests/pos-custom-args/captures/cap-paramlists5.scala new file mode 100644 index 000000000000..28c00e0468da --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists5.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking +import language.experimental.namedTypeArguments + +def test2 = + val x: Any^ = ??? + def foo[cap A, cap B >: {A}, T, U](x: Int) = 1 + foo[{x}, {x}, Int, String](0) + foo[{}, {}, { def bar: Int }, { cap type D = {x} }](0) + trait Foo { cap type D } + foo[{}, {}, Foo, Foo](0) + foo[cap A = {x}, cap B = {x}](0) + foo[cap A = {x}](0) + foo[T = Int](0) + foo[T = Int, cap A = {x}](1) + foo[cap A = {x}, T = Int](1) + foo[cap B = {}, U = String, cap A = {}](1) \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists6.scala b/tests/pos-custom-args/captures/cap-paramlists6.scala new file mode 100644 index 000000000000..f230890e8374 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists6.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val baz = () => [cap C, cap D <: {C}, cap E <: {C,x}, cap F >: {x,y} <: {C,E} : Ctx] => (x: Int) => 1 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cap-paramlists7.scala b/tests/pos-custom-args/captures/cap-paramlists7.scala new file mode 100644 index 000000000000..9a46787e7e34 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlists7.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + val baz2 = (i: Int) => [cap C, cap D <: {C}, cap E <: {C,x}, cap F >: {x,y} <: {C,E} : Ctx] => (x: Int) => 1 \ No newline at end of file diff --git a/tests/pos-custom-args/captures/capset-members.scala b/tests/pos-custom-args/captures/capset-members.scala new file mode 100644 index 000000000000..eed7f4a2fa15 --- /dev/null +++ b/tests/pos-custom-args/captures/capset-members.scala @@ -0,0 +1,25 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + trait CaptureSet: + cap type A >: {y} <: {x} + cap type B = {x} + cap type C <: {x} + cap type D : Ctx + cap type E <: {C} + cap type F <: {C} + cap type G <: {x, y} + cap type H >: {x} <: {x,y} : Ctx + cap type I = {y, G, H} + cap type J = {O.z} + cap type K = {x, O.z} + cap type L <: {x, y, O.z} + cap type M >: {x, y, O.z} <: {C} + cap type N >: {x} <: {x} + cap type O >: {O.z} <: {O.z} \ No newline at end of file diff --git a/tests/pos-custom-args/captures/capset-members2.scala b/tests/pos-custom-args/captures/capset-members2.scala new file mode 100644 index 000000000000..4c5decc1fed1 --- /dev/null +++ b/tests/pos-custom-args/captures/capset-members2.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking + +trait Ctx[T] + +def test = + val x: Any^ = ??? + val y: Any^ = ??? + object O: + val z: Any^ = ??? + + abstract class Foo[cap A, T]: + cap type C <: {A} + abstract class Bar extends Foo[{x,y,O.z}, String]: + override cap type C <: {x,y} + class Baz extends Bar: + final override cap type C = {y} \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cc-poly-1.scala b/tests/pos-custom-args/captures/cc-poly-1.scala index ed32d94f7a99..4e1a7f383db2 100644 --- a/tests/pos-custom-args/captures/cc-poly-1.scala +++ b/tests/pos-custom-args/captures/cc-poly-1.scala @@ -7,13 +7,13 @@ import caps.{CapSet, Capability} class C extends Capability class D - def f[X^](x: D^{X^}): D^{X^} = x - def g[X^](x: D^{X^}, y: D^{X^}): D^{X^} = x - def h[X^](): D^{X^} = ??? + def f[cap X](x: D^{X}): D^{X} = x + def g[cap X](x: D^{X}, y: D^{X}): D^{X} = x + def h[cap X](): D^{X} = ??? def test(c1: C, c2: C) = val d: D^{c1, c2} = D() - val x = f[CapSet^{c1, c2}](d) + val x = f[{c1, c2}](d) val _: D^{c1, c2} = x val d1: D^{c1} = D() val d2: D^{c2} = D() diff --git a/tests/pos-custom-args/captures/cc-poly-source-capability.scala b/tests/pos-custom-args/captures/cc-poly-source-capability.scala index 6f987658923c..9dbcf85f9500 100644 --- a/tests/pos-custom-args/captures/cc-poly-source-capability.scala +++ b/tests/pos-custom-args/captures/cc-poly-source-capability.scala @@ -11,15 +11,15 @@ import caps.use class Listener - class Source[X^]: - private var listeners: Set[Listener^{X^}] = Set.empty - def register(x: Listener^{X^}): Unit = + class Source[cap X]: + private var listeners: Set[Listener^{X}] = Set.empty + def register(x: Listener^{X}): Unit = listeners += x - def allListeners: Set[Listener^{X^}] = listeners + def allListeners: Set[Listener^{X}] = listeners def test1(async1: Async, @use others: List[Async]) = - val src = Source[CapSet^{async1, others*}] + val src = Source[{async1, others*}] val _: Set[Listener^{async1, others*}] = src.allListeners val lst1 = listener(async1) val lsts = others.map(listener) diff --git a/tests/pos-custom-args/captures/cc-poly-varargs.scala b/tests/pos-custom-args/captures/cc-poly-varargs.scala index 8bd0dc89bc7a..31a9699ec773 100644 --- a/tests/pos-custom-args/captures/cc-poly-varargs.scala +++ b/tests/pos-custom-args/captures/cc-poly-varargs.scala @@ -1,13 +1,13 @@ -abstract class Source[+T, Cap^] +abstract class Source[+T, cap Cap] -extension[T, Cap^](src: Source[T, Cap]^) - def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{src, f} = ??? +extension[T, cap Cap](src: Source[T, {Cap}]^) + def transformValuesWith[U](f: (T -> U)^{Cap}): Source[U, {Cap}]^{src, f} = ??? -def race[T, Cap^](sources: Source[T, Cap]^{Cap^}*): Source[T, Cap]^{Cap^} = ??? +def race[T, cap Cap](sources: Source[T, {Cap}]^{Cap}*): Source[T, {Cap}]^{Cap} = ??? -def either[T1, T2, Cap^]( - src1: Source[T1, Cap]^{Cap^}, - src2: Source[T2, Cap]^{Cap^}): Source[Either[T1, T2], Cap]^{Cap^} = +def either[T1, T2, cap Cap]( + src1: Source[T1, {Cap}]^{Cap}, + src2: Source[T2, {Cap}]^{Cap}): Source[Either[T1, T2], {Cap}]^{Cap} = val left = src1.transformValuesWith(Left(_)) val right = src2.transformValuesWith(Right(_)) race(left, right) diff --git a/tests/pos-custom-args/captures/gears-problem-poly.scala b/tests/pos-custom-args/captures/gears-problem-poly.scala index fdbcf37a35a6..a56ef8577cb9 100644 --- a/tests/pos-custom-args/captures/gears-problem-poly.scala +++ b/tests/pos-custom-args/captures/gears-problem-poly.scala @@ -1,5 +1,5 @@ import language.experimental.captureChecking -import caps.{use, CapSet} +import caps.{use} trait Future[+T]: def await: T @@ -7,8 +7,8 @@ trait Future[+T]: trait Channel[+T]: def read(): Ok[T] -class Collector[T, C^](val futures: Seq[Future[T]^{C^}]): - val results: Channel[Future[T]^{C^}] = ??? +class Collector[T, cap C](val futures: Seq[Future[T]^{C}]): + val results: Channel[Future[T]^{C}] = ??? end Collector class Result[+T, +E]: @@ -17,13 +17,13 @@ class Result[+T, +E]: case class Err[+E](e: E) extends Result[Nothing, E] case class Ok[+T](x: T) extends Result[T, Nothing] -extension [T, C^](@use fs: Seq[Future[T]^{C^}]) +extension [T, cap C](@use fs: Seq[Future[T]^{C}]) def awaitAllPoly = val collector = Collector(fs) - val fut: Future[T]^{C^} = collector.results.read().get + val fut: Future[T]^{C} = collector.results.read().get extension [T](@use fs: Seq[Future[T]^]) def awaitAll = fs.awaitAllPoly def awaitExplicit[T](@use fs: Seq[Future[T]^]): Unit = - awaitAllPoly[T, CapSet^{fs*}](fs) + awaitAllPoly[T, {fs*}](fs) diff --git a/tests/pos-custom-args/captures/i21313.scala b/tests/pos-custom-args/captures/i21313.scala index b388b6487cb5..5255510981cc 100644 --- a/tests/pos-custom-args/captures/i21313.scala +++ b/tests/pos-custom-args/captures/i21313.scala @@ -1,20 +1,21 @@ -import caps.CapSet +import language.experimental.captureChecking trait Async: - def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, Cap]^): T = + def await[T, cap Cap](using caps.Contains[Cap, this.type])(src: Source[T, {Cap}]^): T = + // FIXME: this is an irregularity: it works if we write caps.Contains[Cap, this.type], but we should expect to write caps.Contains[{Cap}, this.type]! val x: Async^{this} = ??? - val y: Async^{Cap^} = x + val y: Async^{Cap} = x val ac: Async^ = ??? - def f(using caps.Contains[Cap, ac.type]) = + def f(using caps.Contains[Cap, ac.type]) = // FIXME: dito val x2: Async^{this} = ??? - val y2: Async^{Cap^} = x2 + val y2: Async^{Cap} = x2 val x3: Async^{ac} = ??? - val y3: Async^{Cap^} = x3 + val y3: Async^{Cap} = x3 ??? -trait Source[+T, Cap^]: - final def await(using ac: Async^{Cap^}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap. +trait Source[+T, cap Cap]: + final def await(using ac: Async^{Cap}) = ac.await[T, {Cap}](this) // Contains[Cap, ac] is assured because {ac} <: Cap. def test(using ac1: Async^, ac2: Async^, x: String) = - val src1 = new Source[Int, CapSet^{ac1}] {} + val src1 = new Source[Int, {ac1}] {} ac1.await(src1) diff --git a/tests/pos-custom-args/captures/i21347.scala b/tests/pos-custom-args/captures/i21347.scala index a965b7e4f26b..7e6298200b29 100644 --- a/tests/pos-custom-args/captures/i21347.scala +++ b/tests/pos-custom-args/captures/i21347.scala @@ -2,9 +2,9 @@ import language.experimental.captureChecking -class Box[Cap^] {} +class Box[cap Cap] {} -def run[Cap^](f: Box[Cap]^{Cap^} => Unit): Box[Cap]^{Cap^} = ??? +def run[cap Cap](f: Box[{Cap}]^{Cap} => Unit): Box[{Cap}]^{Cap} = ??? def main() = val b = run(_ => ()) diff --git a/tests/pos-custom-args/captures/i21507.scala b/tests/pos-custom-args/captures/i21507.scala index bb80dafb3b45..285086a26554 100644 --- a/tests/pos-custom-args/captures/i21507.scala +++ b/tests/pos-custom-args/captures/i21507.scala @@ -1,10 +1,10 @@ import language.experimental.captureChecking -trait Box[Cap^]: - def store(f: (() -> Unit)^{Cap^}): Unit +trait Box[cap Cap]: + def store(f: (() -> Unit)^{Cap}): Unit -def run[Cap^](f: Box[Cap]^{Cap^} => Unit): Box[Cap]^{Cap^} = - new Box[Cap]: - private var item: () ->{Cap^} Unit = () => () - def store(f: () ->{Cap^} Unit): Unit = +def run[cap Cap](f: Box[{Cap}]^{Cap} => Unit): Box[{Cap}]^{Cap} = + new Box[{Cap}]: + private var item: () ->{Cap} Unit = () => () + def store(f: () ->{Cap} Unit): Unit = item = f // was error, now ok diff --git a/tests/pos-custom-args/captures/polycap.scala b/tests/pos-custom-args/captures/polycap.scala index 684f46454595..31f32176e707 100644 --- a/tests/pos-custom-args/captures/polycap.scala +++ b/tests/pos-custom-args/captures/polycap.scala @@ -1,11 +1,11 @@ import language.experimental.captureChecking -class Source[+T, Cap^] +class Source[+T, cap Cap] -def completed[T, Cap^](result: T): Source[T, Cap] = +def completed[T, cap Cap](result: T): Source[T, {Cap}] = //val fut = new Source[T, Cap]() - val fut2 = new Source[T, Cap]() - fut2: Source[T, Cap] + val fut2 = new Source[T, {Cap}]() + fut2: Source[T, {Cap}] diff --git a/tests/pos-custom-args/captures/setup/a_1.scala b/tests/pos-custom-args/captures/setup/a_1.scala index 21afde8be3ea..2d5c0869624d 100644 --- a/tests/pos-custom-args/captures/setup/a_1.scala +++ b/tests/pos-custom-args/captures/setup/a_1.scala @@ -1,6 +1,5 @@ // a.scala import language.experimental.captureChecking -import scala.caps.CapSet trait A: - def f[C^](x: AnyRef^{C^}): Unit + def f[cap C](x: AnyRef^{C}): Unit diff --git a/tests/pos-custom-args/captures/setup/b_1.scala b/tests/pos-custom-args/captures/setup/b_1.scala index d5ba925970ba..fd5718224d3d 100644 --- a/tests/pos-custom-args/captures/setup/b_1.scala +++ b/tests/pos-custom-args/captures/setup/b_1.scala @@ -1,5 +1,4 @@ import language.experimental.captureChecking -import scala.caps.CapSet class B extends A: - def f[C^](x: AnyRef^{C^}): Unit = ??? + def f[cap C](x: AnyRef^{C}): Unit = ??? diff --git a/tests/pos-custom-args/captures/setup/b_2.scala b/tests/pos-custom-args/captures/setup/b_2.scala index d5ba925970ba..fd5718224d3d 100644 --- a/tests/pos-custom-args/captures/setup/b_2.scala +++ b/tests/pos-custom-args/captures/setup/b_2.scala @@ -1,5 +1,4 @@ import language.experimental.captureChecking -import scala.caps.CapSet class B extends A: - def f[C^](x: AnyRef^{C^}): Unit = ??? + def f[cap C](x: AnyRef^{C}): Unit = ???