Skip to content

Commit ab1a18f

Browse files
committed
Warn if inline accessors are generated
1 parent 42b31a4 commit ab1a18f

File tree

29 files changed

+317
-83
lines changed

29 files changed

+317
-83
lines changed

compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import dotty.tools.dotc.core.Contexts.Context
99
import dotty.tools.dotc.report
1010
import dotty.tools.dotc.core.Phases
1111

12+
import scala.annotation.binaryAPI
13+
1214
/**
1315
* Functionality needed in the post-processor whose implementation depends on the compiler
1416
* frontend. All methods are synchronized.
@@ -20,6 +22,7 @@ sealed abstract class PostProcessorFrontendAccess {
2022
def backendReporting: BackendReporting
2123
def getEntryPoints: List[String]
2224

25+
@binaryAPI
2326
private val frontendLock: AnyRef = new Object()
2427
inline final def frontendSynch[T](inline x: => T): T = frontendLock.synchronized(x)
2528
}

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import NameKinds.AvoidNameKind
1515
import util.SimpleIdentitySet
1616
import NullOpsDecorator.stripNull
1717

18+
import scala.annotation.binaryAPI
19+
1820
/** Methods for adding constraints and solving them.
1921
*
2022
* What goes into a Constraint as opposed to a ConstrainHandler?
@@ -39,12 +41,12 @@ trait ConstraintHandling {
3941
private var addConstraintInvocations = 0
4042

4143
/** If the constraint is frozen we cannot add new bounds to the constraint. */
42-
protected var frozenConstraint: Boolean = false
44+
@binaryAPI protected var frozenConstraint: Boolean = false
4345

4446
/** Potentially a type lambda that is still instantiatable, even though the constraint
4547
* is generally frozen.
4648
*/
47-
protected var caseLambda: Type = NoType
49+
@binaryAPI protected var caseLambda: Type = NoType
4850

4951
/** If set, align arguments `S1`, `S2`when taking the glb
5052
* `T1 { X = S1 } & T2 { X = S2 }` of a constraint upper bound for some type parameter.
@@ -56,7 +58,7 @@ trait ConstraintHandling {
5658
/** We are currently comparing type lambdas. Used as a flag for
5759
* optimization: when `false`, no need to do an expensive `pruneLambdaParams`
5860
*/
59-
protected var comparedTypeLambdas: Set[TypeLambda] = Set.empty
61+
@binaryAPI protected var comparedTypeLambdas: Set[TypeLambda] = Set.empty
6062

6163
/** Used for match type reduction: If false, we don't recognize an abstract type
6264
* to be a subtype type of any of its base classes. This is in place only at the
@@ -110,7 +112,7 @@ trait ConstraintHandling {
110112
* of `1`. So the lower bound is `1 | x.M` and when we level-avoid that we
111113
* get `1 | Int & String`, which simplifies to `Int`.
112114
*/
113-
private var myTrustBounds = true
115+
@binaryAPI private var myTrustBounds = true
114116

115117
inline def withUntrustedBounds(op: => Type): Type =
116118
val saved = myTrustBounds

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import classfile.ReusableDataReader
3030
import StdNames.nme
3131
import compiletime.uninitialized
3232

33+
import scala.annotation.binaryAPI
3334
import scala.annotation.internal.sharable
3435

