From addd6c8ad1e63db3962cbc80597a3a84a88bf17e Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 15 Aug 2023 14:28:29 +0200 Subject: [PATCH] Add `@publicInBinary` annotation A binary API is a definition that is annotated with `@publicInBinary` or overrides a definition annotated with `@publicInBinary`. This annotation can be placed on `def`, `val`, `lazy val`, `var`, `object`, and `given` definitions. A binary API will be publicly available in the bytecode. It cannot be used on `private`/`private[this]` definitions. This is useful in combination with inline definitions. If an inline definition refers to a private/protected definition marked as `@publicInBinary` it does not need to use an accessor. We still generate the accessors for binary compatibility but do not use them. If the linting option `-WunstableInlineAccessors` is enabled, then a warning will be emitted if an inline accessor is generated. The warning will guide the user to the use of `@publicInBinary`. For SIP: https://github.com/scala/improvement-proposals/pull/58 --- compiler/src/dotty/tools/dotc/Compiler.scala | 3 +- .../tools/dotc/config/ScalaSettings.scala | 1 + .../dotty/tools/dotc/core/Definitions.scala | 1 + .../tools/dotc/core/SymDenotations.scala | 7 + .../dotc/inlines/PrepareInlineable.scala | 16 +- .../tools/dotc/reporting/ErrorMessageID.scala | 5 +- .../dotty/tools/dotc/reporting/messages.scala | 42 ++- .../tools/dotc/transform/AccessProxies.scala | 13 +- .../tools/dotc/transform/PostTyper.scala | 5 +- .../transform/PublicInBinaryAnnotations.scala | 35 ++ .../src/dotty/tools/dotc/typer/Checking.scala | 5 + .../dotty/tools/dotc/typer/TypeAssigner.scala | 5 +- .../backend/jvm/PublicInBinaryTests.scala | 342 +++++++++++++++++ .../src/scala/annotation/publicInBinary.scala | 13 + project/MiMaFilters.scala | 2 +- tests/neg/inline-unstable-accessors.check | 345 ++++++++++++++++++ tests/neg/inline-unstable-accessors.scala | 68 ++++ tests/neg/publicInBinary-not-accessible.check | 31 ++ tests/neg/publicInBinary-not-accessible.scala | 22 ++ tests/neg/publicInBinary.check | 32 ++ tests/neg/publicInBinary.scala | 21 ++ tests/pos-macros/i15413/Macro_1.scala | 10 + tests/pos-macros/i15413/Test_2.scala | 2 + tests/pos-macros/i15413b/Macro_1.scala | 10 + tests/pos-macros/i15413b/Test_2.scala | 1 + tests/pos/publicInBinary.scala | 140 +++++++ tests/run/i13215.scala | 14 + 27 files changed, 1178 insertions(+), 13 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/PublicInBinaryAnnotations.scala create mode 100644 compiler/test/dotty/tools/backend/jvm/PublicInBinaryTests.scala create mode 100644 library/src/scala/annotation/publicInBinary.scala create mode 100644 tests/neg/inline-unstable-accessors.check create mode 100644 tests/neg/inline-unstable-accessors.scala create mode 100644 tests/neg/publicInBinary-not-accessible.check create mode 100644 tests/neg/publicInBinary-not-accessible.scala create mode 100644 tests/neg/publicInBinary.check create mode 100644 tests/neg/publicInBinary.scala create mode 100644 tests/pos-macros/i15413/Macro_1.scala create mode 100644 tests/pos-macros/i15413/Test_2.scala create mode 100644 tests/pos-macros/i15413b/Macro_1.scala create mode 100644 tests/pos-macros/i15413b/Test_2.scala create mode 100644 tests/pos/publicInBinary.scala create mode 100644 tests/run/i13215.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index a6118732d4ae..eac32ff839a9 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -72,7 +72,8 @@ class Compiler { new ElimRepeated, // Rewrite vararg parameters and arguments new RefChecks) :: // Various checks mostly related to abstract members and overriding List(new init.Checker) :: // Check initialization of objects - List(new ProtectedAccessors, // Add accessors for protected members + List(new PublicInBinary, // Makes @publicInBinary definitions public + new ProtectedAccessors, // Add accessors for protected members new ExtensionMethods, // Expand methods of value classes with extension methods new UncacheGivenAliases, // Avoid caching RHS of simple parameterless given aliases new ElimByName, // Map by-name parameters to functions diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 3dbfbfc6bab9..1c071c6bf10f 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -163,6 +163,7 @@ private sealed trait WarningSettings: val WvalueDiscard: Setting[Boolean] = BooleanSetting("-Wvalue-discard", "Warn when non-Unit expression results are unused.") val WNonUnitStatement = BooleanSetting("-Wnonunit-statement", "Warn when block statements are non-Unit expressions.") val WimplausiblePatterns = BooleanSetting("-Wimplausible-patterns", "Warn if comparison with a pattern value looks like it might always fail.") + val WunstableInlineAccessors = BooleanSetting("-WunstableInlineAccessors", "Warn an inline methods has references to non-stable binary APIs.") val Wunused: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting( name = "-Wunused", helpArg = "warning", diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index ea48dd2b56fa..5f23d6682f1f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1047,6 +1047,7 @@ class Definitions { @tu lazy val RequiresCapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.internal.requiresCapability") @tu lazy val RetainsAnnot: ClassSymbol = requiredClass("scala.annotation.retains") @tu lazy val RetainsByNameAnnot: ClassSymbol = requiredClass("scala.annotation.retainsByName") + @tu lazy val BinaryAPIAnnot: ClassSymbol = requiredClass("scala.annotation.publicInBinary") @tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable") diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 3dfa5225df5b..290f1df7330e 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1041,6 +1041,13 @@ object SymDenotations { isOneOf(EffectivelyErased) || is(Inline) && !isRetainedInline && !hasAnnotation(defn.ScalaStaticAnnot) + /** Is this a member that will become public in the generated binary */ + def isBinaryAPI(using Context): Boolean = + isTerm && ( + hasAnnotation(defn.BinaryAPIAnnot) || + allOverriddenSymbols.exists(sym => sym.hasAnnotation(defn.BinaryAPIAnnot)) + ) + /** ()T and => T types should be treated as equivalent for this symbol. * Note: For the moment, we treat Scala-2 compiled symbols as loose matching, * because the Scala library does not always follow the right conventions. diff --git a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala index 060c8d21f390..5f2e31f90550 100644 --- a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala @@ -23,6 +23,8 @@ import transform.SymUtils.* import config.Printers.inlining import util.Property import staging.StagingLevel +import dotty.tools.dotc.reporting.Message +import dotty.tools.dotc.util.SrcPos object PrepareInlineable { import tpd._ @@ -72,6 +74,7 @@ object PrepareInlineable { sym.isTerm && (sym.isOneOf(AccessFlags) || sym.privateWithin.exists) && !sym.isContainedIn(inlineSym) && + !sym.isBinaryAPI && !(sym.isStableMember && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) && !sym.isInlineMethod && (Inlines.inInlineMethod || StagingLevel.level > 0) @@ -87,6 +90,11 @@ object PrepareInlineable { override def transform(tree: Tree)(using Context): Tree = postTransform(super.transform(preTransform(tree))) + + protected def checkUnstableAccessor(accessedTree: Tree, accessor: Symbol)(using Context): Unit = + if ctx.settings.WunstableInlineAccessors.value then + val accessorTree = accessorDef(accessor, accessedTree.symbol) + report.warning(reporting.UnstableInlineAccessor(accessedTree.symbol, accessorTree), accessedTree) } /** Direct approach: place the accessor with the accessed symbol. This has the @@ -101,7 +109,11 @@ object PrepareInlineable { report.error("Implementation restriction: cannot use private constructors in inline methods", tree.srcPos) tree // TODO: create a proper accessor for the private constructor } - else useAccessor(tree) + else + val accessor = useAccessor(tree) + if tree != accessor then + checkUnstableAccessor(tree, accessor.symbol) + accessor case _ => tree } @@ -180,6 +192,8 @@ object PrepareInlineable { accessorInfo = abstractQualType(addQualType(dealiasMap(accessedType))), accessed = accessed) + checkUnstableAccessor(tree, accessor) + val (leadingTypeArgs, otherArgss) = splitArgs(argss) val argss1 = joinArgs( localRefs.map(TypeTree(_)) ++ leadingTypeArgs, // TODO: pass type parameters in two sections? diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 5cbe336acc28..938495117d41 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -199,9 +199,10 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case ClosureCannotHaveInternalParameterDependenciesID // errorNumber: 183 case MatchTypeNoCasesID // errorNumber: 184 case UnimportedAndImportedID // errorNumber: 185 - case ImplausiblePatternWarningID // erorNumber: 186 + case ImplausiblePatternWarningID // errorNumber: 186 case SynchronizedCallOnBoxedClassID // errorNumber: 187 - case VarArgsParamCannotBeGivenID // erorNumber: 188 + case VarArgsParamCannotBeGivenID // errorNumber: 188 + case UnstableInlineAccessorID // errorNumber: 189 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 5848d9e7cba1..cf0dc2acbe54 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -876,7 +876,7 @@ extends Message(PatternMatchExhaustivityID) { val pathes = List( ActionPatch( - srcPos = endPos, + srcPos = endPos, replacement = uncoveredCases.map(c => indent(s"case $c => ???", startColumn)) .mkString("\n", "\n", "") ), @@ -3038,3 +3038,43 @@ class ImplausiblePatternWarning(pat: tpd.Tree, selType: Type)(using Context) |$pat could match selector of type $selType |only if there is an `equals` method identifying elements of the two types.""" def explain(using Context) = "" + +class UnstableInlineAccessor(accessed: Symbol, accessorTree: tpd.Tree)(using Context) + extends Message(UnstableInlineAccessorID) { + def kind = MessageKind.Compatibility + + def msg(using Context) = + i"""Unstable inline accessor ${accessor.name} was generated in $where.""" + + def explain(using Context) = + i"""Access to non-public $accessed causes the automatic generation of an accessor. + |This accessor is not stable, its name may change or it may disappear + |if not needed in a future version. + | + |To make sure that the inlined code is binary compatible you must make sure that + |$accessed is public in the binary API. + | * Option 1: Annotate $accessed with @publicInBinary + | * Option 2: Make $accessed public + | + |This change may break binary compatibility if a previous version of this + |library was compiled with generated accessors. Binary compatibility should + |be checked using MiMa. If binary compatibility is broken, you should add the + |old accessor explicitly in the source code. The following code should be + |added to $where: + | @publicInBinary private[$within] ${accessorTree.showIndented(2)} + |""" + // FIXME accessorTree is shown with package classes, see tests/neg/inline-unstable-accessors.check: + // - @publicInBinary private[G] def inline$valBinaryAPI1: Int = foo.G.package.valBinaryAPI1 + // - @publicInBinary private[I] def inline$valBinaryAPI1: Int = foo.I.inline-unstable-accessors$package.valBinaryAPI1 + + private def accessor = accessorTree.symbol + + private def where = + if accessor.owner.name.isPackageObjectName then s"package ${within}" + else if accessor.owner.is(Module) then s"object $within" + else s"class $within" + + private def within = + if accessor.owner.name.isPackageObjectName then accessor.owner.owner.name.stripModuleClassSuffix + else accessor.owner.name.stripModuleClassSuffix +} diff --git a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala index 3175ffceae49..921ff0a926a2 100644 --- a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala +++ b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala @@ -32,7 +32,11 @@ abstract class AccessProxies { /** The accessor definitions that need to be added to class `cls` */ private def accessorDefs(cls: Symbol)(using Context): Iterator[DefDef] = for accessor <- cls.info.decls.iterator; accessed <- accessedBy.get(accessor) yield - DefDef(accessor.asTerm, prefss => { + accessorDef(accessor, accessed) + + protected def accessorDef(accessor: Symbol, accessed: Symbol)(using Context): DefDef = + DefDef(accessor.asTerm, + prefss => { def numTypeParams = accessed.info match { case info: PolyType => info.paramNames.length case _ => 0 @@ -42,7 +46,7 @@ abstract class AccessProxies { if (passReceiverAsArg(accessor.name)) (argss.head.head.select(accessed), targs.takeRight(numTypeParams), argss.tail) else - (if (accessed.isStatic) ref(accessed) else ref(TermRef(cls.thisType, accessed)), + (if (accessed.isStatic) ref(accessed) else ref(TermRef(accessor.owner.thisType, accessed)), targs, argss) val rhs = if (accessor.name.isSetterName && @@ -54,7 +58,8 @@ abstract class AccessProxies { .appliedToArgss(forwardedArgss) .etaExpandCFT(using ctx.withOwner(accessor)) rhs.withSpan(accessed.span) - }) + } + ) /** Add all needed accessors to the `body` of class `cls` */ def addAccessorDefs(cls: Symbol, body: List[Tree])(using Context): List[Tree] = { @@ -149,7 +154,7 @@ abstract class AccessProxies { def accessorIfNeeded(tree: Tree)(using Context): Tree = tree match { case tree: RefTree if needsAccessor(tree.symbol) => if (tree.symbol.isConstructor) { - report.error("Implementation restriction: cannot use private constructors in inlineable methods", tree.srcPos) + report.error("Cannot use private constructors in inline methods. You can use @publicInBinary to make constructor accessible in inline methods.", tree.srcPos) tree // TODO: create a proper accessor for the private constructor } else useAccessor(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 964486632979..e6554ea37167 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -169,10 +169,13 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => atPhase(thisPhase)(cls.annotationsCarrying(Set(defn.CompanionMethodMetaAnnot))) ++ sym.annotations) else + val publicInBinaryAnnotOpt = sym.getAnnotation(defn.BinaryAPIAnnot) if sym.is(Param) then sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) else if sym.is(ParamAccessor) then - sym.keepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot)) + // FIXME: copyAndKeepAnnotationsCarrying is dropping defn.BinaryAPIAnnot + sym.keepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot, defn.BinaryAPIAnnot, defn.BinaryAPIAnnot)) + for publicInBinaryAnnot <- publicInBinaryAnnotOpt do sym.addAnnotation(publicInBinaryAnnot) else sym.keepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) if sym.isScala2Macro && !ctx.settings.XignoreScala2Macros.value then diff --git a/compiler/src/dotty/tools/dotc/transform/PublicInBinaryAnnotations.scala b/compiler/src/dotty/tools/dotc/transform/PublicInBinaryAnnotations.scala new file mode 100644 index 000000000000..fa929fc40c0f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PublicInBinaryAnnotations.scala @@ -0,0 +1,35 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.DenotTransformers.SymTransformer +import dotty.tools.dotc.core.Flags.* +import dotty.tools.dotc.core.Symbols.NoSymbol +import dotty.tools.dotc.core.SymDenotations.SymDenotation +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.typer.RefChecks + +/** Makes @publicInBinary definitions public. + * + * This makes it possible to elide the generations of some unnecessary accessors. + */ +class PublicInBinary extends MiniPhase with SymTransformer: + + override def runsAfterGroupsOf: Set[String] = Set(RefChecks.name) + + override def phaseName: String = PublicInBinary.name + override def description: String = PublicInBinary.description + + def transformSym(d: SymDenotation)(using Context): SymDenotation = { + if d.isBinaryAPI then + d.resetFlag(Protected) + d.setPrivateWithin(NoSymbol) + if d.is(Module) then + val moduleClass = d.moduleClass + moduleClass.resetFlag(Protected) + moduleClass.setPrivateWithin(NoSymbol) + d + } + +object PublicInBinary: + val name: String = "publicInBinary" + val description: String = "makes @publicInBinary definitions public" diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index c17c5f25ab5f..6ded327109d7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -542,6 +542,11 @@ object Checking { fail(em"Inline methods cannot be @tailrec") if sym.hasAnnotation(defn.TargetNameAnnot) && sym.isClass && sym.isTopLevelClass then fail(TargetNameOnTopLevelClass(sym)) + if sym.hasAnnotation(defn.BinaryAPIAnnot) then + if sym.is(Enum) then fail(em"@publicInBinary cannot be used on enum definitions") + else if sym.isType && !sym.is(Module) && !(sym.is(Given) || sym.companionModule.is(Given)) then fail(em"@publicInBinary cannot be used on ${sym.showKind} definitions") + else if !sym.owner.isClass && !(sym.is(Param) && sym.owner.isConstructor) then fail(em"@publicInBinary cannot be used on local definitions") + else if sym.is(Private) && !sym.privateWithin.exists && !sym.isConstructor then fail(em"@publicInBinary cannot be used on private definitions\n\nCould the definition `private[${sym.owner.name}]` or `protected` instead") if (sym.hasAnnotation(defn.NativeAnnot)) { if (!sym.is(Deferred)) fail(NativeMembersMayNotHaveImplementation(sym)) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 2dc2f0951436..1533bac3982f 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -107,8 +107,9 @@ trait TypeAssigner { val tpe1 = accessibleType(tpe, superAccess) if tpe1.exists then tpe1 else tpe match - case tpe: NamedType => inaccessibleErrorType(tpe, superAccess, pos) - case NoType => tpe + case tpe: NamedType if !tpe.termSymbol.isBinaryAPI => + inaccessibleErrorType(tpe, superAccess, pos) + case _ => tpe /** Return a potentially skolemized version of `qualTpe` to be used * as a prefix when selecting `name`. diff --git a/compiler/test/dotty/tools/backend/jvm/PublicInBinaryTests.scala b/compiler/test/dotty/tools/backend/jvm/PublicInBinaryTests.scala new file mode 100644 index 000000000000..7c58aca30981 --- /dev/null +++ b/compiler/test/dotty/tools/backend/jvm/PublicInBinaryTests.scala @@ -0,0 +1,342 @@ +package dotty.tools.backend.jvm + +import scala.language.unsafeNulls + +import org.junit.Assert._ +import org.junit.Test + +import scala.tools.asm +import asm._ +import asm.tree._ + +import scala.tools.asm.Opcodes +import scala.jdk.CollectionConverters._ +import Opcodes._ + +class PublicInBinaryTests extends DottyBytecodeTest { + import ASMConverters._ + + private def privateOrProtectedOpcode = Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED + + private def checkPublicMethod(classNode: ClassNode, methodName: String, desc: String): Unit = + val method = getMethod(classNode, methodName) + assert(method.desc == desc) + assert((method.access & privateOrProtectedOpcode) == 0) + + private def checkPrivateMethod(classNode: ClassNode, methodName: String, desc: String): Unit = + val method = getMethod(classNode, methodName) + assert(method.desc == desc) + assert((method.access & Opcodes.ACC_PRIVATE) == Opcodes.ACC_PRIVATE) + + private def checkPublicField(classNode: ClassNode, fliedName: String): Unit = + val method = getField(classNode, fliedName) + assert((method.access & privateOrProtectedOpcode) == 0) + + private def checkPrivateField(classNode: ClassNode, fliedName: String): Unit = + val method = getField(classNode, fliedName) + assert((method.access & Opcodes.ACC_PRIVATE) == Opcodes.ACC_PRIVATE) + + @Test + def publicInBinaryDef(): Unit = { + val code = + """import scala.annotation.publicInBinary + |class C: + | @publicInBinary private[C] def packagePrivateBinaryAPI: Int = 1 + | @publicInBinary protected def protectedBinaryAPI: Int = 1 + | inline def inlined = packagePrivateBinaryAPI + protectedBinaryAPI + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPublicMethod(cClass, "packagePrivateBinaryAPI", "()I") + checkPublicMethod(cClass, "protectedBinaryAPI", "()I") + + // Check that the @publicInBinary annotated method is called + val testInlined = getMethod(cClass, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEVIRTUAL, "C", "packagePrivateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedBinaryAPI", "()I", false), + )) + } + } + + @Test + def publicInBinaryVal(): Unit = { + val code = + """import scala.annotation.publicInBinary + |class C: + | @publicInBinary private[C] val packagePrivateBinaryAPI: Int = 1 + | @publicInBinary protected val protectedBinaryAPI: Int = 1 + | @publicInBinary private[C] lazy val lazyPackagePrivateBinaryAPI: Int = 1 + | @publicInBinary protected lazy val lazyProtectedBinaryAPI: Int = 1 + | inline def inlined = packagePrivateBinaryAPI + protectedBinaryAPI + lazyPackagePrivateBinaryAPI + lazyProtectedBinaryAPI + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPublicMethod(cClass, "packagePrivateBinaryAPI", "()I") + checkPublicMethod(cClass, "protectedBinaryAPI", "()I") + + checkPublicMethod(cClass, "lazyPackagePrivateBinaryAPI", "()I") + checkPublicMethod(cClass, "lazyProtectedBinaryAPI", "()I") + + // Check that the @publicInBinaryinaryinaryinaryinary annotated method is called + val testInlined = getMethod(cClass, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEVIRTUAL, "C", "packagePrivateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "lazyPackagePrivateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "lazyProtectedBinaryAPI", "()I", false), + )) + } + } + + @Test + def publicInBinaryVar(): Unit = { + val code = + """import scala.annotation.publicInBinary + |class C: + | @publicInBinary private[C] var packagePrivateBinaryAPI: Int = 1 + | @publicInBinary protected var protectedBinaryAPI: Int = 1 + | inline def inlined = + | packagePrivateBinaryAPI = 1 + | protectedBinaryAPI = 1 + | packagePrivateBinaryAPI + protectedBinaryAPI + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPublicMethod(cClass, "packagePrivateBinaryAPI", "()I") + checkPublicMethod(cClass, "packagePrivateBinaryAPI_$eq", "(I)V") + checkPublicMethod(cClass, "protectedBinaryAPI", "()I") + checkPublicMethod(cClass, "protectedBinaryAPI_$eq", "(I)V") + + // Check that the @publicInBinaryinaryinary annotated method is called + val testInlined = getMethod(cClass, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEVIRTUAL, "C", "packagePrivateBinaryAPI_$eq", "(I)V", false), + Invoke(INVOKEVIRTUAL, "C", "protectedBinaryAPI_$eq", "(I)V", false), + Invoke(INVOKEVIRTUAL, "C", "packagePrivateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedBinaryAPI", "()I", false), + )) + } + } + + @Test + def publicInBinaryGiven(): Unit = { + val code = + """import scala.annotation.publicInBinary + |class C: + | @publicInBinary private[C] given packagePrivateBinaryAPI1: Int = 1 + | @publicInBinary protected given protectedBinaryAPI1: Int = 1 + | @publicInBinary private[C] given packagePrivateBinaryAPI2(using Int): Int = 1 + | @publicInBinary protected given protectedBinaryAPI2(using Int): Int = 1 + | inline def inlined = + | packagePrivateBinaryAPI1 + protectedBinaryAPI1 + packagePrivateBinaryAPI2(using 1) + protectedBinaryAPI2(using 1) + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + checkPublicMethod(cClass, "packagePrivateBinaryAPI1", "()I") + checkPublicMethod(cClass, "protectedBinaryAPI1", "()I") + + checkPublicMethod(cClass, "packagePrivateBinaryAPI2", "(I)I") + checkPublicMethod(cClass, "protectedBinaryAPI2", "(I)I") + + // Check that the @publicInBinary annotated method is called + val testInlined = getMethod(cClass, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEVIRTUAL, "C", "packagePrivateBinaryAPI1", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedBinaryAPI1", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "packagePrivateBinaryAPI2", "(I)I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedBinaryAPI2", "(I)I", false), + )) + } + } + + @Test + def publicInBinaryClassParam(): Unit = { + val code = + """import scala.annotation.publicInBinary + |class C( + | @publicInBinary private[C] val packagePrivateBinaryAPI: Int = 1, + | @publicInBinary protected val protectedBinaryAPI: Int = 1, + |) { + | inline def inlined = + | packagePrivateBinaryAPI + protectedBinaryAPI + | def testInlined = inlined + |} + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + checkPublicMethod(cClass, "packagePrivateBinaryAPI", "()I") + checkPublicMethod(cClass, "protectedBinaryAPI", "()I") + + // Check that the @publicInBinary annotated method is called + val testInlined = getMethod(cClass, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEVIRTUAL, "C", "packagePrivateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedBinaryAPI", "()I", false), + )) + } + } + + @Test + def publicInBinaryObject(): Unit = { + val code = + """package foo + |import scala.annotation.publicInBinary + |@publicInBinary private object PrivateBinaryAPI + |@publicInBinary private[foo] object PackagePrivateBinaryAPI + |@publicInBinary protected object ProtectedBinaryAPI + """.stripMargin + checkBCode(code) { dir => + val privateBinaryAPI = loadClassNode(dir.subdirectoryNamed("foo").lookupName("PrivateBinaryAPI$.class", directory = false).input, skipDebugInfo = false) + checkPublicField(privateBinaryAPI, "MODULE$") + + val packagePrivateBinaryAPI = loadClassNode(dir.subdirectoryNamed("foo").lookupName("PackagePrivateBinaryAPI$.class", directory = false).input, skipDebugInfo = false) + checkPublicField(packagePrivateBinaryAPI, "MODULE$") + + val protectedBinaryAPI = loadClassNode(dir.subdirectoryNamed("foo").lookupName("ProtectedBinaryAPI$.class", directory = false).input, skipDebugInfo = false) + checkPublicField(protectedBinaryAPI, "MODULE$") + } + } + + @Test + def publicInBinaryTraitDefs(): Unit = { + val code = + """import scala.annotation.publicInBinary + |trait C: + | @publicInBinary private[C] val packagePrivateValBinaryAPI: Int = 1 + | @publicInBinary protected val protectedValBinaryAPI: Int = 1 + | @publicInBinary private[C] lazy val packagePrivateLazyValBinaryAPI: Int = 1 + | @publicInBinary protected lazy val protectedLazyValBinaryAPI: Int = 1 + | @publicInBinary private[C] var packagePrivateVarBinaryAPI: Int = 1 + | @publicInBinary protected var protectedVarBinaryAPI: Int = 1 + | @publicInBinary private[C] def packagePrivateDefBinaryAPI: Int = 1 + | @publicInBinary protected def protectedDefBinaryAPI: Int = 1 + | inline def inlined = + | packagePrivateVarBinaryAPI = 1 + | protectedVarBinaryAPI = 1 + | packagePrivateValBinaryAPI + + | protectedValBinaryAPI + + | packagePrivateLazyValBinaryAPI + + | protectedLazyValBinaryAPI + + | packagePrivateVarBinaryAPI + + | protectedVarBinaryAPI + + | packagePrivateDefBinaryAPI + + | protectedDefBinaryAPI + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cTrait = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPublicMethod(cTrait, "packagePrivateValBinaryAPI", "()I") + checkPublicMethod(cTrait, "protectedValBinaryAPI", "()I") + checkPublicMethod(cTrait, "packagePrivateLazyValBinaryAPI", "()I") + checkPublicMethod(cTrait, "protectedLazyValBinaryAPI", "()I") + checkPublicMethod(cTrait, "packagePrivateVarBinaryAPI", "()I") + checkPublicMethod(cTrait, "packagePrivateVarBinaryAPI_$eq", "(I)V") + checkPublicMethod(cTrait, "protectedVarBinaryAPI", "()I") + checkPublicMethod(cTrait, "protectedVarBinaryAPI_$eq", "(I)V") + checkPublicMethod(cTrait, "packagePrivateDefBinaryAPI", "()I") + checkPublicMethod(cTrait, "protectedDefBinaryAPI", "()I") + + // Check that the @publicInBinary annotated method is called + val testInlined = getMethod(cTrait, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEINTERFACE, "C", "packagePrivateVarBinaryAPI_$eq", "(I)V", true), + Invoke(INVOKEINTERFACE, "C", "protectedVarBinaryAPI_$eq", "(I)V", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateValBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "protectedValBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateLazyValBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "protectedLazyValBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateVarBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "protectedVarBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateDefBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "protectedDefBinaryAPI", "()I", true) + )) + } + } + + @Test + def i13215(): Unit = { + val code = + """import scala.annotation.publicInBinary + |package foo: + | trait Bar: + | inline def baz = Baz + | def testInlined = baz + | @publicInBinary private[foo] object Baz + """.stripMargin + checkBCode(code) { dir => + val barClass = loadClassNode(dir.subdirectoryNamed("foo").lookupName("Bar.class", directory = false).input, skipDebugInfo = false) + checkPublicMethod(barClass, "testInlined", "()Lfoo/Baz$;") + } + } + + @Test + def i13215b(): Unit = { + val code = + """import scala.annotation.publicInBinary + |package foo: + | trait Bar: + | inline def baz = Baz + | def testInlined = baz + | @publicInBinary private object Baz + """.stripMargin + checkBCode(code) { dir => + val barClass = loadClassNode(dir.subdirectoryNamed("foo").lookupName("Bar.class", directory = false).input, skipDebugInfo = false) + checkPublicMethod(barClass, "testInlined", "()Lfoo/Baz$;") + } + } + + @Test + def i15413(): Unit = { + val code = + """import scala.quoted.* + |import scala.annotation.publicInBinary + |class Macro: + | inline def foo = Macro.fooImpl + | def test = foo + |object Macro: + | @publicInBinary private[Macro] def fooImpl = {} + """.stripMargin + checkBCode(code) { dir => + val macroClass = loadClassNode(dir.lookupName("Macro.class", directory = false).input, skipDebugInfo = false) + val testMethod = getMethod(macroClass, "test") + val testInstructions = instructionsFromMethod(testMethod).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInstructions, List( + Invoke(INVOKEVIRTUAL, "Macro$", "fooImpl", "()V", false))) + } + } + + @Test + def i15413b(): Unit = { + val code = + """package foo + |import scala.annotation.publicInBinary + |class C: + | inline def baz = D.bazImpl + | def test = baz + |object D: + | @publicInBinary private[foo] def bazImpl = {} + """.stripMargin + checkBCode(code) { dir => + val barClass = loadClassNode(dir.subdirectoryNamed("foo").lookupName("C.class", directory = false).input, skipDebugInfo = false) + val testMethod = getMethod(barClass, "test") + val testInstructions = instructionsFromMethod(testMethod).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInstructions, List( + Invoke(INVOKEVIRTUAL, "foo/D$", "bazImpl", "()V", false))) + } + } +} diff --git a/library/src/scala/annotation/publicInBinary.scala b/library/src/scala/annotation/publicInBinary.scala new file mode 100644 index 000000000000..4bf3728ea1e7 --- /dev/null +++ b/library/src/scala/annotation/publicInBinary.scala @@ -0,0 +1,13 @@ +package scala.annotation + +/** A binary API is a definition that is annotated with `@publicInBinary` or overrides a definition annotated with `@publicInBinary`. + * This annotation can be placed on `def`, `val`, `lazy val`, `var`, `object`, and `given` definitions. + * A binary API will be publicly available in the bytecode. + * + * This annotation cannot be used on `private`/`private[this]` definitions. + * + * This can be used to access private[T]/protected definitions within inline definitions. + * + * Removing this annotation from a non-public definition is a binary incompatible change. + */ +final class publicInBinary extends scala.annotation.StaticAnnotation diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 575bef17fce5..8f86f7ec8eda 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -6,7 +6,7 @@ object MiMaFilters { // New API in 3.4.X ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.ValOrDefDefTypeTest"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.ValOrDefDefMethods"), - // New API in 3.4.X + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.publicInBinary"), ) val TastyCore: Seq[ProblemFilter] = Seq( ProblemFilters.exclude[DirectMissingMethodProblem]("dotty.tools.tasty.TastyFormat.EXPLICITtpt"), diff --git a/tests/neg/inline-unstable-accessors.check b/tests/neg/inline-unstable-accessors.check new file mode 100644 index 000000000000..ba9ecabf5c8b --- /dev/null +++ b/tests/neg/inline-unstable-accessors.check @@ -0,0 +1,345 @@ +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:10:4 ------------------------------------------ +10 | valBinaryAPI1 + // error + | ^^^^^^^^^^^^^ + | Unstable inline accessor foo$A$$inline$valBinaryAPI1 was generated in class A. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI1 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI1 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI1 with @publicInBinary + | * Option 2: Make value valBinaryAPI1 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to class A: + | @publicInBinary private[A] final def foo$A$$inline$valBinaryAPI1: Int = this.valBinaryAPI1 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:11:4 ------------------------------------------ +11 | valBinaryAPI2 + // error + | ^^^^^^^^^^^^^ + | Unstable inline accessor foo$A$$inline$valBinaryAPI2 was generated in class A. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI2 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI2 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI2 with @publicInBinary + | * Option 2: Make value valBinaryAPI2 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to class A: + | @publicInBinary private[A] def foo$A$$inline$valBinaryAPI2: Int = this.valBinaryAPI2 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:15:6 ------------------------------------------ +15 | a.valBinaryAPI2 + // error + | ^^^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI2$i1 was generated in class B. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI2 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI2 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI2 with @publicInBinary + | * Option 2: Make value valBinaryAPI2 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to class B: + | @publicInBinary private[B] def inline$valBinaryAPI2$i1(x$0: foo.A): Int = x$0.valBinaryAPI2 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:23:4 ------------------------------------------ +23 | valBinaryAPI1 + // error + | ^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI1 was generated in class C. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI1 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI1 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI1 with @publicInBinary + | * Option 2: Make value valBinaryAPI1 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to class C: + | @publicInBinary private[C] final def inline$valBinaryAPI1: Int = this.valBinaryAPI1 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:24:4 ------------------------------------------ +24 | valBinaryAPI2 + // error + | ^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI2 was generated in class C. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI2 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI2 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI2 with @publicInBinary + | * Option 2: Make value valBinaryAPI2 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to class C: + | @publicInBinary private[C] def inline$valBinaryAPI2: Int = this.valBinaryAPI2 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:28:6 ------------------------------------------ +28 | c.valBinaryAPI2 + // error + | ^^^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI2$i2 was generated in class D. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI2 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI2 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI2 with @publicInBinary + | * Option 2: Make value valBinaryAPI2 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to class D: + | @publicInBinary private[D] def inline$valBinaryAPI2$i2(x$0: foo.C): Int = x$0.valBinaryAPI2 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:36:4 ------------------------------------------ +36 | valBinaryAPI1 + // error + | ^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI1 was generated in object E. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI1 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI1 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI1 with @publicInBinary + | * Option 2: Make value valBinaryAPI1 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to object E: + | @publicInBinary private[E] final def inline$valBinaryAPI1: Int = foo.E.valBinaryAPI1 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:37:4 ------------------------------------------ +37 | valBinaryAPI2 + // error + | ^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI2 was generated in object E. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI2 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI2 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI2 with @publicInBinary + | * Option 2: Make value valBinaryAPI2 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to object E: + | @publicInBinary private[E] def inline$valBinaryAPI2: Int = foo.E.valBinaryAPI2 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:41:6 ------------------------------------------ +41 | E.valBinaryAPI2 + // error + | ^^^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI2$i3 was generated in object F. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI2 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI2 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI2 with @publicInBinary + | * Option 2: Make value valBinaryAPI2 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to object F: + | @publicInBinary private[F] def inline$valBinaryAPI2$i3(x$0: object foo.E): Int = x$0.valBinaryAPI2 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:49:4 ------------------------------------------ +49 | valBinaryAPI1 + // error + | ^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI1 was generated in package G. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI1 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI1 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI1 with @publicInBinary + | * Option 2: Make value valBinaryAPI1 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to package G: + | @publicInBinary private[G] def inline$valBinaryAPI1: Int = foo.G.package.valBinaryAPI1 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:50:4 ------------------------------------------ +50 | valBinaryAPI2 + // error + | ^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI2 was generated in package G. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI2 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI2 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI2 with @publicInBinary + | * Option 2: Make value valBinaryAPI2 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to package G: + | @publicInBinary private[G] def inline$valBinaryAPI2: Int = foo.G.package.valBinaryAPI2 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:54:6 ------------------------------------------ +54 | G.valBinaryAPI2 + // error + | ^^^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI2$i4 was generated in package H. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI2 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI2 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI2 with @publicInBinary + | * Option 2: Make value valBinaryAPI2 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to package H: + | @publicInBinary private[H] def inline$valBinaryAPI2$i4(x$0: foo.G): Int = x$0.valBinaryAPI2 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:62:4 ------------------------------------------ +62 | valBinaryAPI1 + // error + | ^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI1 was generated in package I. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI1 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI1 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI1 with @publicInBinary + | * Option 2: Make value valBinaryAPI1 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to package I: + | @publicInBinary private[I] def inline$valBinaryAPI1: Int = foo.I.inline-unstable-accessors$package.valBinaryAPI1 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:63:4 ------------------------------------------ +63 | valBinaryAPI2 + // error + | ^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI2 was generated in package I. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI2 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI2 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI2 with @publicInBinary + | * Option 2: Make value valBinaryAPI2 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to package I: + | @publicInBinary private[I] def inline$valBinaryAPI2: Int = foo.I.inline-unstable-accessors$package.valBinaryAPI2 + -------------------------------------------------------------------------------------------------------------------- +-- [E189] Compatibility Error: tests/neg/inline-unstable-accessors.scala:67:6 ------------------------------------------ +67 | I.valBinaryAPI2 + // error + | ^^^^^^^^^^^^^^^ + | Unstable inline accessor inline$valBinaryAPI2$i5 was generated in package J. + |-------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Access to non-public value valBinaryAPI2 causes the automatic generation of an accessor. + | This accessor is not stable, its name may change or it may disappear + | if not needed in a future version. + | + | To make sure that the inlined code is binary compatible you must make sure that + | value valBinaryAPI2 is public in the binary API. + | * Option 1: Annotate value valBinaryAPI2 with @publicInBinary + | * Option 2: Make value valBinaryAPI2 public + | + | This change may break binary compatibility if a previous version of this + | library was compiled with generated accessors. Binary compatibility should + | be checked using MiMa. If binary compatibility is broken, you should add the + | old accessor explicitly in the source code. The following code should be + | added to package J: + | @publicInBinary private[J] def inline$valBinaryAPI2$i5(x$0: foo.I): Int = x$0.valBinaryAPI2 + -------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/inline-unstable-accessors.scala b/tests/neg/inline-unstable-accessors.scala new file mode 100644 index 000000000000..f548de731f35 --- /dev/null +++ b/tests/neg/inline-unstable-accessors.scala @@ -0,0 +1,68 @@ +// scalac: -Werror -WunstableInlineAccessors -explain + +package foo +import scala.annotation.publicInBinary +class A: + private val valBinaryAPI1: Int = 1 + private[foo] val valBinaryAPI2: Int = 1 + @publicInBinary private[foo] val valBinaryAPI3: Int = 1 + inline def inlined = + valBinaryAPI1 + // error + valBinaryAPI2 + // error + valBinaryAPI3 +class B(val a: A): + inline def inlined = + a.valBinaryAPI2 + // error + a.valBinaryAPI3 + +final class C: + private val valBinaryAPI1: Int = 1 + private[foo] val valBinaryAPI2: Int = 1 + @publicInBinary private[foo] val valBinaryAPI3: Int = 1 + inline def inlined = + valBinaryAPI1 + // error + valBinaryAPI2 + // error + valBinaryAPI3 +final class D(val c: C): + inline def inlined = + c.valBinaryAPI2 + // error + c.valBinaryAPI3 + +object E: + private val valBinaryAPI1: Int = 1 + private[foo] val valBinaryAPI2: Int = 1 + @publicInBinary private[foo] val valBinaryAPI3: Int = 1 + inline def inlined = + valBinaryAPI1 + // error + valBinaryAPI2 + // error + valBinaryAPI3 +object F: + inline def inlined = + E.valBinaryAPI2 + // error + E.valBinaryAPI3 + +package object G: + private val valBinaryAPI1: Int = 1 + private[foo] val valBinaryAPI2: Int = 1 + @publicInBinary private[foo] val valBinaryAPI3: Int = 1 + inline def inlined = + valBinaryAPI1 + // error + valBinaryAPI2 + // error + valBinaryAPI3 +package object H: + inline def inlined = + G.valBinaryAPI2 + // error + G.valBinaryAPI3 + +package I: + private val valBinaryAPI1: Int = 1 + private[foo] val valBinaryAPI2: Int = 1 + @publicInBinary private[foo] val valBinaryAPI3: Int = 1 + inline def inlined = + valBinaryAPI1 + // error + valBinaryAPI2 + // error + valBinaryAPI3 +package J: + inline def inlined = + I.valBinaryAPI2 + // error + I.valBinaryAPI3 diff --git a/tests/neg/publicInBinary-not-accessible.check b/tests/neg/publicInBinary-not-accessible.check new file mode 100644 index 000000000000..759867c130e0 --- /dev/null +++ b/tests/neg/publicInBinary-not-accessible.check @@ -0,0 +1,31 @@ +-- [E173] Reference Error: tests/neg/publicInBinary-not-accessible.scala:16:4 ------------------------------------------ +16 | a.p // error + | ^^^ + | value p cannot be accessed as a member of (a : foo.A) from module class publicInBinary-not-accessible$package$. +-- [E173] Reference Error: tests/neg/publicInBinary-not-accessible.scala:17:4 ------------------------------------------ +17 | a.a // error + | ^^^ + |value a cannot be accessed as a member of (a² : foo.A) from module class publicInBinary-not-accessible$package$. + | + |where: a is a value in class A + | a² is a parameter in method test +-- [E173] Reference Error: tests/neg/publicInBinary-not-accessible.scala:18:4 ------------------------------------------ +18 | a.b // error + | ^^^ + |lazy value b cannot be accessed as a member of (a : foo.A) from module class publicInBinary-not-accessible$package$. +-- [E173] Reference Error: tests/neg/publicInBinary-not-accessible.scala:19:4 ------------------------------------------ +19 | a.c // error + | ^^^ + |variable c cannot be accessed as a member of (a : foo.A) from module class publicInBinary-not-accessible$package$. +-- [E173] Reference Error: tests/neg/publicInBinary-not-accessible.scala:20:4 ------------------------------------------ +20 | a.d // error + | ^^^ + |method d cannot be accessed as a member of (a : foo.A) from module class publicInBinary-not-accessible$package$. +-- [E173] Reference Error: tests/neg/publicInBinary-not-accessible.scala:21:4 ------------------------------------------ +21 | a.e // error + | ^^^ + |given instance e cannot be accessed as a member of (a : foo.A) from module class publicInBinary-not-accessible$package$. +-- [E173] Reference Error: tests/neg/publicInBinary-not-accessible.scala:22:4 ------------------------------------------ +22 | a.f(using 1.0) // error + | ^^^ + |given instance f cannot be accessed as a member of (a : foo.A) from module class publicInBinary-not-accessible$package$. diff --git a/tests/neg/publicInBinary-not-accessible.scala b/tests/neg/publicInBinary-not-accessible.scala new file mode 100644 index 000000000000..138d71a33196 --- /dev/null +++ b/tests/neg/publicInBinary-not-accessible.scala @@ -0,0 +1,22 @@ +// scalac: -Werror -WunstableInlineAccessors + +package foo + +import scala.annotation.publicInBinary + +class A(@publicInBinary private[A] val p: Int): + @publicInBinary private[A] val a: Int = 1 + @publicInBinary private[A] lazy val b: Int = 1 + @publicInBinary private[A] var c: Int = 1 + @publicInBinary private[A] def d: Int = 1 + @publicInBinary private[A] given e: Int = 1 + @publicInBinary private[A] given f(using Double): Int = 1 + +def test(a: A) = + a.p // error + a.a // error + a.b // error + a.c // error + a.d // error + a.e // error + a.f(using 1.0) // error diff --git a/tests/neg/publicInBinary.check b/tests/neg/publicInBinary.check new file mode 100644 index 000000000000..8ce8acd5b50f --- /dev/null +++ b/tests/neg/publicInBinary.check @@ -0,0 +1,32 @@ +-- Error: tests/neg/publicInBinary.scala:8:22 -------------------------------------------------------------------------- +8 |@publicInBinary class C: // error + | ^ + | @publicInBinary cannot be used on class definitions +-- Error: tests/neg/publicInBinary.scala:10:24 ------------------------------------------------------------------------- +10 | @publicInBinary def g = () // error + | ^ + | @publicInBinary cannot be used on local definitions +-- Error: tests/neg/publicInBinary.scala:12:24 ------------------------------------------------------------------------- +12 |class D[@publicInBinary T] // error + | ^ + | @publicInBinary cannot be used on type definitions +-- Error: tests/neg/publicInBinary.scala:16:21 ------------------------------------------------------------------------- +16 |@publicInBinary enum Enum1: // error + | ^ + | @publicInBinary cannot be used on enum definitions +-- Error: tests/neg/publicInBinary.scala:20:23 ------------------------------------------------------------------------- +20 | @publicInBinary case A // error + | ^ + | @publicInBinary cannot be used on enum definitions +-- Error: tests/neg/publicInBinary.scala:21:23 ------------------------------------------------------------------------- +21 | @publicInBinary case B(a: Int) // error + | ^ + | @publicInBinary cannot be used on enum definitions +-- Error: tests/neg/publicInBinary.scala:7:21 -------------------------------------------------------------------------- +7 |@publicInBinary type A // error + | ^ + | @publicInBinary cannot be used on type definitions +-- Error: tests/neg/publicInBinary.scala:14:22 ------------------------------------------------------------------------- +14 |def f(@publicInBinary x: Int) = 3 // error + | ^ + | @publicInBinary cannot be used on local definitions diff --git a/tests/neg/publicInBinary.scala b/tests/neg/publicInBinary.scala new file mode 100644 index 000000000000..f05b725ed204 --- /dev/null +++ b/tests/neg/publicInBinary.scala @@ -0,0 +1,21 @@ +// scalac: -Werror -WunstableInlineAccessors + +package foo + +import scala.annotation.publicInBinary + +@publicInBinary type A // error +@publicInBinary class C: // error + def f: Unit = + @publicInBinary def g = () // error + () +class D[@publicInBinary T] // error + +def f(@publicInBinary x: Int) = 3 // error + +@publicInBinary enum Enum1: // error + case A + +enum Enum2: + @publicInBinary case A // error + @publicInBinary case B(a: Int) // error diff --git a/tests/pos-macros/i15413/Macro_1.scala b/tests/pos-macros/i15413/Macro_1.scala new file mode 100644 index 000000000000..57b70c5c3cc9 --- /dev/null +++ b/tests/pos-macros/i15413/Macro_1.scala @@ -0,0 +1,10 @@ +// scalac: -Werror -WunstableInlineAccessors + +import scala.quoted.* +import scala.annotation.publicInBinary + +class Macro: + inline def foo = ${ Macro.fooImpl } + +object Macro: + @publicInBinary private[Macro] def fooImpl(using Quotes) = '{} diff --git a/tests/pos-macros/i15413/Test_2.scala b/tests/pos-macros/i15413/Test_2.scala new file mode 100644 index 000000000000..a8310a8970fd --- /dev/null +++ b/tests/pos-macros/i15413/Test_2.scala @@ -0,0 +1,2 @@ +def test = + new Macro().foo diff --git a/tests/pos-macros/i15413b/Macro_1.scala b/tests/pos-macros/i15413b/Macro_1.scala new file mode 100644 index 000000000000..9818ecdb1869 --- /dev/null +++ b/tests/pos-macros/i15413b/Macro_1.scala @@ -0,0 +1,10 @@ +// scalac: -Werror -WunstableInlineAccessors + +package bar + +import scala.quoted.* +import scala.annotation.publicInBinary + +inline def foo = ${ fooImpl } + +@publicInBinary private[bar] def fooImpl(using Quotes) = '{} diff --git a/tests/pos-macros/i15413b/Test_2.scala b/tests/pos-macros/i15413b/Test_2.scala new file mode 100644 index 000000000000..5fc688c79b68 --- /dev/null +++ b/tests/pos-macros/i15413b/Test_2.scala @@ -0,0 +1 @@ +def test = bar.foo diff --git a/tests/pos/publicInBinary.scala b/tests/pos/publicInBinary.scala new file mode 100644 index 000000000000..a61f45b358a0 --- /dev/null +++ b/tests/pos/publicInBinary.scala @@ -0,0 +1,140 @@ +// scalac: -Werror -WunstableInlineAccessors + +package foo + +import scala.annotation.publicInBinary + +class Foo(@publicInBinary private[Foo] val paramVal: Int, @publicInBinary private[Foo] var paramVar: Int): + @publicInBinary + protected val protectedVal: Int = 2 + @publicInBinary + private[foo] val packagePrivateVal: Int = 2 + @publicInBinary + protected var protectedVar: Int = 2 + @publicInBinary + private[foo] var packagePrivateVar: Int = 2 + + inline def foo: Int = + paramVar = 3 + protectedVar = 3 + packagePrivateVar = 3 + paramVal + paramVar + protectedVal + packagePrivateVal + protectedVar + packagePrivateVar + +class Bar() extends Foo(3, 3): + override protected val protectedVal: Int = 2 + + override private[foo] val packagePrivateVal: Int = 2 + + inline def bar: Int = protectedVal + packagePrivateVal + +class Baz() extends Foo(4, 4): + @publicInBinary // TODO warn? Not needed because Foo.protectedVal is already @publicInBinary + override protected val protectedVal: Int = 2 + + @publicInBinary + override private[foo] val packagePrivateVal: Int = 2 + + inline def baz: Int = protectedVal + packagePrivateVal + + +class Qux() extends Foo(5, 5): + inline def qux: Int = protectedVal + packagePrivateVal + +def test = + Foo(3, 3).foo + Bar().bar + Baz().baz + Qux().qux + +@publicInBinary given Int = 1 +@publicInBinary given (using Double): Int = 1 + +trait A[T]: + def f: T +@publicInBinary given A[Int] with + def f: Int = 1 +@publicInBinary given (using Double): A[Int] with + def f: Int = 1 + +package inlines { + // Case that needed to be converted with MakeInlineablePassing + class C[T](x: T) { + @publicInBinary private[inlines] def next[U](y: U): (T, U) = (x, y) + } + class TestPassing { + inline def foo[A](x: A): (A, Int) = { + val c = new C[A](x) + c.next(1) + } + inline def bar[A](x: A): (A, String) = { + val c = new C[A](x) + c.next("") + } + } +} + +package foo { + @publicInBinary private object Foo: + @publicInBinary private[foo] def x: Int = 1 + inline def f: Int = Foo.x +} +def testFoo = foo.f + +def localTest = + class Foo: + @publicInBinary private[Foo] val a: Int = 1 + @publicInBinary protected val b: Int = 1 + +package traits { + trait Trait: + @publicInBinary private[Trait] val myVal = 1 + @publicInBinary private[Trait] lazy val myLazyVl = 2 + @publicInBinary private[Trait] var myVar = 2 + @publicInBinary private[Trait] def myDef = 3 + @publicInBinary private[Trait] given myGiven: Int = 4 + + @publicInBinary protected val myVal2 = 1 + @publicInBinary protected lazy val myLazyVl2 = 2 + @publicInBinary protected var myVar2 = 2 + @publicInBinary protected def myDef2 = 3 + @publicInBinary protected given myGiven2: Int = 4 + + inline def inlined: Unit = + myVar2 = 1 + myVar = 1 + myVal + myLazyVl + myVar + myDef + myGiven + + myVal2 + myLazyVl2 + myVar2 + myDef2 + myGiven2 + + def testTrait(t: Trait) = t.inlined + + class Baz extends Foo + object Baz extends Foo + + trait Foo: + inline def foo: Any = bar + @publicInBinary private[Foo] def bar: Any = ??? + end Foo + + def test = + Baz.foo + (new Baz).foo + val baz = new Baz + baz.foo +} + +package constructors { + class Foo @publicInBinary private[constructors] (x: Int): + @publicInBinary private[constructors] def this(x: Int, y: Int) = this(x + y) + + class Bar @publicInBinary(@publicInBinary private[Bar] x: Int): + @publicInBinary private def this(x: Int, y: Int) = this(x + y) + inline def bar: Bar = new Bar(x, x) + + inline def newFoo(x: Int) = new Foo(x) + inline def newFoo(x: Int, y: Int) = new Foo(x, y) +} + +def testConstructors = + val f = constructors.newFoo(1) + val g = constructors.newFoo(1, 2) + val h = new constructors.Bar(1).bar diff --git a/tests/run/i13215.scala b/tests/run/i13215.scala new file mode 100644 index 000000000000..5375efa87180 --- /dev/null +++ b/tests/run/i13215.scala @@ -0,0 +1,14 @@ +// scalac: -Werror -WunstableInlineAccessors + +import scala.annotation.publicInBinary + +package foo { + trait Bar: + inline def baz = Baz + + @publicInBinary private[foo] object Baz +} + +@main def Test: Unit = + val bar = new foo.Bar {} + bar.baz