Skip to content

Commit dd862f8

Browse files
committed
Update to a single annotation with linter warning
1 parent 8ea645a commit dd862f8

File tree

1 file changed

+100
-83
lines changed

1 file changed

+100
-83
lines changed

content/binary-api.md

Lines changed: 100 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ title: SIP-52 - Binary APIs
1313
| Date | Version |
1414
|---------------|--------------------|
1515
| Feb 27 2022 | Initial Draft |
16+
| Aug 16 2022 | Single Annotation |
1617

1718
## Summary
1819

19-
This proposal introduces the `@binaryAPI` and `@binaryAPIAccessor` annotations on term definitions. The purpose of binary APIs is to have publicly accessible definitions in generated bytecode for definitions that are private or protected.
20+
The purpose of binary APIs is to have publicly accessible definitions in generated bytecode for definitions that are package private or protected.
21+
This proposal introduces the `@binaryAPI` annotation on term definitions and the `-WunstableInlineAccessors` linting flag.
2022

2123

2224
## Motivation
@@ -66,7 +68,7 @@ object C:
6668

6769
### High-level overview
6870

69-
This proposal introduces 2 the `@binaryAPI` and `@binaryAPIAccessor` annotations, and adds a migration path to inline methods.
71+
This proposal introduces the `@binaryAPI` annotation, and adds a migration path to inline methods in libraries (requiring binary compatibility).
7072

7173
#### `@binaryAPI` annotation
7274

