@@ -559,6 +559,62 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
559559 sepApplyError(fn, parts, arg, app)
560560 end checkApply
561561
562+ /** Compute the set of capabilities that are consumed along a selection path.
563+ * For example, if we have the path `a.b` where `b` is a consume field with type `B^{x}`,
564+ * this returns the actual consumed capabilities `{x}` from the type's capture set.
565+ *
566+ * This walks the path and for each consume field encountered, collects the capabilities
567+ * from that field's widened type's capture set.
568+ *
569+ * @param cap The capability to analyze (e.g., `a.b`)
570+ * @return The set of actual consumed capabilities from all consume fields along the path
571+ */
572+ def consumedByPath (cap : Capability )(using Context ): Refs = cap match
573+ case ref : TermRef =>
574+ def recur (tp : Type , acc : Refs ): Refs = tp match
575+ case ref : TermRef =>
576+ // This is a selection like a.b, or just a reference like a
577+ val fieldSym = ref.symbol
578+ val accWithCurrent =
579+ if fieldSym.exists && fieldSym.isConsumeParam then
580+ // This field is a consume parameter. Collect capabilities from its type's capture set.
581+ acc ++ ref.widen.captureSet.elems
582+ else
583+ acc
584+ // Also check if the current reference's type has consume fields
585+ val accWithConsumeFields = accWithCurrent ++ capsFromConsumeFields(ref)
586+ // Recurse on the prefix if it's a selection
587+ if ref.prefix ne NoPrefix then
588+ recur(ref.prefix, accWithConsumeFields)
589+ else
590+ accWithConsumeFields
591+ case _ =>
592+ acc
593+
594+ def capsFromConsumeFields (ref : TermRef ): Refs =
595+ // For types with consume fields, use the capture set of the reference itself
596+ // and recursively check what those capabilities consume
597+ val tpe = ref.widen.dealias
598+ val cls = tpe.classSymbol
599+ if cls.exists then
600+ // Check if this class has any consume fields
601+ val hasConsumeFields = cls.info.decls.exists(m => m.isConsumeParam && m.isTerm)
602+ if hasConsumeFields then
603+ // Use the capture set of the reference itself (e.g., {a} for holder.a : A^{a})
604+ // Return both the capabilities in the capture set AND what they transitively consume
605+ var result = ref.widen.captureSet.elems
606+ for cap <- ref.widen.captureSet.elems do
607+ result = result ++ consumedByPath(cap)
608+ result
609+ else
610+ emptyRefs
611+ else
612+ emptyRefs
613+
614+ recur(ref, emptyRefs)
615+ case _ =>
616+ emptyRefs
617+
562618 /** 1. Check that the capabilities used at `tree` don't overlap with
563619 * capabilities hidden by a previous definition.
564620 * 2. Also check that none of the used capabilities was consumed before.
@@ -598,8 +654,15 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
598654 for ref <- used do
599655 val pos = consumed.clashing(ref)
600656 if pos != null then
601- // println(i"consumed so far ${consumed.refs.toList} with peaks ${consumed.directPeaks.toList}, used = $used, exposed = ${ref.directPeaks }")
602- consumeError(ref, pos, tree.srcPos)
657+ // Check if this reference should be exempted because consume fields along
658+ // the path own the consumed capabilities.
659+ val pathConsumed = consumedByPath(ref)
660+
661+ // Check if any capability consumed by the path was consumed at position `pos`.
662+ val shouldExempt = pathConsumed.exists(consumed.get(_) == pos)
663+
664+ if ! shouldExempt then
665+ consumeError(ref, pos, tree.srcPos)
603666 end checkUse
604667
605668 /** If `tp` denotes some version of a singleton capability `x.type` the set `{x, x*}`
0 commit comments