Skip to content

Commit fff8c0d

Browse files
authored
Refine checking for outer references (#16122)
Outer references should also count anywhere in a type if we are in the scope of an inline method. Expansions of calls to these methods will have to reference these types using outer accessors. Fixes #16119
2 parents 19dcba5 + 25d0205 commit fff8c0d

File tree

2 files changed

+113
-52
lines changed

2 files changed

+113
-52
lines changed

compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala

Lines changed: 78 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,7 @@ class ExplicitOuter extends MiniPhase with InfoTransformer { thisPhase =>
7272
override def transformTemplate(impl: Template)(using Context): Tree = {
7373
val cls = ctx.owner.asClass
7474
val isTrait = cls.is(Trait)
75-
if (needsOuterIfReferenced(cls) &&
76-
!needsOuterAlways(cls) &&
77-
impl.existsSubTree(referencesOuter(cls, _)))
75+
if needsOuterIfReferenced(cls) && !needsOuterAlways(cls) && referencesOuter(cls, impl) then
7876
ensureOuterAccessors(cls)
7977

8078
val clsHasOuter = hasOuter(cls)
@@ -255,55 +253,83 @@ object ExplicitOuter {
255253

256254
/** Tree references an outer class of `cls` which is not a static owner.
257255
*/
258-
def referencesOuter(cls: Symbol, tree: Tree)(using Context): Boolean = {
259-
def isOuterSym(sym: Symbol) =
260-
!sym.isStaticOwner && cls.isProperlyContainedIn(sym)
261-
def isOuterRef(ref: Type): Boolean = ref match {
262-
case ref: ThisType =>
263-
isOuterSym(ref.cls)
264-
case ref: TermRef =>
265-
if (ref.prefix ne NoPrefix)
266-
!ref.symbol.isStatic && isOuterRef(ref.prefix)
267-
else (
268-
ref.symbol.isOneOf(HoistableFlags) &&
269-
// ref.symbol will be placed in enclosing class scope by LambdaLift, so it might need
270-
// an outer path then.
271-
isOuterSym(ref.symbol.owner.enclosingClass)
272-
||
273-
// If not hoistable, ref.symbol will get a proxy in immediately enclosing class. If this properly
274-
// contains the current class, it needs an outer path.
275-
// If the symbol is hoistable, it might have free variables for which the same
276-
// reasoning applies. See pos/i1664.scala
277-
ctx.owner.enclosingClass.owner.enclosingClass.isContainedIn(ref.symbol.owner)
278-
)
279-
case _ => false
280-
}
281-
def hasOuterPrefix(tp: Type): Boolean = tp.stripped match {
282-
case AppliedType(tycon, _) => hasOuterPrefix(tycon)
283-
case TypeRef(prefix, _) => isOuterRef(prefix)
284-
case _ => false
285-
}
286-
def containsOuterRefs(tp: Type): Boolean = tp match
287-
case tp: SingletonType => isOuterRef(tp)
288-
case tp: AndOrType => containsOuterRefs(tp.tp1) || containsOuterRefs(tp.tp2)
289-
case _ => false
290-
tree match {
291-
case _: This | _: Ident => isOuterRef(tree.tpe)
292-
case nw: New =>
293-
val newCls = nw.tpe.classSymbol
294-
isOuterSym(newCls.owner.enclosingClass) ||
295-
hasOuterPrefix(nw.tpe) ||
296-
newCls.owner.isTerm && cls.isProperlyContainedIn(newCls)
297-
// newCls might get proxies for free variables. If current class is
298-
// properly contained in newCls, it needs an outer path to newCls access the
299-
// proxies and forward them to the new instance.
300-
case app: TypeApply if app.symbol.isTypeTest =>
301-
// Type tests of singletons translate to `eq` tests with references, which might require outer pointers
302-
containsOuterRefs(app.args.head.tpe)
303-
case _ =>
304-
false
305-
}
306-
}
256+
def referencesOuter(cls: Symbol, tree: Tree)(using Context): Boolean =
257+
258+
259+
val test = new TreeAccumulator[Boolean]:
260+
private var inInline = false
261+
262+
def isOuterSym(sym: Symbol) =
263+
!sym.isStaticOwner && cls.isProperlyContainedIn(sym)
264+
265+
def isOuterRef(ref: Type): Boolean = ref match
266+
case ref: ThisType =>
267+
isOuterSym(ref.cls)
268+
case ref: TermRef =>
269+
if (ref.prefix ne NoPrefix)
270+
!ref.symbol.isStatic && isOuterRef(ref.prefix)
271+
else (
272+
ref.symbol.isOneOf(HoistableFlags) &&
273+
// ref.symbol will be placed in enclosing class scope by LambdaLift, so it might need
274+
// an outer path then.
275+
isOuterSym(ref.symbol.owner.enclosingClass)
276+
||
277+
// If not hoistable, ref.symbol will get a proxy in immediately enclosing class. If this properly
278+
// contains the current class, it needs an outer path.
279+
// If the symbol is hoistable, it might have free variables for which the same
280+
// reasoning applies. See pos/i1664.scala
281+
ctx.owner.enclosingClass.owner.enclosingClass.isContainedIn(ref.symbol.owner)
282+
)
283+
case _ => false
284+
285+
def hasOuterPrefix(tp: Type): Boolean = tp.stripped match
286+
case AppliedType(tycon, _) => hasOuterPrefix(tycon)
287+
case TypeRef(prefix, _) => isOuterRef(prefix)
288+
case _ => false
289+
290+
def containsOuterRefsAtTopLevel(tp: Type): Boolean = tp match
291+
case tp: SingletonType => isOuterRef(tp)
292+
case tp: AndOrType => containsOuterRefsAtTopLevel(tp.tp1) || containsOuterRefsAtTopLevel(tp.tp2)
293+
case _ => false
294+
295+
def containsOuterRefsAnywhere(tp: Type): Boolean =
296+
tp.existsPart({
297+
case t: SingletonType => isOuterRef(t)
298+
case _ => false
299+
}, StopAt.Static)
300+
301+
def containsOuterRefs(t: Tree): Boolean = t match
302+
case _: This | _: Ident => isOuterRef(t.tpe)
303+
case nw: New =>
304+
val newCls = nw.tpe.classSymbol
305+
isOuterSym(newCls.owner.enclosingClass) ||
306+
hasOuterPrefix(nw.tpe) ||
307+
newCls.owner.isTerm && cls.isProperlyContainedIn(newCls)
308+
// newCls might get proxies for free variables. If current class is
309+
// properly contained in newCls, it needs an outer path to newCls access the
310+
// proxies and forward them to the new instance.
311+
case app: TypeApply if app.symbol.isTypeTest =>
312+
// Type tests of singletons translate to `eq` tests with references, which might require outer pointers
313+
containsOuterRefsAtTopLevel(app.args.head.tpe)
314+
case t: TypeTree if inInline =>
315+
// Expansions of inline methods must be able to address outer types
316+
containsOuterRefsAnywhere(t.tpe)
317+
case _ =>
318+
false
319+
320+
def apply(x: Boolean, t: Tree)(using Context) =
321+
if x || containsOuterRefs(t) then true
322+
else t match
323+
case t: DefDef if t.symbol.isInlineMethod =>
324+
val saved = inInline
325+
inInline = true
326+
try foldOver(x, t)
327+
finally inInline = saved
328+
case _ =>
329+
foldOver(x, t)
330+
331+
test(false, tree)
332+
end referencesOuter
307333

308334
private final val HoistableFlags = Method | Lazy | Module
309335

tests/pos/i16119.scala

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
class Foo:
2+
self =>
3+
type T = this.type
4+
val foo: T = ???
5+
object bar:
6+
inline def baz(): Any =
7+
??? : T
8+
9+
bar.baz()
10+
11+
class Foo2:
12+
self =>
13+
object bar:
14+
inline def baz(): Any = ??? : self.type
15+
16+
bar.baz()
17+
18+
class Foo3:
19+
20+
type T
21+
object bar:
22+
inline def baz(): Any = ??? : List[T]
23+
24+
bar.baz()
25+
26+
class Foo4:
27+
self =>
28+
29+
type T
30+
object bar:
31+
inline def baz(): Any =
32+
val xs: Foo4 { type T = self.T } = ???
33+
xs
34+
35+
bar.baz()

0 commit comments

Comments
 (0)