Skip to content

Commit 0537a3b

Browse files
committed
Add @binaryAPI
The `@binaryAPI` annotation will make any package private or protected term definition public in the generated bytecode. Definitions that override an `@binaryAPI` will also become public. We cannot annotate `private[this]` definitions because we might have different definitions with the same name and signature in a subclass. These would clash if made public. Instead of using private/private[this], the user can write `private[C]` where `C` is the enclosing class. This is useful in combination with inline definitions. If an inline definition refers to a `private`/`protected` definition marked as `@binaryAPI` it does not need to use an accessor. We still generate the accessors for binary compatibility but do not use them.
1 parent 120edd2 commit 0537a3b

File tree

22 files changed

+641
-16
lines changed

22 files changed

+641
-16
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class Compiler {
7171
new RefChecks) :: // Various checks mostly related to abstract members and overriding
7272
List(new init.Checker) :: // Check initialization of objects
7373
List(new CrossVersionChecks, // Check issues related to deprecated and experimental
74+
new BinaryAPIAnnotations, // Makes @binaryAPI definitions public
7475
new ProtectedAccessors, // Add accessors for protected members
7576
new ExtensionMethods, // Expand methods of value classes with extension methods
7677
new UncacheGivenAliases, // Avoid caching RHS of simple parameterless given aliases
@@ -89,7 +90,7 @@ class Compiler {
8990
new ExplicitOuter, // Add accessors to outer classes from nested ones.
9091
new ExplicitSelf, // Make references to non-trivial self types explicit as casts
9192
new StringInterpolatorOpt, // Optimizes raw and s and f string interpolators by rewriting them to string concatenations or formats
92-
new DropBreaks) :: // Optimize local Break throws by rewriting them
93+
new DropBreaks) :: // Optimize local Break throws by rewriting them
9394
List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions
9495
new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_`
9596
new InlinePatterns, // Remove placeholders of inlined patterns

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,7 @@ class Definitions {
10521052
@tu lazy val RequiresCapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.internal.requiresCapability")
10531053
@tu lazy val RetainsAnnot: ClassSymbol = requiredClass("scala.annotation.retains")
10541054
@tu lazy val RetainsByNameAnnot: ClassSymbol = requiredClass("scala.annotation.retainsByName")
1055+
@tu lazy val BinaryAPIAnnot: ClassSymbol = requiredClass("scala.annotation.binaryAPI")
10551056

10561057
@tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable")
10571058

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,13 @@ object SymDenotations {
10351035
isOneOf(EffectivelyErased)
10361036
|| is(Inline) && !isRetainedInline && !hasAnnotation(defn.ScalaStaticAnnot)
10371037

1038+
/** Is this a member that will become public in the generated binary */
1039+
def isBinaryAPI(using Context): Boolean =
1040+
isTerm && (
1041+
hasAnnotation(defn.BinaryAPIAnnot) ||
1042+
allOverriddenSymbols.exists(_.hasAnnotation(defn.BinaryAPIAnnot))
1043+
)
1044+
10381045
/** ()T and => T types should be treated as equivalent for this symbol.
10391046
* Note: For the moment, we treat Scala-2 compiled symbols as loose matching,
10401047
* because the Scala library does not always follow the right conventions.

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

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ object PrepareInlineable {
5353
/** A tree map which inserts accessors for non-public term members accessed from inlined code.
5454
*/
5555
abstract class MakeInlineableMap(val inlineSym: Symbol) extends TreeMap with Insert {
56+
57+
def useBinaryAPI: Boolean
58+
5659
def accessorNameOf(name: TermName, site: Symbol)(using Context): TermName =
5760
val accName = InlineAccessorName(name)
5861
if site.isExtensibleClass then accName.expandedName(site) else accName
@@ -70,6 +73,7 @@ object PrepareInlineable {
7073
def needsAccessor(sym: Symbol)(using Context): Boolean =
7174
sym.isTerm &&
7275
(sym.isOneOf(AccessFlags) || sym.privateWithin.exists) &&
76+
(!useBinaryAPI || !sym.isBinaryAPI) &&
7377
!sym.isContainedIn(inlineSym) &&
7478
!(sym.isStableMember && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) &&
7579
!sym.isInlineMethod &&
@@ -100,7 +104,7 @@ object PrepareInlineable {
100104
* possible if the receiver is essentially this or an outer this, which is indicated
101105
* by the test that we can find a host for the accessor.
102106
*/
103-
class MakeInlineableDirect(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) {
107+
class MakeInlineableDirect(inlineSym: Symbol, val useBinaryAPI: Boolean) extends MakeInlineableMap(inlineSym) {
104108
def preTransform(tree: Tree)(using Context): Tree = tree match {
105109
case tree: RefTree if needsAccessor(tree.symbol) =>
106110
if (tree.symbol.isConstructor) {
@@ -124,13 +128,14 @@ object PrepareInlineable {
124128
* private[inlines] def next[U](y: U): (T, U) = (x, y)
125129
* }
126130
* class TestPassing {
127-
* inline def foo[A](x: A): (A, Int) = {
128-
* val c = new C[A](x)
129-
* c.next(1)
130-
* }
131-
* inline def bar[A](x: A): (A, String) = {
132-
* val c = new C[A](x)
133-
* c.next("")
131+
* inline def foo[A](x: A): (A, Int) = {
132+
* val c = new C[A](x)
133+
* c.next(1)
134+
* }
135+
* inline def bar[A](x: A): (A, String) = {
136+
* val c = new C[A](x)
137+
* c.next("")
138+
* }
134139
* }
135140
*
136141
* `C` could be compiled separately, so we cannot place the inline accessor in it.
@@ -143,7 +148,7 @@ object PrepareInlineable {
143148
* Since different calls might have different receiver types, we need to generate one
144149
* such accessor per call, so they need to have unique names.
145150
*/
146-
class MakeInlineablePassing(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) {
151+
class MakeInlineablePassing(inlineSym: Symbol, val useBinaryAPI: Boolean) extends MakeInlineableMap(inlineSym) {
147152

148153
def preTransform(tree: Tree)(using Context): Tree = tree match {
149154
case _: Apply | _: TypeApply | _: RefTree
@@ -153,7 +158,7 @@ object PrepareInlineable {
153158
val qual = qualifier(refPart)
154159
inlining.println(i"adding receiver passing inline accessor for $tree/$refPart -> (${qual.tpe}, $refPart: ${refPart.getClass}, $argss%, %")
155160

156-
// Need to dealias in order to cagtch all possible references to abstracted over types in
161+
// Need to dealias in order to catch all possible references to abstracted over types in
157162
// substitutions
158163
val dealiasMap = new TypeMap {
159164
def apply(t: Type) = mapOver(t.dealias)
@@ -226,8 +231,13 @@ object PrepareInlineable {
226231
// so no accessors are needed for them.
227232
tree
228233
else
229-
new MakeInlineablePassing(inlineSym).transform(
230-
new MakeInlineableDirect(inlineSym).transform(tree))
234+
// Make sure the old accessors are generated for binary compatibility
235+
new MakeInlineablePassing(inlineSym, useBinaryAPI = false).transform(
236+
new MakeInlineableDirect(inlineSym, useBinaryAPI = false).transform(tree))
237+
238+
// TODO: warn if MakeInlineablePassing or MakeInlineableDirect generate accessors when useBinaryAPI in enabled
239+
new MakeInlineablePassing(inlineSym, useBinaryAPI = true).transform(
240+
new MakeInlineableDirect(inlineSym, useBinaryAPI = true).transform(tree))
231241
}
232242
}
233243

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package dotty.tools.dotc.transform
2+
3+
import dotty.tools.dotc.core.Contexts.*
4+
import dotty.tools.dotc.core.DenotTransformers.SymTransformer
5+
import dotty.tools.dotc.core.Flags.*
6+
import dotty.tools.dotc.core.Symbols.NoSymbol
7+
import dotty.tools.dotc.core.SymDenotations.SymDenotation
8+
import dotty.tools.dotc.transform.MegaPhase.MiniPhase
9+
import dotty.tools.dotc.typer.RefChecks
10+
11+
/** Makes @binaryAPI definitions public */
12+
class BinaryAPIAnnotations extends MiniPhase with SymTransformer:
13+
14+
override def runsAfterGroupsOf: Set[String] = Set(RefChecks.name)
15+
16+
override def phaseName: String = BinaryAPIAnnotations.name
17+
override def description: String = BinaryAPIAnnotations.description
18+
19+
def transformSym(d: SymDenotation)(using Context): SymDenotation = {
20+
if d.isBinaryAPI then
21+
d.resetFlag(Protected)
22+
d.setPrivateWithin(NoSymbol)
23+
if d.is(Module) then
24+
val moduleClass = d.moduleClass
25+
moduleClass.resetFlag(Protected)
26+
moduleClass.setPrivateWithin(NoSymbol)
27+
d
28+
}
29+
30+
object BinaryAPIAnnotations:
31+
val name: String = "binaryAPIAnnotations"
32+
val description: String = "makes @binaryAPI definitions public"

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,13 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
159159
if sym.isSetter then
160160
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.SetterMetaAnnot))
161161
else
162+
val binaryAPIAnnotOpt = sym.getAnnotation(defn.BinaryAPIAnnot)
162163
if sym.is(Param) then
163164
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots)
164165
else if sym.is(ParamAccessor) then
165-
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot))
166+
// FIXME: copyAndKeepAnnotationsCarrying is dropping defn.BinaryAPIAnnot
167+
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot, defn.BinaryAPIAnnot))
168+
for binaryAPIAnnot <- binaryAPIAnnotOpt do sym.addAnnotation(binaryAPIAnnot)
166169
else
167170
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots)
168171
if sym.isScala2Macro && !ctx.settings.XignoreScala2Macros.value then

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,11 @@ object Checking {
526526
fail(em"Inline methods cannot be @tailrec")
527527
if sym.hasAnnotation(defn.TargetNameAnnot) && sym.isClass && sym.isTopLevelClass then
528528
fail(TargetNameOnTopLevelClass(sym))
529+
if sym.hasAnnotation(defn.BinaryAPIAnnot) then
530+
if sym.is(Enum) then fail(em"@binaryAPI cannot be used on enum definitions.")
531+
else if sym.isType && !sym.is(Module) && !(sym.is(Given) || sym.companionModule.is(Given)) then fail(em"@binaryAPI cannot be used on ${sym.showKind} definitions")
532+
else if !sym.owner.isClass && !(sym.is(Param) && sym.owner.isConstructor) then fail(em"@binaryAPI cannot be used on local definitions.")
533+
else if sym.is(Private) then fail(em"@binaryAPI cannot be used on private definitions.\n\nCould use private[${sym.owner.name}] or protected instead.")
529534
if (sym.hasAnnotation(defn.NativeAnnot)) {
530535
if (!sym.is(Deferred))
531536
fail(NativeMembersMayNotHaveImplementation(sym))

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ trait TypeAssigner {
9999
val tpe1 = accessibleType(tpe, superAccess)
100100
if tpe1.exists then tpe1
101101
else tpe match
102-
case tpe: NamedType => inaccessibleErrorType(tpe, superAccess, pos)
102+
case tpe: NamedType =>
103+
if tpe.termSymbol.isBinaryAPI then tpe
104+
else inaccessibleErrorType(tpe, superAccess, pos)
103105
case NoType => tpe
104106

105107
/** Return a potentially skolemized version of `qualTpe` to be used

0 commit comments

Comments
 (0)