3536
import DenotTransformers.DenotTransformer
@@ -805,6 +806,7 @@ object Contexts {
805806
* Note: plain TypeComparers always take on the kind of the outer comparer if they are in the same context.
806807
* In other words: tracking or explaining is a sticky property in the same context.
807808
*/
809+
@binaryAPI
808810
private def comparer(using Context): TypeComparer =
809811
util.Stats.record("comparing")
810812
val base = ctx.base
@@ -980,7 +982,7 @@ object Contexts {
980982
private[core] var phasesPlan: List[List[Phase]] = uninitialized
981983

982984
/** Phases by id */
983-
private[dotc] var phases: Array[Phase] = uninitialized
985+
@binaryAPI private[dotc] var phases: Array[Phase] = uninitialized
984986

985987
/** Phases with consecutive Transforms grouped into a single phase, Empty array if fusion is disabled */
986988
private[core] var fusedPhases: Array[Phase] = Array.empty[Phase]
@@ -1019,7 +1021,7 @@ object Contexts {
10191021
val generalContextPool = ContextPool()
10201022

10211023
private[Contexts] val comparers = new mutable.ArrayBuffer[TypeComparer]
1022-
private[Contexts] var comparersInUse: Int = 0
1024+
@binaryAPI private[Contexts] var comparersInUse: Int = 0
10231025

10241026
private var charArray = new Array[Char](256)
10251027

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import reporting.trace
2525
import annotation.constructorOnly
2626
import cc.{CapturingType, derivedCapturingType, CaptureSet, stripCapturing, isBoxedCapturing, boxed, boxedUnlessFun, boxedIfTypeParam, isAlwaysPure}
2727

28+
import scala.annotation.binaryAPI
29+
2830
/** Provides methods to compare types.
2931
*/
3032
class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling, PatternTypeConstrainer {
@@ -34,7 +36,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
3436
private var myContext: Context = initctx
3537
def comparerContext: Context = myContext
3638

37-
protected given [DummySoItsADef]: Context = myContext
39+
@binaryAPI protected given [DummySoItsADef]: Context = myContext
3840

3941
protected var state: TyperState = compiletime.uninitialized
4042
def constraint: Constraint = state.constraint
@@ -115,7 +117,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
115117

116118
private def isBottom(tp: Type) = tp.widen.isRef(NothingClass)
117119

118-
protected def gadtBounds(sym: Symbol)(using Context) = ctx.gadt.bounds(sym)
120+
@binaryAPI protected def gadtBounds(sym: Symbol)(using Context) = ctx.gadt.bounds(sym)
119121
protected def gadtAddBound(sym: Symbol, b: Type, isUpper: Boolean): Boolean = ctx.gadtState.addBound(sym, b, isUpper)
120122

121123
protected def typeVarInstance(tvar: TypeVar)(using Context): Type = tvar.underlying
@@ -156,7 +158,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
156158
private [this] var leftRoot: Type | Null = null
157159

158160
/** Are we forbidden from recording GADT constraints? */
159-
private var frozenGadt = false
161+
@binaryAPI private var frozenGadt = false
160162
private inline def inFrozenGadt[T](inline op: T): T =
161163
inFrozenGadtIf(true)(op)
162164

@@ -187,7 +189,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
187189
inFrozenGadtIf(true)(inFrozenConstraint(op))
188190

189191
extension (sym: Symbol)
190-
private inline def onGadtBounds(inline op: TypeBounds => Boolean): Boolean =
192+
@binaryAPI private inline def onGadtBounds(inline op: TypeBounds => Boolean): Boolean =
191193
val bounds = gadtBounds(sym)
192194
bounds != null && op(bounds)
193195

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import compiletime.uninitialized
3939
import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, EventuallyCapturingType, boxedUnlessFun}
4040
import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap}
4141

42+
import scala.annotation.binaryAPI
4243
import scala.annotation.internal.sharable
4344
import scala.annotation.threadUnsafe
4445

@@ -5542,7 +5543,7 @@ object Types {
55425543

55435544
/** Common base class of TypeMap and TypeAccumulator */
55445545
abstract class VariantTraversal:
5545-
protected[dotc] var variance: Int = 1
5546+
@binaryAPI protected[dotc] var variance: Int = 1
55465547

55475548
inline protected def atVariance[T](v: Int)(op: => T): T = {
55485549
val saved = variance

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

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import transform.{AccessProxies, Splicer}
2121
import staging.CrossStageSafety
2222
import transform.SymUtils.*
2323
import config.Printers.inlining
24+
import util.SrcPos
2425
import util.Property
2526
import staging.StagingLevel
2627

@@ -74,7 +75,7 @@ object PrepareInlineable {
7475
def needsAccessor(sym: Symbol)(using Context): Boolean =
7576
sym.isTerm &&
7677
(sym.isOneOf(AccessFlags) || sym.privateWithin.exists) &&
77-
(!sym.isBinaryAPI || (sym.is(Private) && !sym.owner.is(Trait))) &&
78+
(!sym.isBinaryAPI || sym.is(Private)) &&
7879
!(sym.isStableMember && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) &&
7980
!sym.isInlineMethod &&
8081
(Inlines.inInlineMethod || StagingLevel.level > 0)
@@ -100,6 +101,22 @@ object PrepareInlineable {
100101

101102
override def transform(tree: Tree)(using Context): Tree =
102103
postTransform(super.transform(preTransform(tree)))
104+
105+
protected def unstableAccessorWarning(accessor: Symbol, accessed: Symbol, srcPos: SrcPos)(using Context): Unit =
106+
val accessorDefTree = accessorDef(accessor, accessed)
107+
val solution =
108+
if accessed.is(Private) then
109+
s"Annotate ${accessed.name} with `@binaryAPI` to generate a stable accessor."
110+
else
111+
s"Annotate ${accessed.name} with `@binaryAPI` to make it accessible."
112+
val binaryCompat =
113+
s"""Adding @binaryAPI may break binary compatibility if a previous version of this
114+
|library was compiled with Scala 3.0-3.3, Binary compatibility should be checked
115+
|using MiMa. To keep binary you can add the following accessor to ${accessor.owner.showKind} ${accessor.owner.name.stripModuleClassSuffix}:
116+
| @binaryAPI private[${accessor.owner.name.stripModuleClassSuffix}] ${accessorDefTree.show}
117+
|
118+
|""".stripMargin
119+
report.warning(em"Generated unstable inline accessor for $accessed defined in ${accessed.owner}.\n\n$solution\n\n$binaryCompat", srcPos)
103120
}
104121

105122
/** Direct approach: place the accessor with the accessed symbol. This has the
@@ -114,7 +131,11 @@ object PrepareInlineable {
114131
report.error("Implementation restriction: cannot use private constructors in inline methods", tree.srcPos)
115132
tree // TODO: create a proper accessor for the private constructor
116133
}
117-
else useAccessor(tree)
134+
else
135+
val accessorTree = useAccessor(tree)
136+
if !tree.symbol.isBinaryAPI && tree.symbol != accessorTree.symbol then
137+
unstableAccessorWarning(accessorTree.symbol, tree.symbol, tree.srcPos)
138+
accessorTree
118139
case _ =>
119140
tree
120141
}
@@ -199,7 +220,9 @@ object PrepareInlineable {
199220
localRefs.map(TypeTree(_)) ++ leadingTypeArgs, // TODO: pass type parameters in two sections?
200221
(qual :: Nil) :: otherArgss
201222
)
202-
ref(accessor).appliedToArgss(argss1).withSpan(tree.span)
223+
val accessorTree = ref(accessor).appliedToArgss(argss1).withSpan(tree.span)
224+
225+
unstableAccessorWarning(accessorTree.symbol, tree.symbol, tree.srcPos)
203226

204227
// TODO: Handle references to non-public types.
205228
// This is quite tricky, as such types can appear anywhere, including as parts
@@ -211,6 +234,7 @@ object PrepareInlineable {
211234
// myAccessors += TypeDef(accessor).withPos(tree.pos.focus)
212235
// ref(accessor).withSpan(tree.span)
213236
//
237+
accessorTree
214238
case _: TypeDef if tree.symbol.is(Case) =>
215239
report.error(reporting.CaseClassInInlinedCode(tree), tree)
216240
tree
@@ -222,7 +246,7 @@ object PrepareInlineable {
222246
/** Create an inline accessor for this definition. */
223247
def makePrivateBinaryAPIAccessor(sym: Symbol)(using Context): Unit =
224248
assert(sym.is(Private))
225-
if !sym.owner.is(Trait) then
249+
if !sym.is(Accessor) then
226250
val ref = tpd.ref(sym).asInstanceOf[RefTree]
227251
val accessor = InsertPrivateBinaryAPIAccessors.useAccessor(ref)
228252
if sym.is(Mutable) then
@@ -243,7 +267,6 @@ object PrepareInlineable {
243267
// so no accessors are needed for them.
244268
tree
245269
else
246-
// TODO: warn if MakeInlineablePassing or MakeInlineableDirect generate accessors
247270
new MakeInlineablePassing(inlineSym).transform(
248271
new MakeInlineableDirect(inlineSym).transform(tree))
249272
}

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import config.Feature.{migrateTo3, fewerBracesEnabled}
2121
import config.SourceVersion.`3.0`
2222
import reporting.{NoProfile, Profile, Message}
2323

24+
import scala.annotation.binaryAPI
25+
2426
import java.util.Objects
2527

2628
object Scanners {
@@ -1597,7 +1599,7 @@ object Scanners {
15971599
protected def coversIndent(w: IndentWidth): Boolean =
15981600
knownWidth != null && w == indentWidth
15991601

1600-
private var myCommasExpected: Boolean = false
1602+
@binaryAPI private var myCommasExpected: Boolean = false
16011603

16021604
inline def withCommasExpected[T](inline op: => T): T =
16031605
val saved = myCommasExpected

compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import Decorators.{em, toMessage}
1919
import util.SourceFile
2020
import Utility._
2121

22+
import scala.annotation.binaryAPI
2223

2324
// XXX/Note: many/most of the functions in here are almost direct cut and pastes
2425
// from another file - scala.xml.parsing.MarkupParser, it looks like.
@@ -52,7 +53,7 @@ object MarkupParsers {
5253
override def getMessage: String = "input ended while parsing XML"
5354
}
5455

55-
class MarkupParser(parser: Parser, final val preserveWS: Boolean)(using Context) extends MarkupParserCommon {
56+
class MarkupParser(@binaryAPI parser: Parser, final val preserveWS: Boolean)(using @binaryAPI c: Context) extends MarkupParserCommon {
5657

5758
import Tokens.{ LBRACE, RBRACE }
5859

@@ -93,8 +94,8 @@ object MarkupParsers {
9394
var xEmbeddedBlock: Boolean = false
9495

9596
private var debugLastStartElement = List.empty[(Int, String)]
96-
private def debugLastPos = debugLastStartElement.head._1
97-
private def debugLastElem = debugLastStartElement.head._2
97+
@binaryAPI private def debugLastPos = debugLastStartElement.head._1
98+
@binaryAPI private def debugLastElem = debugLastStartElement.head._2
9899

99100
private def errorBraces() = {
100101
reportSyntaxError("in XML content, please use '}}' to express '}'")

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ import dotty.tools.dotc.util.SourcePosition
3131
import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef}
3232
import cc.{CaptureSet, toCaptureSet, IllegalCaptureRef}
3333

34+
import scala.annotation.binaryAPI
35+
3436
class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
3537

3638
/** A stack of enclosing DefDef, TypeDef, or ClassDef, or ModuleDefs nodes */
3739
private var enclosingDef: untpd.Tree = untpd.EmptyTree
38-
private var myCtx: Context = super.printerContext
40+
@binaryAPI private var myCtx: Context = super.printerContext
3941
private var printPos = ctx.settings.YprintPos.value
4042
private val printLines = ctx.settings.printLines.value
4143

compiler/src/dotty/tools/dotc/reporting/trace.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import core.*, Contexts.*, Decorators.*
88
import config.*
99
import printing.Formatting.*
1010

11+
import scala.annotation.binaryAPI
1112
import scala.compiletime.*
1213

1314
/** Exposes the {{{ trace("question") { op } }}} syntax.
@@ -76,9 +77,9 @@ trait TraceSyntax:
7677
inline def apply[T](inline question: String)(inline op: T)(using Context): T =
7778
apply[T](question, false)(op)
7879

79-
private val alwaysToString = (x: Any) => String.valueOf(x)
80+
@binaryAPI private val alwaysToString = (x: Any) => String.valueOf(x)
8081

81-
private def doTrace[T](question: => String,
82+
@binaryAPI private def doTrace[T](question: => String,
8283
printer: Printers.Printer = Printers.default,
8384
showOp: T => String)
8485
(op: => T)(using Context): T =

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

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,29 +32,33 @@ abstract class AccessProxies {
3232
/** The accessor definitions that need to be added to class `cls` */
3333
private def accessorDefs(cls: Symbol)(using Context): Iterator[DefDef] =
3434
for accessor <- cls.info.decls.iterator; accessed <- accessedBy.get(accessor) yield
35-
DefDef(accessor.asTerm, prefss => {
36-
def numTypeParams = accessed.info match {
37-
case info: PolyType => info.paramNames.length
38-
case _ => 0
39-
}
40-
val (targs, argss) = splitArgs(prefss)
41-
val (accessRef, forwardedTpts, forwardedArgss) =
42-
if (passReceiverAsArg(accessor.name))
43-
(argss.head.head.select(accessed), targs.takeRight(numTypeParams), argss.tail)
44-
else
45-
(if (accessed.isStatic) ref(accessed) else ref(TermRef(cls.thisType, accessed)),
46-
targs, argss)
47-
val rhs =
48-
if (accessor.name.isSetterName &&
49-
forwardedArgss.nonEmpty && forwardedArgss.head.nonEmpty) // defensive conditions
50-
accessRef.becomes(forwardedArgss.head.head)
51-
else
52-
accessRef
53-
.appliedToTypeTrees(forwardedTpts)
54-
.appliedToArgss(forwardedArgss)
55-
.etaExpandCFT(using ctx.withOwner(accessor))
56-
rhs.withSpan(accessed.span)
57-
})
35+
accessorDef(accessor, accessed)
36+
37+
/** The accessor definitions that need to be added to class `cls` */
38+
protected def accessorDef(accessor: Symbol, accessed: Symbol)(using Context): DefDef =
39+
DefDef(accessor.asTerm, prefss => {
40+
def numTypeParams = accessed.info match {
41+
case info: PolyType => info.paramNames.length
42+
case _ => 0
43+
}
44+
val (targs, argss) = splitArgs(prefss)
45+
val (accessRef, forwardedTpts, forwardedArgss) =
46+
if (passReceiverAsArg(accessor.name))
47+
(argss.head.head.select(accessed), targs.takeRight(numTypeParams), argss.tail)
48+
else
49+
(if (accessed.isStatic) ref(accessed) else ref(TermRef(accessor.owner.thisType, accessed)),
50+
targs, argss)
51+
val rhs =
52+
if (accessor.name.isSetterName &&
53+
forwardedArgss.nonEmpty && forwardedArgss.head.nonEmpty) // defensive conditions
54+
accessRef.becomes(forwardedArgss.head.head)
55+
else
56+
accessRef
57+
.appliedToTypeTrees(forwardedTpts)
58+
.appliedToArgss(forwardedArgss)
59+
.etaExpandCFT(using ctx.withOwner(accessor))
60+
rhs.withSpan(accessed.span)
61+
})
5862

5963
/** Add all needed accessors to the `body` of class `cls` */
6064
def addAccessorDefs(cls: Symbol, body: List[Tree])(using Context): List[Tree] = {

0 commit comments

Comments
 (0)