diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala
index 471a9953c4f0..385e74f829e3 100644
--- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala
+++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala
@@ -642,7 +642,7 @@ object desugar {
       .withMods(mods & (GivenOrImplicit | Erased | hasDefault | Tracked) | Param)
   }
 
-  /** Desugar type def (not param): Under x.moduliity this can expand
+  /** Desugar type def (not param): Under x.modularity this can expand
    *  context bounds, which are expanded to evidence ValDefs. These will
    *  ultimately map to deferred givens.
    */
diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala
index fcc257da27e4..c6cde66374b3 100644
--- a/compiler/src/dotty/tools/dotc/ast/Trees.scala
+++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala
@@ -34,6 +34,9 @@ object Trees {
 
   val SyntheticUnit: Property.StickyKey[Unit] = Property.StickyKey()
 
+  /** Property key for marking capture-set variables and members */
+  val CaptureVar: Property.StickyKey[Unit] = Property.StickyKey()
+
   /** Trees take a parameter indicating what the type of their `tpe` field
    *  is. Two choices: `Type` or `Untyped`.
    *  Untyped trees have type `Tree[Untyped]`.
diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala
index 145c61584fcc..8b4bfc468a94 100644
--- a/compiler/src/dotty/tools/dotc/ast/untpd.scala
+++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala
@@ -533,14 +533,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
   def makeCapsOf(tp: RefTree)(using Context): Tree =
     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 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/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/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala
index 9853107ca8fc..2a91dc5090bb 100644
--- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala
+++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala
@@ -227,6 +227,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
 
@@ -1591,8 +1592,7 @@ object Parsers {
       case _ => None
     }
 
-    /** CaptureRef  ::=  { SimpleRef `.` } SimpleRef [`*`] [`.` rd]
-     *                |  [ { SimpleRef `.` } SimpleRef `.` ] id `^`
+    /** CaptureRef  ::=  { SimpleRef `.` } SimpleRef [`*`] [`.` `rd`] -- under captureChecking
      */
     def captureRef(): Tree =
 
@@ -1611,12 +1611,6 @@ 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())
@@ -1624,9 +1618,10 @@ object Parsers {
 
     /**  CaptureSet ::=  `{` CaptureRef {`,` CaptureRef} `}`    -- under captureChecking
      */
-    def captureSet(): List[Tree] = inBraces {
-      if in.token == RBRACE then Nil else commaSeparated(captureRef)
-    }
+    def captureSet(): List[Tree] =
+      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)
@@ -1640,9 +1635,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 } ')'
@@ -1885,7 +1880,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())
 
