Skip to content

Commit 20c84d6

Browse files
authored
Merge pull request #12815 from dotty-staging/fix-opaque-inline
Drop "no inlines with opaques" implementation restriction
2 parents b1d5760 + d4e8f5d commit 20c84d6

30 files changed

+362
-77
lines changed

compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ trait ConstraintHandling {
9999
val bound = dropWildcards(rawBound)
100100
val oldBounds @ TypeBounds(lo, hi) = constraint.nonParamBounds(param)
101101
val equalBounds = (if isUpper then lo else hi) eq bound
102-
if equalBounds && !bound.existsPart(_ eq param, stopAtStatic = true) then
102+
if equalBounds && !bound.existsPart(_ eq param, StopAt.Static) then
103103
// The narrowed bounds are equal and not recursive,
104104
// so we can remove `param` from the constraint.
105105
constraint = constraint.replace(param, bound)

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2546,7 +2546,7 @@ object SymDenotations {
25462546
}
25472547

25482548
private[SymDenotations] def stillValidInOwner(denot: SymDenotation)(using Context): Boolean = try
2549-
val owner = denot.owner.denot
2549+
val owner = denot.maybeOwner.denot
25502550
stillValid(owner)
25512551
&& (
25522552
!owner.isClass

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1391,7 +1391,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
13911391
*/
13921392
def canCompare(ts: Set[Type]) =
13931393
ctx.phase.isTyper
1394-
|| !ts.exists(_.existsPart(_.isInstanceOf[SkolemType], stopAtStatic = true))
1394+
|| !ts.exists(_.existsPart(_.isInstanceOf[SkolemType], StopAt.Static))
13951395

13961396
def verified(result: Boolean): Boolean =
13971397
if Config.checkAtomsComparisons then

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -439,23 +439,23 @@ object Types {
439439

440440
/** Does this type contain wildcard types? */
441441
final def containsWildcardTypes(using Context) =
442-
existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true, forceLazy = false)
442+
existsPart(_.isInstanceOf[WildcardType], StopAt.Static, forceLazy = false)
443443

444444
// ----- Higher-order combinators -----------------------------------
445445

446446
/** Returns true if there is a part of this type that satisfies predicate `p`.
447447
*/
448-
final def existsPart(p: Type => Boolean, stopAtStatic: Boolean = false, forceLazy: Boolean = true)(using Context): Boolean =
449-
new ExistsAccumulator(p, stopAtStatic, forceLazy).apply(false, this)
448+
final def existsPart(p: Type => Boolean, stopAt: StopAt = StopAt.None, forceLazy: Boolean = true)(using Context): Boolean =
449+
new ExistsAccumulator(p, stopAt, forceLazy).apply(false, this)
450450

451451
/** Returns true if all parts of this type satisfy predicate `p`.
452452
*/
453453
final def forallParts(p: Type => Boolean)(using Context): Boolean =
454454
!existsPart(!p(_))
455455

456456
/** Performs operation on all parts of this type */
457-
final def foreachPart(p: Type => Unit, stopAtStatic: Boolean = false)(using Context): Unit =
458-
new ForeachAccumulator(p, stopAtStatic).apply((), this)
457+
final def foreachPart(p: Type => Unit, stopAt: StopAt = StopAt.None)(using Context): Unit =
458+
new ForeachAccumulator(p, stopAt).apply((), this)
459459

460460
/** The parts of this type which are type or term refs and which
461461
* satisfy predicate `p`.
@@ -5199,6 +5199,12 @@ object Types {
51995199

52005200
// ----- TypeMaps --------------------------------------------------------------------
52015201

5202+
/** Where a traversal should stop */
5203+
enum StopAt:
5204+
case None // traverse everything
5205+
case Package // stop at package references
5206+
case Static // stop at static references
5207+
52025208
/** Common base class of TypeMap and TypeAccumulator */
52035209
abstract class VariantTraversal:
52045210
protected[core] var variance: Int = 1
@@ -5211,7 +5217,7 @@ object Types {
52115217
res
52125218
}
52135219

5214-
protected def stopAtStatic: Boolean = true
5220+
protected def stopAt: StopAt = StopAt.Static
52155221

52165222
/** Can the prefix of this static reference be omitted if the reference
52175223
* itself can be omitted? Overridden in TypeOps#avoid.
@@ -5220,7 +5226,11 @@ object Types {
52205226

52215227
protected def stopBecauseStaticOrLocal(tp: NamedType)(using Context): Boolean =
52225228
(tp.prefix eq NoPrefix)
5223-
|| stopAtStatic && tp.currentSymbol.isStatic && isStaticPrefix(tp.prefix)
5229+
|| {
5230+
val stop = stopAt
5231+
stop == StopAt.Static && tp.currentSymbol.isStatic && isStaticPrefix(tp.prefix)
5232+
|| stop == StopAt.Package && tp.currentSymbol.is(Package)
5233+
}
52245234
end VariantTraversal
52255235

52265236
abstract class TypeMap(implicit protected var mapCtx: Context)
@@ -5409,7 +5419,7 @@ object Types {
54095419
derivedClassInfo(tp, this(tp.prefix))
54105420

54115421
def andThen(f: Type => Type): TypeMap = new TypeMap {
5412-
override def stopAtStatic = thisMap.stopAtStatic
5422+
override def stopAt = thisMap.stopAt
54135423
def apply(tp: Type) = f(thisMap(tp))
54145424
}
54155425
}
@@ -5831,12 +5841,12 @@ object Types {
58315841

58325842
class ExistsAccumulator(
58335843
p: Type => Boolean,
5834-
override val stopAtStatic: Boolean,
5844+
override val stopAt: StopAt,
58355845
forceLazy: Boolean)(using Context) extends TypeAccumulator[Boolean]:
58365846
def apply(x: Boolean, tp: Type): Boolean =
58375847
x || p(tp) || (forceLazy || !tp.isInstanceOf[LazyRef]) && foldOver(x, tp)
58385848

5839-
class ForeachAccumulator(p: Type => Unit, override val stopAtStatic: Boolean)(using Context) extends TypeAccumulator[Unit] {
5849+
class ForeachAccumulator(p: Type => Unit, override val stopAt: StopAt)(using Context) extends TypeAccumulator[Unit] {
58405850
def apply(x: Unit, tp: Type): Unit = foldOver(p(tp), tp)
58415851
}
58425852

compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
700700
}
701701
// Cannot use standard `existsPart` method because it calls `lookupRefined`
702702
// which can cause CyclicReference errors.
703-
val isBoundAccumulator = new ExistsAccumulator(isBound, stopAtStatic = true, forceLazy = true):
703+
val isBoundAccumulator = new ExistsAccumulator(isBound, StopAt.Static, forceLazy = true):
704704
override def foldOver(x: Boolean, tp: Type): Boolean = tp match
705705
case tp: TypeRef => applyToPrefix(x, tp)
706706
case _ => super.foldOver(x, tp)

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
7878
}
7979

8080
override def nameString(name: Name): String =
81-
if ctx.settings.YdebugNames.value then name.debugString
81+
def strippedName = if printDebug then name else name.stripModuleClassSuffix
82+
if ctx.settings.YdebugNames.value then strippedName.debugString
8283
else if name.isTypeName && name.is(WildcardParamName) && !printDebug then "_"
83-
else super.nameString(name)
84+
else super.nameString(strippedName)
8485

8586
override protected def simpleNameString(sym: Symbol): String =
8687
nameString(if (ctx.property(XprintMode).isEmpty) sym.initial.name else sym.name)

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,7 @@ trait Checking {
10881088
}
10891089
case _ =>
10901090
}
1091-
tp.foreachPart(check, stopAtStatic = true)
1091+
tp.foreachPart(check, StopAt.Static)
10921092
if (ok) tp else UnspecifiedErrorType
10931093
}
10941094

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ trait ImplicitRunInfo:
732732
case null =>
733733
record(i"implicitScope")
734734
val liftToAnchors = new TypeMap:
735-
override def stopAtStatic = true
735+
override def stopAt = StopAt.Static
736736
private val seen = util.HashSet[Type]()
737737

738738
def applyToUnderlying(t: TypeProxy) =

compiler/src/dotty/tools/dotc/typer/Inliner.scala

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -556,18 +556,93 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
556556
ref(lastSelf).outerSelect(lastLevel - level, selfSym.info)
557557
else
558558
inlineCallPrefix
559-
val binding = ValDef(selfSym.asTerm, QuoteUtils.changeOwnerOfTree(rhs, selfSym)).withSpan(selfSym.span)
559+
val binding = accountForOpaques(
560+
ValDef(selfSym.asTerm, QuoteUtils.changeOwnerOfTree(rhs, selfSym)).withSpan(selfSym.span))
560561
bindingsBuf += binding
561562
inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}")
562563
lastSelf = selfSym
563564
lastLevel = level
564565
}
565566
}
566567

568+
/** A list of pairs between TermRefs appearing in thisProxy bindings that
569+
* refer to objects with opaque type aliases and local proxy symbols
570+
* that contain refined versions of these TermRefs where the aliases
571+
* are exposed.
572+
*/
573+
private val opaqueProxies = new mutable.ListBuffer[(TermRef, TermRef)]
574+
575+
/** Map first halfs of opaqueProxies pairs to second halfs, using =:= as equality */
576+
def mapRef(ref: TermRef): Option[TermRef] =
577+
opaqueProxies.collectFirst {
578+
case (from, to) if from.symbol == ref.symbol && from =:= ref => to
579+
}
580+
581+
/** If `binding` contains TermRefs that refer to objects with opaque
582+
* type aliases, add proxy definitions that expose these aliases
583+
* and substitute such TermRefs with theproxies. Example from pos/opaque-inline1.scala:
584+
*
585+
* object refined:
586+
* opaque type Positive = Int
587+
* inline def Positive(value: Int): Positive = f(value)
588+
* def f(x: Positive): Positive = x
589+
* def run: Unit = { val x = 9; val nine = refined.Positive(x) }
590+
*
591+
* This generates the following proxies:
592+
*
593+
* val $proxy1: refined.type{type Positive = Int} =
594+
* refined.$asInstanceOf$[refined.type{type Positive = Int}]
595+
* val refined$_this: ($proxy1 : refined.type{Positive = Int}) =
596+
* $proxy1
597+
*
598+
* and every reference to `refined` in the inlined expression is replaced by
599+
* `refined_$this`.
600+
*/
601+
def accountForOpaques(binding: ValDef)(using Context): ValDef =
602+
binding.symbol.info.foreachPart {
603+
case ref: TermRef =>
604+
for cls <- ref.widen.classSymbols do
605+
if cls.containsOpaques && mapRef(ref).isEmpty then
606+
def openOpaqueAliases(selfType: Type): List[(Name, Type)] = selfType match
607+
case RefinedType(parent, rname, TypeAlias(alias)) =>
608+
val opaq = cls.info.member(rname).symbol
609+
if opaq.isOpaqueAlias then
610+
(rname, alias.stripLazyRef.asSeenFrom(ref, cls))
611+
:: openOpaqueAliases(parent)
612+
else Nil
613+
case _ =>
614+
Nil
615+
val refinements = openOpaqueAliases(cls.givenSelfType)
616+
val refinedType = refinements.foldLeft(ref: Type) ((parent, refinement) =>
617+
RefinedType(parent, refinement._1, TypeAlias(refinement._2))
618+
)
619+
val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType).asTerm
620+
val refiningDef = ValDef(refiningSym, tpd.ref(ref).cast(refinedType)).withSpan(binding.span)
621+
inlining.println(i"add opaque alias proxy $refiningDef")
622+
bindingsBuf += refiningDef
623+
opaqueProxies += ((ref, refiningSym.termRef))
624+
case _ =>
625+
}
626+
if opaqueProxies.isEmpty then binding
627+
else
628+
val mapType = new TypeMap:
629+
override def stopAt = StopAt.Package
630+
def apply(t: Type) = mapOver {
631+
t match
632+
case ref: TermRef => mapRef(ref).getOrElse(ref)
633+
case _ => t
634+
}
635+
binding.symbol.info = mapType(binding.symbol.info)
636+
val mapTree = TreeTypeMap(typeMap = mapType)
637+
mapTree.transform(binding).asInstanceOf[ValDef]
638+
.showing(i"transformed this binding exposing opaque aliases: $result", inlining)
639+
end accountForOpaques
640+
567641
private def canElideThis(tpe: ThisType): Boolean =
568-
inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) ||
569-
tpe.cls.isContainedIn(inlinedMethod) ||
570-
tpe.cls.is(Package)
642+
inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls)
643+
|| tpe.cls.isContainedIn(inlinedMethod)
644+
|| tpe.cls.is(Package)
645+
|| tpe.cls.isStaticOwner && !(tpe.cls.seesOpaques && inlinedMethod.isContainedIn(tpe.cls))
571646