@@ -100,49 +102,18 @@ public class C {
100102
In the bytecode, `@binaryAPI` definitions will have the [ACC_PUBLIC](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1-200-E.1) flag.
101103
<!-- We can also set the [ACC_SYNTHETIC](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1-200-E.1) to hide these definitions from javac and java IDEs. -->
102104

103-
#### `@binaryAPIAccessor` annotation
104-
105-
A binary API with accessor is a definition that is annotated with `@binaryAPIAccessor`.
106-
This annotation can be placed on `def`, `val`, `lazy val`, `var`, `object`, and `given` definitions.
107-
The annotated definition will get a public accessor.
108-
109-
This can be used to access `private`/`private[this]` definitions within inline definitions.
110-
111-
Example:
112-
~~~ scala
113-
class C {
114-
@binaryAPIAccessor private def privateAPI: Int = ...
115-
@binaryAPIAccessor def publicAPI: Int = ...
116-
}
117-
~~~
118-
will generate the following bytecode signatures
119-
~~~ java
120-
public class C {
121-
public C();
122-
private int privateAPI();
123-
public int publicAPI();
124-
public final int C$$inline$privateAPI();
125-
public final int C$$inline$publicAPI();
126-
}
127-
~~~
128-
129-
Note that the change from `private[this]` to package private, protected or public is a binary compatible change.
130-
Removing this annotation is a binary incompatible change.
131-
132-
In the bytecode, `@binaryAPIAccessor` generated accessors will have the [ACC_PUBLIC | ACC_SYNTHETIC](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1-200-E.1) flag.
133-
134105
#### Binary API and inlining
135106

136107
A non-public reference in an inline method is handled as follows:
137108
- if the reference is a `@binaryAPI` the reference is used;
138-
- if the reference is a `@binaryAPIAccessor` the accessor is used;
139-
- otherwise, an accessor is automatically generated and used. We will emit a warning with an actionable diagnostic containing the code needed to migrate to `@binaryAPI` or `@binaryAPIAccessor`.
109+
- otherwise, an accessor is automatically generated and used.
140110

141-
Example 3:
111+
Example:
142112
~~~ scala
113+
import scala.annotation.binaryAPI
143114
class C {
144-
@binaryAPIAccessor private def a: Int = ...
145-
private def b: Int = ...
115+
@binaryAPI private[C] def a: Int = ...
116+
private[C] def b: Int = ...
146117
@binaryAPI protected def c: Int = ...
147118
protected def d: Int = ...
148119
inline def foo: Int = a + b + c + d
@@ -151,26 +122,100 @@ class C {
151122
before inlining the compiler will generate the accessors for inlined definitions
152123
~~~ scala
153124
class C {
154-
@binaryAPIAccessor private def a: Int = ...
155-
private def b: Int = ...
125+
@binaryAPI private[C] def a: Int = ...
126+
private[C] def b: Int = ...
156127
@binaryAPI protected def c: Int = ...
157128
protected def d: Int = ...
158-
final def C$$inline$a: Int = ... // generated by `@binaryAPIAccessor`
159-
final def C$$inline$b: Int = ... // warn: `b` should be annotated with `@binaryAPIAccessor` + migration code
160-
final def C$$inline$d: Int = ... // warn: `d` should be annotated with `@binaryAPI` + migration code
161-
inline def foo: Int = C$$inline$a + C$$inline$b + c + C$$inline$d
129+
final def C$$inline$b: Int = ...
130+
final def C$$inline$d: Int = ...
131+
inline def foo: Int = a + C$$inline$b + c + C$$inline$d
162132
}
163133
~~~
164134

135+
##### `-WunstableInlineAccessors`
136+
137+
In addition we introduce the `-WunstableInlineAccessors` flag to allow libraries to detect when the compiler generates unstable accessors.
138+
The previous code would show a linter warning that looks like this:
139+
140+
~~~
141+
-- [E...] Compatibility Warning: C.scala -----------------------------
142+
| inline def foo: Int = a + b + c + d
143+
| ^
144+
| Unstable inline accessor C$$inline$b was generated in class C.
145+
|
146+
| longer explanation available when compiling with `-explain`
147+
-- [E...] Compatibility Warning: C.scala -----------------------------
148+
| inline def foo: Int = a + b + c + d
149+
| ^
150+
| Unstable inline accessor C$$inline$d was generated in class C.
151+
|
152+
| longer explanation available when compiling with `-explain`
153+
~~~
154+
155+
When an accessor is detected we can tell the user how to fix the issue. For example we could use the `-explain` flag to add the following details to the message.
156+
157+
<details>
158+
<summary>With `-WunstableInlineAccessors -explain`</summary>
159+
160+
~~~
161+
-- [E...] Compatibility Warning: C.scala -----------------------------
162+
| inline def foo: Int = a + b + c + d
163+
| ^
164+
| Unstable inline accessor C$$inline$b was generated in class C.
165+
|-----------------------------------------------------------------------------
166+
| Explanation (enabled by `-explain`)
167+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
168+
| Access to non-public method b causes the automatic generation of an accessor.
169+
| This accessor is not stable, its name may change or it may disappear
170+
| if not needed in a future version.
171+
|
172+
| To make sure that the inlined code is binary compatible you must make sure that
173+
| method b is public in the binary API.
174+
| * Option 1: Annotate method b with @binaryAPI
175+
| * Option 2: Make method b public
176+
|
177+
| This change may break binary compatibility if a previous version of this
178+
| library was compiled with generated accessors. Binary compatibility should
179+
| be checked using MiMa. If binary compatibility is broken, you should add the
180+
| old accessor explicitly in the source code. The following code should be
181+
| added to class C:
182+
| @binaryAPI private[C] def C$$inline$b: Int = this.b
183+
-----------------------------------------------------------------------------
184+
-- [E...] Compatibility Warning: C.scala -----------------------------
185+
| inline def foo: Int = a + b + c + d
186+
| ^
187+
| Unstable inline accessor C$$inline$d was generated in class C.
188+
|-----------------------------------------------------------------------------
189+
| Explanation (enabled by `-explain`)
190+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
191+
| Access to non-public method d causes the automatic generation of an accessor.
192+
| This accessor is not stable, its name may change or it may disappear
193+
| if not needed in a future version.
194+
|
195+
| To make sure that the inlined code is binary compatible you must make sure that
196+
| method d is public in the binary API.
197+
| * Option 1: Annotate method d with @binaryAPI
198+
| * Option 2: Make method d public
199+
|
200+
| This change may break binary compatibility if a previous version of this
201+
| library was compiled with generated accessors. Binary compatibility should
202+
| be checked using MiMa. If binary compatibility is broken, you should add the
203+
| old accessor explicitly in the source code. The following code should be
204+
| added to class C:
205+
| @binaryAPI private[C] def C$$inline$d: Int = this.d
206+
-----------------------------------------------------------------------------
207+
~~~
208+
209+
</details>
210+
165211
### Specification
166212

167-
We must add `binaryAPI` and `binaryAPIAccessor` to the standard library.
213+
We must add `binaryAPI` to the standard library.
168214

169215
```scala
170216
package scala.annotation
171217

172218
final class binaryAPI extends scala.annotation.StaticAnnotation
173-
final class binaryAPIAccessor extends scala.annotation.StaticAnnotation
174219
```
175220

176221
#### `@binaryAPI` annotation
@@ -179,63 +224,35 @@ final class binaryAPIAccessor extends scala.annotation.StaticAnnotation
179224
* TASTy will contain references to non-public definitions that are out of scope but `@binaryAPI`. TASTy already allows those references.
180225
* The annotated definitions will be public in the generated bytecode. Definitions should be made public as early as possible in the compiler phases, as this can remove the need to create other accessors. It should be done after we check the accessibility of references.
181226

182-
183-
#### `@binaryAPIAccessor` annotation
184-
185-
This annotation is only valid on `def`, `val`, `lazy val`, `var`, `object`, and `given`.
186-
187-
A public accessor will be generated for the annotated definition. This accessor will be named `<fullClassName>$$inline$<definitionName>`. If the public accessor is in an object, the accessor will be named `inline$<definitionName>`. These names where chosen to minimize the complexity of migrating from automatically generates accessors in inline methods to `@binaryAPIAccessor` (see [Gist](https://gist.github.com/nicolasstucki/003f7293941836b08a0d53dbcb913e3c)).
188-
189227
#### Inline
190228

191229
* Inlining will not require the generation of an inline accessor for binary APIs.
192-
* Inlining will not require the generation of a new inline accessor, it will use the binary API accessors.
193-
* The user will be warned if a new inline accessor is automatically generated.
194-
The message will suggest `@binaryAPI` or `@binaryAPIAccessor` and how to fix potential incompatibilities.
195-
In a future version, these will become an error.
230+
* The user will be warned if a new inline accessor is automatically generated under `-WunstableInlineAccessors`.
231+
The message will suggest `@binaryAPI` and how to fix potential incompatibilities.
196232

197233
### Compatibility
198234

199-
The introduction of the `@binaryAPI` and `@binaryAPIAccessor` do not introduce any binary incompatibility.
200-
201-
Using references to `@binaryAPI` and `@binaryAPIAccessor` in inline code can cause binary incompatibilities. These incompatibilities are equivalent to the ones that can occur due to the unsoundness we want to fix. When migrating to binary APIs, the compiler will show the implementation of accessors that the users need to add to keep binary compatibility with pre-binaryAPI code.
235+
The introduction of the `@binaryAPI` do not introduce any binary incompatibility.
202236

203-
A definition can be both `@binaryAPI` and `@binaryAPIAccessor`. This would be used to indicate that the definition used to be private, but now we want to publish it as public. The definition would become public, and the accessor would be generated for binary compatibility.
237+
Using references to `@binaryAPI` in inline code can cause binary incompatibilities. These incompatibilities are equivalent to the ones that can occur due to the unsoundness we want to fix. When migrating to binary APIs, the compiler will show the implementation of accessors that the users need to add to keep binary compatibility with pre-binaryAPI code.
204238

205239
### Other concerns
206240

207241
* Tools that analyze inlined TASTy code might need to know about `@binaryAPI`. For example [MiMa](https://github.com/lightbend/mima/) and [TASTy MiMa](https://github.com/scalacenter/tasty-mima).
208242

209-
### Open questions
210-
211-
#### Question 1
212-
Should `@binaryAPIAccessor` accessors be named `<fullClassName>$$<definitionName>`? This encoding would match the names of `trait` accessor generated for private definition. We could use a single accessor instead of two. This would introduce an extra binary incompatibility with pre-binaryAPI code.
213-
214-
#### Question 2
215-
```scala
216-
class A:
217-
@binaryAPIAccessor protected def protectedDef: Int = ...
218-
class B extends A:
219-
override protected def protectedDef: Int = ...
220-
inline def inlinedDef: Int =
221-
// Should this use the accessor of generated for `A.protectedDef`? Or should we warn that `protectedDef` should be a `@binaryAPI`
222-
protectedDef
223-
```
224-
225243
## Alternatives
226244

227-
Having alternatives is not a strict requirement for a proposal, but having at least one with carefully exposed pros and cons gives much more weight to the proposal as a whole.
245+
### Add a `@binaryAPIAccessor`
246+
This annotation would generate an stable accessor. This annotation could be used on `private` definition. It would also mitigate [migration costs](https://gist.github.com/nicolasstucki/003f7293941836b08a0d53dbcb913e3c) for library authors that have published unstable accessors.
228247

229-
### Only add `@binaryAPI`
230-
This would simplify the system and the user interaction with this feature. The drawback is that we could not access `private[this]` definitions in inline code. Users would need to use `private[C]` instead, which could cause name clashes.
248+
* Implementation https://github.com/lampepfl/dotty/pull/16992
231249

232-
### Only add `@binaryAPIAccessor`
233-
This would simplify the system and the user interaction with this feature. The drawback is that we would add code size and runtime overhead to all uses of this feature. It would not solve the [Removing deprecated APIs](#removing-deprecated-apis) motivation.
234250

235251
## Related work
236252

237-
* Proof of concept: [https://github.com/lampepfl/dotty/pull/16992](https://github.com/lampepfl/dotty/pull/16992)
238253
* Initial discussions: [https://github.com/lampepfl/dotty/issues/16983](https://github.com/lampepfl/dotty/issues/16983)
254+
* Initial proof of concept (outdated): [https://github.com/lampepfl/dotty/pull/16992](https://github.com/lampepfl/dotty/pull/16992)
255+
* Single annotation proof of concept: [https://github.com/lampepfl/dotty/pull/18402](https://github.com/lampepfl/dotty/pull/18402)
239256
* Community migration analysis: [Gist](https://gist.github.com/nicolasstucki/003f7293941836b08a0d53dbcb913e3c)
240257

241258
<!-- ## FAQ -->

0 commit comments

Comments
 (0)