@@ -1916,6 +1911,12 @@ object Parsers {
       || !canStartInfixTypeTokens.contains(ahead.token)
       || ahead.lineOffset > 0
 
+    inline def gobbleHat(): Boolean =
+      if Feature.ccEnabled && isIdent(nme.UPARROW) then
+        in.nextToken()
+        true
+      else false
+
     def refinedTypeRest(t: Tree): Tree = {
       argumentStart()
       if in.isNestedStart then
@@ -2172,35 +2173,45 @@ 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 ‘=’ TypeArg
      *    NamesAndTypes     ::=  NameAndType {‘,’ NameAndType}
-     *    NameAndType       ::=  id ':' Type
+     *    NameAndType       ::=  id ‘:’ Type
      */
     def argTypes(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): List[Tree] =
-      def argType() =
-        val t = typ()
+      def wildCardCheck(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 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 name = ident()
           accept(EQUALS)
-          NamedArg(name.toTypeName, argType())
+          NamedArg(name.toTypeName, typeArg())
 
-      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) 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 =
@@ -2244,7 +2255,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] =
@@ -2258,21 +2269,28 @@ object Parsers {
       else
         inBraces(refineStatSeq())
 
-    /** TypeBounds ::= [`>:' Type] [`<:' Type]
-     *              |  `^`                     -- under captureChecking
+    /** TypeBounds ::= [`>:' TypeBound ] [`<:' TypeBound ]
+     *  TypeBound  ::= Type
+     *               | CaptureSet -- 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))
 
     private def bound(tok: Int): Tree =
-      if (in.token == tok) { in.nextToken(); toplevelTyp() }
+      if in.token == tok then
+        in.nextToken()
+        if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then
+          capsBound(captureSet(), isLowerBound = tok == SUPERTYPE)
+        else 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
+        concreteCapsType(refs)
+
     /** TypeAndCtxBounds  ::=  TypeBounds [`:` ContextBounds]
      */
     def typeAndCtxBounds(pname: TypeName): Tree = {
@@ -3397,7 +3415,7 @@ object Parsers {
      *                  |  opaque
      *  LocalModifier  ::= abstract | final | sealed | open | implicit | lazy | erased |
      *                     inline | transparent | infix |
-     *                     mut                              -- under cc
+     *                     mut                              -- under captureChecking
      */
     def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
       @tailrec
@@ -3486,22 +3504,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} [‘+’ | ‘-’] id `^` TypeAndCtxBounds   -- under captureChecking
      *
      *  DefTypeParamClause::=  ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’
-     *  DefTypeParam      ::=  {Annotation}
-     *                         id [HkTypeParamClause] TypeAndCtxBounds
+     *  DefTypeParam      ::=   {Annotation}
+     *                          id [HkTypeParamClause] TypeAndCtxBounds
+     *                      |   {Annotation} id `^` TypeAndCtxBounds               -- under captureChecking
      *
      *  TypTypeParamClause::=  ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
-     *  TypTypeParam      ::=  {Annotation}
-     *                         (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds
+     *  TypTypeParam      ::=   {Annotation}
+     *                          (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds
+     *                      |   {Annotation} (id | ‘_’) `^` TypeAndCtxBounds       -- under captureChecking
      *
      *  HkTypeParamClause ::=  ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’
-     *  HkTypeParam       ::=  {Annotation} [‘+’ | ‘-’]
-     *                         (id | ‘_’) [HkTypeParamClause] TypeBounds
+     *  HkTypeParam       ::=   {Annotation} [‘+’ | ‘-’]
+     *                          (id | ‘_’) [HkTypePamClause] TypeBounds
+     *                      |   {Annotation} [‘+’ | ‘-’] (id | ‘_’) `^` TypeBounds -- under captureChecking
      */
     def typeParamClause(paramOwner: ParamOwner): List[TypeDef] = inBracketsWithCommas {
 
@@ -3526,12 +3547,18 @@ object Parsers {
               in.nextToken()
               WildcardParamName.fresh().toTypeName
             else ident().toTypeName
+          val isCap = gobbleHat()
           val hkparams = typeParamClauseOpt(ParamOwner.Hk)
           val bounds =
             if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name)
             else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name)
             else typeBounds()
-          TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
+          val res = TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
+          if isCap then
+            res.pushAttachment(CaptureVar, ())
+            // putting the attachment here as well makes post-processing in the typer easier
+            bounds.pushAttachment(CaptureVar, ())
+          res
         }
       }
       commaSeparated(() => typeParam())
@@ -4052,12 +4079,22 @@ object Parsers {
         argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs()))
       }
 
-    /** TypeDef ::=  id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ Type]
+    /** TypeDef    ::=  id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ TypeDefRHS ]
+     *               |  id `^` TypeAndCtxBounds [‘=’ TypeDefRHS ] -- under captureChecking
+     *  TypeDefRHS ::= Type
+     *               | CaptureSet -- under captureChecking
      */
     def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
+
+      def typeDefRHS(): Tree =
+        if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then
+          concreteCapsType(captureSet())
+        else toplevelTyp()
+
       newLinesOpt()
       atSpan(start, nameStart) {
         val nameIdent = typeIdent()
+        val isCapDef = gobbleHat()
         val tname = nameIdent.name.asTypeName
         val tparams = typeParamClauseOpt(ParamOwner.Hk)
         val vparamss = funParamClauses()
@@ -4065,20 +4102,24 @@ object Parsers {
         def makeTypeDef(rhs: Tree): Tree = {
           val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs)
           val tdef = TypeDef(nameIdent.name.toTypeName, rhs1)
-          if (nameIdent.isBackquoted)
+          if nameIdent.isBackquoted then
             tdef.pushAttachment(Backquoted, ())
+          if isCapDef then
+            tdef.pushAttachment(CaptureVar, ())
+            // putting the attachment here as well makes post-processing in the typer easier
+            rhs.pushAttachment(CaptureVar, ())
           finalizeDef(tdef, mods, start)
         }
 
         in.token match {
           case EQUALS =>
             in.nextToken()
-            makeTypeDef(toplevelTyp())
+            makeTypeDef(typeDefRHS())
           case SUBTYPE | SUPERTYPE =>
             typeAndCtxBounds(tname) match
               case bounds: TypeBoundsTree if in.token == EQUALS =>
                 val eqOffset = in.skipToken()
-                var rhs = toplevelTyp()
+                var rhs = typeDefRHS()
                 rhs match {
                   case mtt: MatchTypeTree =>
                     bounds match {
@@ -4107,6 +4148,9 @@ object Parsers {
       }
     }
 
+    private def concreteCapsType(refs: List[Tree]): Tree =
+      makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains)
+
     /** TmplDef ::=  ([‘case’] ‘class’ | ‘trait’) ClassDef
      *            |  [‘case’] ‘object’ ObjectDef
      *            |  ‘enum’ EnumDef
diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
index 0dcb06ae8c87..a5843b545117 100644
--- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
+++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
@@ -451,7 +451,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
     homogenize(tp) match
       case tp: TermRef if tp.symbol == defn.captureRoot => "cap"
       case tp: SingletonType => toTextRef(tp)
-      case tp: (TypeRef | TypeParamRef) => toText(tp) ~ "^"
+      case tp: (TypeRef | TypeParamRef) => toText(tp)
       case ReadOnlyCapability(tp1) => toTextCaptureRef(tp1) ~ ".rd"
       case ReachCapability(tp1) => toTextCaptureRef(tp1) ~ "*"
       case MaybeCapability(tp1) => toTextCaptureRef(tp1) ~ "?"
diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala
index 07ddb37d93b7..78e2099511d7 100644
--- a/compiler/src/dotty/tools/dotc/typer/Applications.scala
+++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala
@@ -37,6 +37,7 @@ import annotation.threadUnsafe
 import scala.util.control.NonFatal
 import dotty.tools.dotc.inlines.Inlines
 import scala.annotation.tailrec
+import dotty.tools.dotc.cc.isRetains
 
 object Applications {
   import tpd.*
@@ -1116,7 +1117,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 428568ecc795..935f90519b7f 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
 
@@ -2512,6 +2524,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
     val tycon = typedType(tree.tycon)
     def spliced(tree: Tree) = untpd.TypedSplice(tree)
     val tparam = untpd.Ident(tree.paramName).withSpan(tree.span.withEnd(tree.span.point))
+    if Feature.ccEnabled && typed(tparam).tpe.derivesFrom(defn.Caps_CapSet) then
+      report.error(em"Capture variable `${tree.paramName}` cannot have a context bound.", tycon.srcPos)
     if tycon.tpe.typeParams.nonEmpty then
       val tycon0 = tycon.withType(tycon.tpe.etaCollapse)
       typed(untpd.AppliedTypeTree(spliced(tycon0), tparam :: Nil))
@@ -2722,13 +2736,28 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
       assignType(cpy.ByNameTypeTree(tree)(result1), result1)
 
   def typedTypeBoundsTree(tree: untpd.TypeBoundsTree, pt: Type)(using Context): Tree =
+    lazy val CapSetBot = untpd.TypeTree(defn.Caps_CapSet.typeRef)
+    lazy val CapSetTop = untpd.makeRetaining(untpd.TypeTree(defn.Caps_CapSet.typeRef), Nil, tpnme.retainsCap).withSpan(tree.span)
+
     val TypeBoundsTree(lo, hi, alias) = tree
     val lo1 = typed(lo)
     val hi1 = typed(hi)
     val alias1 = typed(alias)
-    val lo2 = if (lo1.isEmpty) typed(untpd.TypeTree(defn.NothingType)) else lo1
-    val hi2 = if (hi1.isEmpty) typed(untpd.TypeTree(defn.AnyType)) else hi1
+    val isCap = tree.hasAttachment(CaptureVar)
+    val lo2 =
+      if lo1.isEmpty then
+        if Feature.ccEnabled && (isCap || hi1.tpe.derivesFrom(defn.Caps_CapSet)) then
+          typed(CapSetBot)
+        else typed(untpd.TypeTree(defn.NothingType))
+      else lo1
+    val hi2 =
+      if hi1.isEmpty then
+        if Feature.ccEnabled && (isCap || lo1.tpe.derivesFrom(defn.Caps_CapSet)) then
+          typed(CapSetTop)
+        else typed(untpd.TypeTree(defn.AnyType))
+      else hi1
     assignType(cpy.TypeBoundsTree(tree)(lo2, hi2, alias1), lo2, hi2, alias1)
+  end typedTypeBoundsTree
 
   def typedBind(tree: untpd.Bind, pt: Type)(using Context): Tree = {
     if !isFullyDefined(pt, ForceDegree.all) then
@@ -3024,6 +3053,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
     if sym.isOpaqueAlias then
       checkFullyAppliedType(rhs1, "Opaque type alias must be fully applied, but ")
       checkNoContextFunctionType(rhs1)
+    if Feature.ccEnabled then
+      val isCap = tdef.hasAttachment(CaptureVar)
+      rhs1 match
+        case TypeBoundsTree(lo, hi, _) =>
+          if !isCap && (lo.tpe.derivesFrom(defn.Caps_CapSet) ^ hi.tpe.derivesFrom(defn.Caps_CapSet)) then
+            report.error(em"Illegal type bounds: >: $lo <: $hi. Capture-set bounds cannot be mixed with type bounds of other kinds", rhs.srcPos)
+          if isCap && !(lo.tpe.derivesFrom(defn.Caps_CapSet) && hi.tpe.derivesFrom(defn.Caps_CapSet)) then
+            report.error(em"Illegal type bounds: >: $lo <: $hi. $name^ can only have capture sets as bounds", rhs.srcPos)
+        case LambdaTypeTree(_, _) if isCap =>
+          report.error(em"`$name` cannot have type parameters, because it ranges over capture sets", rhs.srcPos)
+        case _ =>
     assignType(cpy.TypeDef(tdef)(name, rhs1), sym)
   }
 
diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md
index 5a0e543a6d42..baf9145ae8a6 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  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,17 +225,23 @@ 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)
+TypeBounds        ::=  [‘>:’ TypeBound] [‘<:’ TypeBound]                        TypeBoundsTree(lo, hi)
 TypeAndCtxBounds  ::=  TypeBounds [‘:’ ContextBounds]                           ContextBounds(typeBounds, tps)
 ContextBounds     ::=  ContextBound
                     |  ContextBound `:` ContextBounds                           -- to be deprecated
                     |  '{' ContextBound {',' ContextBound} '}'
 ContextBound      ::=  Type ['as' id]
 Types             ::=  Type {‘,’ Type}
+TypeArg           ::=  Type
+                    |  CaptureSet                                               -- under captureChecking
+TypeBound         ::=  Type
+                    |  CaptureSet                                               -- under captureChecking
 NamesAndTypes     ::=  NameAndType {‘,’ NameAndType}
 NameAndType       ::=  id ':' Type
+CaptureSet        ::=  ‘{’ CaptureRef {‘,’ CaptureRef} ‘}’                      -- under captureChecking
+CaptureRef        ::=  { SimpleRef ‘.’ } SimpleRef [‘*’] [‘.’ ‘rd’]             -- under captureChecking
 ```
 
 ### Expressions
@@ -365,16 +377,20 @@ ArgumentPatterns  ::=  ‘(’ [Patterns] ‘)’
 ClsTypeParamClause::=  ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’
 ClsTypeParam      ::=  {Annotation} [‘+’ | ‘-’]                                 TypeDef(Modifiers, name, tparams, bounds)
                        id [HkTypeParamClause] TypeAndCtxBounds                  Bound(below, above, context)
+                    |  {Annotation} [‘+’ | ‘-’] id `^` TypeAndCtxBounds         -- under captureChecking
 
 DefTypeParamClause::=  [nl] ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’
 DefTypeParam      ::=  {Annotation} id [HkTypeParamClause] TypeAndCtxBounds
+                    |  {Annotation} id `^` TypeAndCtxBounds                     -- under captureChecking
 
 TypTypeParamClause::=  ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
-TypTypeParam      ::=  {Annotation} (id | ‘_’) [HkTypeParamClause] TypeBounds
+TypTypeParam      ::=  {Annotation} (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds
+                    |  {Annotation} id `^` TypeAndCtxBounds                     -- under captureChecking
 
 HkTypeParamClause ::=  ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’
 HkTypeParam       ::=  {Annotation} [‘+’ | ‘-’] (id  | ‘_’) [HkTypeParamClause]
                        TypeBounds
+                    |  {Annotation} [‘+’ | ‘-’] id `^` TypeBounds               -- under captureChecking
 
 ClsParamClauses   ::=  {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’]
 ClsParamClause    ::=  [nl] ‘(’ ClsParams ‘)’
@@ -419,6 +435,7 @@ LocalModifier     ::=  ‘abstract’
                     |  ‘infix’
                     |  ‘erased’
                     |  ‘tracked’
+                    |  ‘mut’                                                      -- under captureChecking
 
 AccessModifier    ::=  (‘private’ | ‘protected’) [AccessQualifier]
 AccessQualifier   ::=  ‘[’ id ‘]’
@@ -462,7 +479,10 @@ DefDef            ::=  DefSig [‘:’ Type] [‘=’ Expr]
                     |  ‘this’ ConstrParamClauses [DefImplicitClause] ‘=’ ConstrExpr     DefDef(_, <init>, vparamss, EmptyTree, expr | Block)
 DefSig            ::=  id [DefParamClauses] [DefImplicitClause]
 TypeDef           ::=  id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds   TypeDefTree(_, name, tparams, bound
-                       [‘=’ Type]
+                       [‘=’ TypeDefRHS]
+                    | id `^` TypeAndCtxBounds [‘=’ TypeDefRHS]                  -- under captureChecking
+TypeDefRHS        ::= Type
+                    | CaptureSet                                                -- under captureChecking
 
 TmplDef           ::=  ([‘case’] ‘class’ | ‘trait’) ClassDef
                     |  [‘case’] ‘object’ ObjectDef
diff --git a/docs/_docs/reference/experimental/cc-advanced.md b/docs/_docs/reference/experimental/cc-advanced.md
new file mode 100644
index 000000000000..52d1956caf49
--- /dev/null
+++ b/docs/_docs/reference/experimental/cc-advanced.md
@@ -0,0 +1,78 @@
+---
+layout: doc-page
+title: "Capture Checking -- Advanced Use Cases"
+nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-advanced.html
+---
+
+
+## Access Control
+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[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
+```
+
+## Capture-safe Lexical Control
+
+Capability members and paths to these members can prevent leakage
+of labels for lexically-delimited control operators:
+```scala
+trait Label extends Capability:
+  type Fv^ // the capability set occurring freely in the `block` passed to `boundary` below.
+
+def boundary[T, C^](block: Label{type Fv = {C} } ->{C} T): T = ??? // ensure free caps of label and block match
+def suspend[U](label: Label)[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.
+
+[Back to Capture Checking](cc.md)
\ No newline at end of file
diff --git a/docs/_docs/reference/experimental/cc.md b/docs/_docs/reference/experimental/cc.md
index ff480ffb638b..542094b05c96 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[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
+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 `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,44 @@ 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, C^](var iterators: mutable.List[IterableOnce[A]^{C}]):
+  def concat(it: IterableOnce[A]^): ConcatIterator[A, {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.
+
+
+## Capability Members
+
+Just as parametrization by types can be equally expressed with type members, we could
+also define the `Source[X^]` class above could using a _capability member_:
+```scala
+class Source:
+  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:
+  type Cap^
+  def run(block: () ->{this.Cap} -> Unit): Unit
+
+trait GPUThread extends Thread:
+  type Cap^ >: {cudaMalloc, cudaFree} <: {caps.cap}
+```
+Since `caps.cap` is the top element for subcapturing, we could have also left out the
+upper bound: `type Cap^ >: {cudaMalloc, cudaFree}`.
+
+
+[More Advanced Use Cases](cc-advanced.md)
 
 ## Compilation Options
 
diff --git a/docs/sidebar.yml b/docs/sidebar.yml
index aecd974326ab..16957ba979bd 100644
--- a/docs/sidebar.yml
+++ b/docs/sidebar.yml
@@ -162,6 +162,8 @@ subsection:
           - page: reference/experimental/main-annotation.md
           - page: reference/experimental/into-modifier.md
           - page: reference/experimental/cc.md
+          - page: reference/experimental/cc-advanced.md
+            hidden: true
           - page: reference/experimental/purefuns.md
           - page: reference/experimental/tupled-function.md
           - page: reference/experimental/modularity.md
diff --git a/project/resources/referenceReplacements/sidebar.yml b/project/resources/referenceReplacements/sidebar.yml
index 2e84b0b5e433..4e40642858c9 100644
--- a/project/resources/referenceReplacements/sidebar.yml
+++ b/project/resources/referenceReplacements/sidebar.yml
@@ -150,6 +150,8 @@ subsection:
       - page: reference/experimental/explicit-nulls.md
       - page: reference/experimental/main-annotation.md
       - page: reference/experimental/cc.md
+      - page: reference/experimental/cc-advanced.md
+        hidden: true
       - page: reference/experimental/tupled-function.md
   - page: reference/syntax.md
   - title: Language Versions
diff --git a/project/scripts/expected-links/reference-expected-links.txt b/project/scripts/expected-links/reference-expected-links.txt
index 8be7dba8d4d0..3eb5df97bf14 100644
--- a/project/scripts/expected-links/reference-expected-links.txt
+++ b/project/scripts/expected-links/reference-expected-links.txt
@@ -68,6 +68,7 @@
 ./enums/enums.html
 ./enums/index.html
 ./experimental/canthrow.html
+./experimental/cc-advanced.html
 ./experimental/cc.html
 ./experimental/erased-defs-spec.html
 ./experimental/erased-defs.html
diff --git a/tests/neg-custom-args/captures/branding.scala b/tests/neg-custom-args/captures/branding.scala
new file mode 100644
index 000000000000..7b722971e670
--- /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[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..c3091befdc25
--- /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: () => // 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/cap-paramlists6.check b/tests/neg-custom-args/captures/cap-paramlists6.check
new file mode 100644
index 000000000000..ad14cc7f27f7
--- /dev/null
+++ b/tests/neg-custom-args/captures/cap-paramlists6.check
@@ -0,0 +1,8 @@
+-- Error: tests/neg-custom-args/captures/cap-paramlists6.scala:10:71 ---------------------------------------------------
+10 |  val baz  = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E} : Ctx, // error
+   |                                                                       ^^^
+   |                                                               Capture variable `F` cannot have a context bound.
+-- Error: tests/neg-custom-args/captures/cap-paramlists6.scala:11:51 ---------------------------------------------------
+11 |                    G >: {} <: {}, H >: {} <: {} : Ctx] => (x: Int) => 1 // error
+   |                                                   ^^^
+   |                                                   Capture variable `H` cannot have a context bound.
diff --git a/tests/neg-custom-args/captures/cap-paramlists6.scala b/tests/neg-custom-args/captures/cap-paramlists6.scala
new file mode 100644
index 000000000000..a83164f5466b
--- /dev/null
+++ b/tests/neg-custom-args/captures/cap-paramlists6.scala
@@ -0,0 +1,11 @@
+import language.experimental.captureChecking
+
+trait Ctx[T]
+
+def test =
+  val x: Any^ = ???
+  val y: Any^ = ???
+  object O:
+    val z: Any^ = ???
+  val baz  = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E} : Ctx, // error
+                    G >: {} <: {}, H >: {} <: {} : Ctx] => (x: Int) => 1 // 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..71250ec1585e 100644
--- a/tests/neg-custom-args/captures/capset-bound.scala
+++ b/tests/neg-custom-args/captures/capset-bound.scala
@@ -1,3 +1,4 @@
+import language.experimental.captureChecking
 import caps.*
 
 class IO
@@ -5,14 +6,22 @@ class IO
 case class File(io: IO^)
 
 def test(io1: IO^, io2: IO^) =
-  def f[C >: CapSet^{io1} <: CapSet^](file: File^{C^}) = ???
+  def f[C^ >: {io1}](file: File^{C}) = ???
+  def g[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)
+  g[{io1}](f1)
+  g[{io1}](f2) // error
+  g[{io1}](f3) // error
+  g[{io2}](f2) // error
+  g[{io1, io2}](f1)
+  g[{io1, io2}](f2)
+  g[{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..803ec6419a21 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[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-nohat.scala b/tests/neg-custom-args/captures/capset-members-nohat.scala
new file mode 100644
index 000000000000..f99c47f23ac0
--- /dev/null
+++ b/tests/neg-custom-args/captures/capset-members-nohat.scala
@@ -0,0 +1,30 @@
+import language.experimental.captureChecking
+import caps.*
+
+trait Abstract[X^]:
+  type C >: {X}
+  // Don't test the return type using Unit, because it is a pure type.
+  def boom(): AnyRef^{C}
+
+class Concrete extends Abstract[{}]:
+  type C^ = {}
+  // TODO: Why do we get error without the return type here?
+  def boom(): AnyRef = new Object
+
+class Concrete2 extends Abstract[{}]:
+  type C^ = {}
+  def boom(): AnyRef^ = new Object // error
+
+class Concrete3 extends Abstract[{}]:
+  def boom(): AnyRef = new Object
+
+class Concrete4(a: AnyRef^) extends Abstract[{a}]:
+  type C^ = {} // error
+  def boom(): AnyRef^{a} = a // error
+
+class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]:
+  type C^ = {a}
+  def boom(): AnyRef^{b} = b // error
+
+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-members.scala b/tests/neg-custom-args/captures/capset-members.scala
index 540216852a43..3d931200f326 100644
--- a/tests/neg-custom-args/captures/capset-members.scala
+++ b/tests/neg-custom-args/captures/capset-members.scala
@@ -1,30 +1,30 @@
+import language.experimental.captureChecking
 import caps.*
 
 trait Abstract[X^]:
-  type C >: X <: CapSet^
+  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[{}]:
+  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[{}]:
+  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}]:
+  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}]:
+  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..903f875432a4
--- /dev/null
+++ b/tests/neg-custom-args/captures/capset-members2.check
@@ -0,0 +1,12 @@
+-- Error: tests/neg-custom-args/captures/capset-members2.scala:5:10 ----------------------------------------------------
+5 |  type C^[T] // error
+  |          ^
+  |          `C` cannot have type parameters, because it ranges over capture sets
+-- Error: tests/neg-custom-args/captures/capset-members2.scala:7:13 ----------------------------------------------------
+7 |  def foo[C^[_]]: Int // error
+  |             ^
+  |             `C` cannot have type parameters, because it ranges over capture sets
+-- Error: tests/neg-custom-args/captures/capset-members2.scala:9:20 ----------------------------------------------------
+9 |  def bar [M[_], C^ <: M]: Int // error
+  |                    ^^^^
+  |                    Illegal type bounds: >: scala.caps.CapSet <: M. C^ can only have capture sets as bounds
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..727dd3e1e742
--- /dev/null
+++ b/tests/neg-custom-args/captures/capset-members2.scala
@@ -0,0 +1,9 @@
+import language.experimental.captureChecking
+import caps.*
+
+trait Foo:
+  type C^[T] // error
+
+  def foo[C^[_]]: Int // error
+
+  def bar [M[_], C^ <: M]: Int // error
\ No newline at end of file
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..246bced980d6
--- /dev/null
+++ b/tests/neg-custom-args/captures/capset-members3.check
@@ -0,0 +1,6 @@
+-- [E095] Syntax Error: tests/neg-custom-args/captures/capset-members3.scala:5:10 --------------------------------------
+5 |  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..c8b767ae167c
--- /dev/null
+++ b/tests/neg-custom-args/captures/capset-members3.scala
@@ -0,0 +1,6 @@
+import language.experimental.captureChecking
+import caps.*
+
+trait Foo:
+  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..bdce213e7de1
--- /dev/null
+++ b/tests/neg-custom-args/captures/capset-members4.scala
@@ -0,0 +1,18 @@
+import language.experimental.captureChecking
+import caps.*
+
+def test =
+  val x: Any^ = ???
+  val y: Any^ = ???
+  val z: Any^ = ???
+  def onlyWithZ[C^](using c: Contains[C, z.type]) = ???
+
+  trait Foo:
+    type C >: {z,x} <: {x,y,z}
+
+  val foo: Foo = ???
+  onlyWithZ[{foo.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/capset-members5.scala b/tests/neg-custom-args/captures/capset-members5.scala
new file mode 100644
index 000000000000..a779e9041a48
--- /dev/null
+++ b/tests/neg-custom-args/captures/capset-members5.scala
@@ -0,0 +1,31 @@
+import language.experimental.captureChecking
+import caps.*
+
+trait Ctx[T]
+
+def test =
+  val x: Any^ = ???
+  val y: Any^ = ???
+  object O:
+    val z: Any^ = ???
+  trait CaptureSet:
+    type A^ >: {y} <: {x}
+    type B = {x}
+    type C <: {x}
+    type D^ : Ctx // error
+    type E <: {C}
+    type F <: {C}
+    type G <: {x, y}
+    type H >: {x} <: {x,y} : Ctx // error
+    type I^ = {y, G, H}
+    type J = {O.z}
+    type K^ = {x, O.z}
+    type L <: {x, y, O.z}
+    type M >: {x, y, O.z} <: {C}
+    type N >: {x} <: {x}
+    type O >: {O.z} <: {O.z}
+    type P >: {B,D} // error
+    type Q >: {E} <: Int // error
+    type R^ >: {E} <: Int // error
+    type S^ >: Nothing <: Int // error
+    type T >: Nothing <: Int
\ No newline at end of file
diff --git a/tests/neg-custom-args/captures/capset-variance.scala b/tests/neg-custom-args/captures/capset-variance.scala
new file mode 100644
index 000000000000..2ad97a014648
--- /dev/null
+++ b/tests/neg-custom-args/captures/capset-variance.scala
@@ -0,0 +1,32 @@
+import language.experimental.captureChecking
+
+trait Test:
+  type T[-C^]
+  type U[+C^]
+  type C^
+  type D^
+
+  def foo[C^](x: T[C]): Unit = ???
+  def bar(x: T[{}]): Unit = ???
+  def baz(x: T[{caps.cap}]): Unit = ???
+  def foo2[C^](x: U[C]): Unit = ???
+  def bar2(x: U[{}]): Unit = ???
+  def baz2(x: U[{caps.cap}]): Unit = ???
+
+  def test =
+    val t: T[{C}] = ???
+    val t2: T[C] = ???
+    val u: U[{D}] = ???
+    val u2: U[D] = ???
+    foo(t)
+    bar(t)
+    baz(t) // error
+    foo2(u)
+    bar2(u) // error
+    baz2(u)
+    foo(t2)
+    bar(t2)
+    baz(t2) // error
+    foo2(u2)
+    bar2(u2) // error
+    baz2(u2)
\ 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..d9288a062ded 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[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
diff --git a/tests/neg-custom-args/captures/capture-poly.scala b/tests/neg-custom-args/captures/capture-poly.scala
index 88989b418726..c0608ad812de 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^
+  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[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}
+    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..9a32abe0b6e3 100644
--- a/tests/neg-custom-args/captures/capture-vars-subtyping.scala
+++ b/tests/neg-custom-args/captures/capture-vars-subtyping.scala
@@ -3,32 +3,32 @@ import caps.*
 
 def test[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[C^, D^ <: C, E^ <: {D}, 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]
@@ -37,12 +37,12 @@ trait B[-C]
 
 def testCong[C^, 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..ccf646f34dca 100644
--- a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala
+++ b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala
@@ -9,36 +9,36 @@ 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^}] =
+                    B^ <: {A},
+                    C^ <: {B},
+                    D^ <: C,
+                    E^ <: {D},
+                    F^ <: {A,b},
+                    X^ <: {F,D},
+                    Y^ >: {F} <: {F,A,b},
+                    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..b205b9b25246 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[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.scala b/tests/neg-custom-args/captures/cc-poly-2.scala
index c9249ba59437..8fb590f4f769 100644
--- a/tests/neg-custom-args/captures/cc-poly-2.scala
+++ b/tests/neg-custom-args/captures/cc-poly-2.scala
@@ -6,7 +6,7 @@ object Test:
   class C extends Capability
   class D
 
-  def f[X^](x: D^{X^}): D^{X^} = x
+  def f[X^](x: D^{X}): D^{X} = x
 
   def test(c1: C, c2: C) =
     val d: D^ = D()
diff --git a/tests/neg-custom-args/captures/cc-poly-source.scala b/tests/neg-custom-args/captures/cc-poly-source.scala
index e08ea36a6fc9..fc47e6504810 100644
--- a/tests/neg-custom-args/captures/cc-poly-source.scala
+++ b/tests/neg-custom-args/captures/cc-poly-source.scala
@@ -10,14 +10,14 @@ import caps.use
   class Listener
 
   class Source[X^]:
-    private var listeners: Set[Listener^{X^}] = Set.empty
-    def register(x: Listener^{X^}): Unit =
+    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..9ee31aa7b196 100644
--- a/tests/neg-custom-args/captures/i21313.scala
+++ b/tests/neg-custom-args/captures/i21313.scala
@@ -1,15 +1,15 @@
-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^](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.
+  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..2e37511f053c 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[C^](ops: List[() ->{C} Unit]): Unit =
   ops.foreach: op => // error
     op()
 
diff --git a/tests/neg-custom-args/captures/i21868b.scala b/tests/neg-custom-args/captures/i21868b.scala
index 70f4e9c9d59c..3c15f5c1cdf7 100644
--- a/tests/neg-custom-args/captures/i21868b.scala
+++ b/tests/neg-custom-args/captures/i21868b.scala
@@ -1,3 +1,4 @@
+import language.experimental.captureChecking
 import language.experimental.modularity
 import caps.*
 
@@ -6,44 +7,44 @@ class IO
 class File
 
 trait Abstract:
-  type C >: CapSet <: CapSet^
-  def f(file: File^{C^}): Unit
+  type C^
+  def f(file: File^{C}): Unit
 
 class Concrete1 extends Abstract:
-  type C = CapSet
+  type C = {}
   def f(file: File) = ()
 
 class Concrete2(io: IO^) extends Abstract:
-  type C = CapSet^{io}
+  type C = {io}
   def f(file: File^{io}) = ()
 
 class Concrete3(io: IO^) extends Abstract:
-  type C = CapSet^{io}
+  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
+  type C^ <: {io}
+  def f(file: File^{C}): Unit
 
 class Concrete4(io: IO^) extends Abstract2(io):
-  type C = CapSet
+  type C = {}
   def f(file: File) = ()
 
 class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1):
-  type C = CapSet^{io2} // error
+  type C = {io2} // error
   def f(file: File^{io2}) = ()
 
 trait Abstract3[X^]:
-  type C >: CapSet <: X
-  def f(file: File^{C^}): Unit
+  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}]:
+  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}]:
+  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..fc7d4183f8c3 100644
--- a/tests/neg-custom-args/captures/i22005.scala
+++ b/tests/neg-custom-args/captures/i22005.scala
@@ -1,9 +1,10 @@
 
+import language.experimental.captureChecking
 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
+  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..7a38a6a94600
--- /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:
+  type Fv^ // the capability set occurring freely in the `block` passed to `boundary` below.
+
+def boundary[T, C^](block: Label{type Fv = {C} } ->{C} T): T = ??? // link label and block capture set
+def suspend[U](label: Label)[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..08ebaf1c63ee 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
+4 |val runOpsCheck: [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
+5 |val runOpsCheck2: [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..eb8e50f0b997 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 = [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
 
 
diff --git a/tests/neg-custom-args/captures/use-capset.check b/tests/neg-custom-args/captures/use-capset.check
index 6789ec306464..d10cf2ccaeb7 100644
--- a/tests/neg-custom-args/captures/use-capset.check
+++ b/tests/neg-custom-args/captures/use-capset.check
@@ -1,8 +1,8 @@
--- Error: tests/neg-custom-args/captures/use-capset.scala:5:50 ---------------------------------------------------------
-5 |private def g[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
+-- Error: tests/neg-custom-args/captures/use-capset.scala:5:49 ---------------------------------------------------------
+5 |private def g[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}
    |                      ^
diff --git a/tests/neg-custom-args/captures/use-capset.scala b/tests/neg-custom-args/captures/use-capset.scala
index 74288d616396..2d489a5061d8 100644
--- a/tests/neg-custom-args/captures/use-capset.scala
+++ b/tests/neg-custom-args/captures/use-capset.scala
@@ -1,14 +1,14 @@
 import caps.{use, CapSet}
 
-def f[C^](@use xs: List[Object^{C^}]): Unit = ???
+def f[C^](@use xs: List[Object^{C}]): Unit = ???
 
-private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error
+private def g[C^] = (xs: List[Object^{C}]) => xs.head // error
 
-private def g2[@use C^] = (xs: List[Object^{C^}]) => xs.head // ok
+private def g2[@use 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/pos-custom-args/captures/cap-paramlist8-desugared.scala b/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala
index e57946bdadc1..d34a81bcfa2f 100644
--- a/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala
+++ b/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala
@@ -12,17 +12,17 @@
       }
       val baz3:
         Int -> [C >: caps.CapSet <: caps.CapSet^,
-          D >: caps.CapSet <: caps.CapSet^{C^}, E >: caps.CapSet <:
-          caps.CapSet^{C^, x}] => () -> [F >: caps.CapSet^{x, y} <:
-          caps.CapSet^{C^, E^}] => (x: Int) -> (Ctx[F]) ?-> Int
+          D >: caps.CapSet <: caps.CapSet^{C}, E >: caps.CapSet <:
+          caps.CapSet^{C, x}] => () -> [F >: caps.CapSet^{x, y} <:
+          caps.CapSet^{C, E}] => (x: Int) -> (Ctx[F]) ?-> Int
         = (i: Int) => [
         C >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{cap},
-        D >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C^},
-        E >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C^, x}] =>
+        D >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C},
+        E >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C, x}] =>
         () => [
         F
             >: _root_.scala.caps.CapSet^{x, y} <:
-            _root_.scala.caps.CapSet^{C^, E^}
+            _root_.scala.caps.CapSet^{C, E}
         ] => (x: Int) => (ev: Ctx[F]) ?=> 1
       ()
     }
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..536b7f1d739a
--- /dev/null
+++ b/tests/pos-custom-args/captures/cap-paramlists.scala
@@ -0,0 +1,21 @@
+import language.experimental.captureChecking
+
+def test =
+  val x: Any^ = ???
+  val y: Any^ = ???
+  object O:
+    val z: Any^ = ???
+  def foo[A >: {y} <: {x},
+          B^,
+          C <: {x},
+          D^,
+          E <: {C},
+          F <: {C},
+          G <: {x, y},
+          H >: {x} <: {x,y}, T, U >: {x}]()[I^ <: {y, G, H},
+                                            J <: {O.z},
+                                            K <: {x, O.z},
+                                            L <: {x, y, O.z},
+                                            M >: {x, y, O.z} <: {C},
+                                            N >: {x} <: {x},
+                                            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..2b62d0f0f34c
--- /dev/null
+++ b/tests/pos-custom-args/captures/cap-paramlists2.scala
@@ -0,0 +1,6 @@
+import language.experimental.captureChecking
+
+trait Bar:
+  type C^
+
+def useFoo[D^](x: Bar { 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..29ae98cb98d6
--- /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 = [C^, D <: {C}, E^ <: {C,x}, 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..179da36ef1ed
--- /dev/null
+++ b/tests/pos-custom-args/captures/cap-paramlists4.scala
@@ -0,0 +1,6 @@
+import language.experimental.captureChecking
+
+trait Foo[U^, V^, W^]:
+  type C = {caps.cap}
+  type D = {caps.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..ec0933017c36
--- /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[A^, B^ >: {A}, T, U](x: Int) = 1
+  foo[{x}, {x}, Int, String](0)
+  foo[{}, {}, { def bar: Int }, { type D^ = {x} }](0)
+  trait Foo { type D^ }
+  foo[{}, {}, Foo, Foo](0)
+  foo[A = {x}, B = {x}](0)
+  foo[A = {x}](0)
+  foo[T = Int](0)
+  foo[T = Int, A = {x}](1)
+  foo[A = {x}, T = Int](1)
+  foo[B = {}, U = String, 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..d0567089556c
--- /dev/null
+++ b/tests/pos-custom-args/captures/cap-paramlists6.scala
@@ -0,0 +1,8 @@
+import language.experimental.captureChecking
+
+def test =
+  val x: Any^ = ???
+  val y: Any^ = ???
+  object O:
+    val z: Any^ = ???
+  val baz  = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E}] => (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..abe59edd6835
--- /dev/null
+++ b/tests/pos-custom-args/captures/cap-paramlists7.scala
@@ -0,0 +1,8 @@
+import language.experimental.captureChecking
+
+def test =
+  val x: Any^ = ???
+  val y: Any^ = ???
+  object O:
+    val z: Any^ = ???
+  val baz2 = (i: Int) => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E}] => (x: Int) => 1
\ No newline at end of file
diff --git a/tests/pos-custom-args/captures/cap-paramlists8.scala b/tests/pos-custom-args/captures/cap-paramlists8.scala
new file mode 100644
index 000000000000..f300e857282a
--- /dev/null
+++ b/tests/pos-custom-args/captures/cap-paramlists8.scala
@@ -0,0 +1,8 @@
+import language.experimental.captureChecking
+
+def test =
+  val x: Any^ = ???
+  val y: Any^ = ???
+  object O:
+    val z: Any^ = ???
+  val baz3 = (i: Int) => [C^, D^ <: {C}, E^ <: {C,x}] => () => [F^ >: {x,y} <: {C,E}] => (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..00b6486a6819
--- /dev/null
+++ b/tests/pos-custom-args/captures/capset-members.scala
@@ -0,0 +1,24 @@
+import language.experimental.captureChecking
+
+def test =
+  val x: Any^ = ???
+  val y: Any^ = ???
+  object O:
+    val z: Any^ = ???
+  trait CaptureSet:
+    type A^ >: {y} <: {x}
+    type B = {x}
+    type C <: {x}
+    type D^
+    type E <: {C}
+    type F <: {C}
+    type G <: {x, y}
+    type H >: {x} <: {x,y}
+    type I^ = {y, G, H}
+    type J = {O.z}
+    type K^ = {x, O.z}
+    type L <: {x, y, O.z}
+    type M >: {x, y, O.z} <: {C}
+    type N >: {x} <: {x}
+    type O >: {O.z} <: {O.z}
+    type P >: {B,D}
\ 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..13d527d0b556
--- /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[A^, T]:
+    type C^ <: {A}
+  abstract class Bar extends Foo[{x,y,O.z}, String]:
+    override type C^ <: {x,y}
+  class Baz extends Bar:
+    final override 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..96bd58d65334 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[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 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..c76e6067fbef 100644
--- a/tests/pos-custom-args/captures/cc-poly-source-capability.scala
+++ b/tests/pos-custom-args/captures/cc-poly-source-capability.scala
@@ -12,14 +12,14 @@ import caps.use
   class Listener
 
   class Source[X^]:
-    private var listeners: Set[Listener^{X^}] = Set.empty
-    def register(x: Listener^{X^}): Unit =
+    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..d39d4a9c30e5 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^]
 
-extension[T, Cap^](src: Source[T, Cap]^)
-  def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{src, f} = ???
+extension[T, 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^](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^} =
+    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..0b6fe3a9a0e9 100644
--- a/tests/pos-custom-args/captures/gears-problem-poly.scala
+++ b/tests/pos-custom-args/captures/gears-problem-poly.scala
@@ -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, 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, 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..4113c6a2998e 100644
--- a/tests/pos-custom-args/captures/i21313.scala
+++ b/tests/pos-custom-args/captures/i21313.scala
@@ -1,20 +1,21 @@
 import caps.CapSet
 
 trait Async:
-  def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, Cap]^): T =
+  def await[T, 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.
+  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..a8fbb4fb6680 100644
--- a/tests/pos-custom-args/captures/i21347.scala
+++ b/tests/pos-custom-args/captures/i21347.scala
@@ -4,7 +4,7 @@ import language.experimental.captureChecking
 
 class Box[Cap^] {}
 
-def run[Cap^](f: Box[Cap]^{Cap^} => Unit): Box[Cap]^{Cap^} = ???
+def run[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..455aa3940fb6 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
+  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^](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/neg-custom-args/captures/i21868.scala b/tests/pos-custom-args/captures/i21868.scala
similarity index 57%
rename from tests/neg-custom-args/captures/i21868.scala
rename to tests/pos-custom-args/captures/i21868.scala
index 876b68ac90a4..5385902c2c53 100644
--- a/tests/neg-custom-args/captures/i21868.scala
+++ b/tests/pos-custom-args/captures/i21868.scala
@@ -1,12 +1,13 @@
+import language.experimental.captureChecking
 import caps.*
 
 trait AbstractWrong:
   type C <: CapSet
-  def f(): Unit^{C^} // error
+  def f(): Unit^{C}
 
 trait Abstract1:
-  type C >: CapSet <: CapSet^
-  def f(): Unit^{C^}
+  type C^
+  def f(): Unit^{C}
 
 // class Abstract2:
 //   type C^
diff --git a/tests/pos-custom-args/captures/polycap.scala b/tests/pos-custom-args/captures/polycap.scala
index 684f46454595..4d830ed279b8 100644
--- a/tests/pos-custom-args/captures/polycap.scala
+++ b/tests/pos-custom-args/captures/polycap.scala
@@ -2,10 +2,10 @@ import language.experimental.captureChecking
 
 class Source[+T, Cap^]
 
-def completed[T, Cap^](result: T): Source[T, Cap] =
+def completed[T, 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..8c7de185e71b 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[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..c10048a78cce 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[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..c10048a78cce 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[C^](x: AnyRef^{C}): Unit = ???