572647
/** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions:
573648
* - synthetic case class apply methods, when the case class constructor is empty, are
@@ -666,12 +741,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
666741
case _ =>
667742
}
668743

744+
private val registerTypes = new TypeTraverser:
745+
override def stopAt = StopAt.Package
746+
override def traverse(t: Type) =
747+
registerType(t)
748+
traverseChildren(t)
749+
669750
/** Register type of leaf node */
670-
private def registerLeaf(tree: Tree): Unit = tree match {
671-
case _: This | _: Ident | _: TypeTree =>
672-
tree.typeOpt.foreachPart(registerType, stopAtStatic = true)
751+
private def registerLeaf(tree: Tree): Unit = tree match
752+
case _: This | _: Ident | _: TypeTree => registerTypes.traverse(tree.typeOpt)
673753
case _ =>
674-
}
675754

676755
/** Make `tree` part of inlined expansion. This means its owner has to be changed
677756
* from its `originalOwner`, and, if it comes from outside the inlined method
@@ -797,6 +876,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
797876
val inliner = new InlinerMap(
798877
typeMap =
799878
new DeepTypeMap {
879+
override def stopAt =
880+
if opaqueProxies.isEmpty then StopAt.Static else StopAt.Package
800881
def apply(t: Type) = t match {
801882
case t: ThisType => thisProxy.getOrElse(t.cls, t)
802883
case t: TypeRef => paramProxy.getOrElse(t, mapOver(t))
@@ -915,7 +996,17 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
915996

916997
// Take care that only argument bindings go into `bindings`, since positions are
917998
// different for bindings from arguments and bindings from body.
918-
tpd.Inlined(call, finalBindings, finalExpansion)
999+
val res = tpd.Inlined(call, finalBindings, finalExpansion)
1000+
if opaqueProxies.isEmpty then res
1001+
else
1002+
val target =
1003+
if inlinedMethod.is(Transparent) then call.tpe & res.tpe
1004+
else call.tpe
1005+
res.ensureConforms(target)
1006+
// Make sure that the sealing with the declared type
1007+
// is type correct. Without it we might get problems since the
1008+
// expression's type is the opaque alias but the call's type is
1009+
// the opaque type itself. An example is in pos/opaque-inline1.scala.
9191010
}
9201011
}
9211012

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1519,7 +1519,7 @@ class Namer { typer: Typer =>
15191519
approxTp.stripPoly match
15201520
case atp @ defn.ContextFunctionType(_, resType, _)
15211521
if !defn.isNonRefinedFunction(atp) // in this case `resType` is lying, gives us only the non-dependent upper bound
1522-
|| resType.existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true, forceLazy = false) =>
1522+
|| resType.existsPart(_.isInstanceOf[WildcardType], StopAt.Static, forceLazy = false) =>
15231523
originalTp
15241524
case _ =>
15251525
approxTp

compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,6 @@ object PrepareInlineable {
259259
}
260260

261261
private def checkInlineMethod(inlined: Symbol, body: Tree)(using Context): body.type = {
262-
if (inlined.owner.isClass && inlined.owner.seesOpaques)
263-
report.error(em"Implementation restriction: No inline methods allowed where opaque type aliases are in scope", inlined.srcPos)
264262
if Inliner.inInlineMethod(using ctx.outer) then
265263
report.error(ex"Implementation restriction: nested inline methods are not supported", inlined.srcPos)
266264

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2803,7 +2803,7 @@ class Typer extends Namer
28032803
// see tests/pos/i7778b.scala
28042804

28052805
val paramTypes = {
2806-
val hasWildcard = formals.exists(_.existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true))
2806+
val hasWildcard = formals.exists(_.existsPart(_.isInstanceOf[WildcardType], StopAt.Static))
28072807
if hasWildcard then formals.map(_ => untpd.TypeTree())
28082808
else formals.map(untpd.TypeTree)
28092809
}

0 commit comments

Comments
 (0)