@@ -23,6 +23,7 @@ import collection.mutable
2323import ProtoTypes .*
2424import staging .StagingLevel
2525import inlines .Inlines .inInlineMethod
26+ import cc .{isRetainsLike , CaptureAnnotation }
2627
2728import dotty .tools .backend .jvm .DottyBackendInterface .symExtensions
2829
@@ -162,17 +163,34 @@ object TreeChecker {
162163 */
163164 def checkNoOrphans (tp0 : Type , tree : untpd.Tree = untpd.EmptyTree )(using Context ): Type = new TypeMap () {
164165 val definedBinders = new java.util.IdentityHashMap [Type , Any ]
166+ private var inRetainingAnnot = false
167+
168+ def insideRetainingAnnot [T ](op : => T ): T =
169+ val saved = inRetainingAnnot
170+ inRetainingAnnot = true
171+ try op finally inRetainingAnnot = saved
172+
165173 def apply (tp : Type ): Type = {
166174 tp match {
167175 case tp : BindingType =>
168176 definedBinders.put(tp, tp)
169177 mapOver(tp)
170178 definedBinders.remove(tp)
171179 case tp : ParamRef =>
172- assert(definedBinders.get(tp.binder) != null , s " orphan param: ${tp.show}, hash of binder = ${System .identityHashCode(tp.binder)}, tree = ${tree.show}, type = $tp0" )
180+ val isValidRef =
181+ definedBinders.get(tp.binder) != null
182+ || inRetainingAnnot
183+ // Inside a normal @retains annotation, the captured references could be ill-formed. See issue #19661.
184+ // But this is ok since capture checking does not rely on them.
185+ assert(isValidRef, s " orphan param: ${tp.show}, hash of binder = ${System .identityHashCode(tp.binder)}, tree = ${tree.show}, type = $tp0" )
173186 case tp : TypeVar =>
174187 assert(tp.isInstantiated, s " Uninstantiated type variable: ${tp.show}, tree = ${tree.show}" )
175188 apply(tp.underlying)
189+ case tp @ AnnotatedType (underlying, annot) if annot.symbol.isRetainsLike && ! annot.isInstanceOf [CaptureAnnotation ] =>
190+ val underlying1 = this (underlying)
191+ val annot1 = insideRetainingAnnot :
192+ annot.mapWith(this )
193+ derivedAnnotatedType(tp, underlying1, annot1)
176194 case _ =>
177195 mapOver(tp)
178196 }
0 commit comments