From 583d5ae655f0c807feb2ac1bd90f6b46e6c6286d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 15 Aug 2023 14:28:29 +0200 Subject: [PATCH] Add `@publicInBinary` annotation and `-WunstableInlineAccessors` Introduces [SIP-52](https://github.com/scala/improvement-proposals/pull/58) as experimental feature. 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`. --- .../tools/dotc/config/ScalaSettings.scala | 1 + .../dotty/tools/dotc/core/Definitions.scala | 3 + .../tools/dotc/core/SymDenotations.scala | 7 + .../dotc/inlines/PrepareInlineable.scala | 16 +- .../tools/dotc/printing/RefinedPrinter.scala | 15 +- .../tools/dotc/reporting/ErrorMessageID.scala | 1 + .../dotty/tools/dotc/reporting/messages.scala | 37 ++ .../tools/dotc/transform/AccessProxies.scala | 13 +- .../tools/dotc/transform/PostTyper.scala | 3 + .../dotc/transform/ProtectedAccessors.scala | 2 +- .../src/dotty/tools/dotc/typer/Checking.scala | 6 + .../dotty/tools/dotc/typer/TypeAssigner.scala | 6 +- compiler/test-resources/repl/i15493 | 2 +- .../backend/jvm/PublicInBinaryTests.scala | 388 ++++++++++++++++++ .../src/scala/annotation/publicInBinary.scala | 20 + tests/neg/inline-unstable-accessors.check | 345 ++++++++++++++++ tests/neg/inline-unstable-accessors.scala | 68 +++ tests/neg/publicInBinary-not-accessible.check | 38 ++ tests/neg/publicInBinary-not-accessible.scala | 22 + tests/neg/publicInBinary.check | 40 ++ tests/neg/publicInBinary.scala | 28 ++ 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-special/fatal-warnings/i17735.scala | 2 +- .../pos-special/fatal-warnings/i17735a.scala | 2 +- tests/pos-special/fatal-warnings/i17741.scala | 2 +- .../stdlibExperimentalDefinitions.scala | 3 + tests/run/i13215.scala | 14 + tests/run/noProtectedSuper.scala | 38 ++ tests/run/publicInBinary/Lib_1.scala | 135 ++++++ tests/run/publicInBinary/Test_2.scala | 26 ++ 33 files changed, 1289 insertions(+), 17 deletions(-) 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/run/i13215.scala create mode 100644 tests/run/noProtectedSuper.scala create mode 100644 tests/run/publicInBinary/Lib_1.scala create mode 100644 tests/run/publicInBinary/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index a71f28f49410..d1b91f77f933 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -166,6 +166,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 d7ffff758ff9..e08672c693b9 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1059,6 +1059,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 PublicInBinaryAnnot: ClassSymbol = requiredClass("scala.annotation.publicInBinary") @tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable") @@ -1070,6 +1071,8 @@ class Definitions { // A list of meta-annotations that are relevant for fields and accessors @tu lazy val NonBeanMetaAnnots: Set[Symbol] = Set(FieldMetaAnnot, GetterMetaAnnot, ParamMetaAnnot, SetterMetaAnnot, CompanionClassMetaAnnot, CompanionMethodMetaAnnot) + @tu lazy val NonBeanParamAccessorAnnots: Set[Symbol] = + Set(PublicInBinaryAnnot) @tu lazy val MetaAnnots: Set[Symbol] = NonBeanMetaAnnots + BeanGetterMetaAnnot + BeanSetterMetaAnnot diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 83362897e5e3..902bbda8bead 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1031,6 +1031,13 @@ object SymDenotations { isOneOf(EffectivelyErased) || is(Inline) && !isRetainedInline && !hasAnnotation(defn.ScalaStaticAnnot) + /** Is this a member that will become public in the generated binary */ + def hasPublicInBinary(using Context): Boolean = + isTerm && ( + hasAnnotation(defn.PublicInBinaryAnnot) || + allOverriddenSymbols.exists(sym => sym.hasAnnotation(defn.PublicInBinaryAnnot)) + ) + /** ()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 0b5c72bd997f..1acc6a1c8317 100644 --- a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala @@ -22,6 +22,8 @@ import staging.CrossStageSafety 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.* @@ -71,6 +73,7 @@ object PrepareInlineable { sym.isTerm && (sym.isOneOf(AccessFlags) || sym.privateWithin.exists) && !sym.isContainedIn(inlineSym) && + !sym.hasPublicInBinary && !(sym.isStableMember && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) && !sym.isInlineMethod && (Inlines.inInlineMethod || StagingLevel.level > 0) @@ -86,6 +89,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 @@ -100,7 +108,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 } @@ -179,6 +191,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/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 44ccf5c3c9fe..a556a87173f5 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -122,14 +122,19 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else if homogenizedView then isEmptyPrefix(sym) // drop and anonymous classes, but not scala, Predef. else if sym.isPackageObject then isOmittablePrefix(sym.owner) else isOmittablePrefix(sym) + def isSkippedPackageObject(sym: Symbol) = + sym.isPackageObject && !homogenizedView && !printDebug tp.prefix match { - case thisType: ThisType if isOmittable(thisType.cls) => - "" - case termRef @ TermRef(pre, _) => + case thisType: ThisType => + val sym = thisType.cls + if isSkippedPackageObject(sym) then toTextPrefixOf(sym.typeRef) + else if isOmittable(sym) then "" + else super.toTextPrefixOf(tp) + case termRef: TermRef => val sym = termRef.symbol - if sym.isPackageObject && !homogenizedView && !printDebug then toTextPrefixOf(termRef) - else if (isOmittable(sym)) "" + if isSkippedPackageObject(sym) then toTextPrefixOf(termRef) + else if isOmittable(sym) then "" else super.toTextPrefixOf(tp) case _ => super.toTextPrefixOf(tp) } diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 89fd071137cf..f5e7f9d44f56 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -205,6 +205,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case ExtractorNotFoundID // errorNumber: 189 case PureUnitExpressionID // errorNumber: 190 case MatchTypeLegacyPatternID // errorNumber: 191 + case UnstableInlineAccessorID // errorNumber: 192 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 033376a9ab09..4c7cf003cbc9 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3091,3 +3091,40 @@ 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.show} + |""" + + 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 237db90b315f..6d445887e1d9 100644 --- a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala +++ b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala @@ -31,7 +31,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 @@ -41,7 +45,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 && @@ -53,7 +57,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] = { @@ -148,7 +153,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 3b6c3f9bff96..3915a8a8dfd7 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -172,7 +172,10 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => if sym.is(Param) then sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) else if sym.is(ParamAccessor) then + // @publicInBinary is not a meta-annotation and therefore not kept by `keepAnnotationsCarrying` + val publicInBinaryAnnotOpt = sym.getAnnotation(defn.PublicInBinaryAnnot) sym.keepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot)) + 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/ProtectedAccessors.scala b/compiler/src/dotty/tools/dotc/transform/ProtectedAccessors.scala index b6df581beee2..482da0edb82b 100644 --- a/compiler/src/dotty/tools/dotc/transform/ProtectedAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/ProtectedAccessors.scala @@ -37,7 +37,7 @@ object ProtectedAccessors { * is not in a subclass or subtrait of `sym`? */ def needsAccessorIfNotInSubclass(sym: Symbol)(using Context): Boolean = - sym.isTerm && sym.is(Protected) && + sym.isTerm && sym.is(Protected) && !sym.hasPublicInBinary && !sym.owner.is(Trait) && // trait methods need to be handled specially, are currently always public !insideBoundaryOf(sym) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index f4605adea0e7..81375fe73549 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -538,6 +538,12 @@ 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.PublicInBinaryAnnot) 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(ParamAccessor) && sym.is(Private) then fail(em"@publicInBinary cannot be non `val` constructor parameters") + else if sym.is(Private) && !sym.privateWithin.exists && !sym.isConstructor then fail(em"@publicInBinary cannot be used on private definitions\n\nConsider using `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 34ac0875696a..8bae3a2fb3a7 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -107,8 +107,10 @@ 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.hasPublicInBinary && tpd.enclosingInlineds.nonEmpty then tpe + else 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-resources/repl/i15493 b/compiler/test-resources/repl/i15493 index 670cf8ebcbd2..063f7edfaca4 100644 --- a/compiler/test-resources/repl/i15493 +++ b/compiler/test-resources/repl/i15493 @@ -146,4 +146,4 @@ scala> Vector.unapplySeq(Vector(2)) val res35: scala.collection.SeqFactory.UnapplySeqWrapper[Int] = scala.collection.SeqFactory$UnapplySeqWrapper@df507bfd scala> new scala.concurrent.duration.DurationInt(5) -val res36: scala.concurrent.duration.package.DurationInt = scala.concurrent.duration.package$DurationInt@5 +val res36: scala.concurrent.duration.DurationInt = scala.concurrent.duration.package$DurationInt@5 \ No newline at end of file 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..25b46532e58b --- /dev/null +++ b/compiler/test/dotty/tools/backend/jvm/PublicInBinaryTests.scala @@ -0,0 +1,388 @@ +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) + + private def checkPublicClass(classNode: ClassNode): Unit = + assert((classNode.access & privateOrProtectedOpcode) == 0) + + @Test + def publicInBinaryDef(): Unit = { + val code = + """import scala.annotation.publicInBinary + |class C: + | @publicInBinary private[C] def packagePrivateMethod: Int = 1 + | @publicInBinary protected def protectedMethod: Int = 1 + | inline def inlined = packagePrivateMethod + protectedMethod + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPublicMethod(cClass, "packagePrivateMethod", "()I") + checkPublicMethod(cClass, "protectedMethod", "()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", "packagePrivateMethod", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedMethod", "()I", false), + )) + } + } + + @Test + def publicInBinaryVal(): Unit = { + val code = + """import scala.annotation.publicInBinary + |class C: + | @publicInBinary private[C] val packagePrivateVal: Int = 1 + | @publicInBinary protected val protectedVal: Int = 1 + | @publicInBinary private[C] lazy val lazyPackagePrivateVal: Int = 1 + | @publicInBinary protected lazy val lazyProtectedVal: Int = 1 + | inline def inlined = packagePrivateVal + protectedVal + lazyPackagePrivateVal + lazyProtectedVal + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPublicMethod(cClass, "packagePrivateVal", "()I") + checkPublicMethod(cClass, "protectedVal", "()I") + + checkPublicMethod(cClass, "lazyPackagePrivateVal", "()I") + checkPublicMethod(cClass, "lazyProtectedVal", "()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", "packagePrivateVal", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedVal", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "lazyPackagePrivateVal", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "lazyProtectedVal", "()I", false), + )) + } + } + + @Test + def publicInBinaryVar(): Unit = { + val code = + """import scala.annotation.publicInBinary + |class C: + | @publicInBinary private[C] var packagePrivateVar: Int = 1 + | @publicInBinary protected var protectedVar: Int = 1 + | inline def inlined = + | packagePrivateVar = 1 + | protectedVar = 1 + | packagePrivateVar + protectedVar + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPublicMethod(cClass, "packagePrivateVar", "()I") + checkPublicMethod(cClass, "packagePrivateVar_$eq", "(I)V") + checkPublicMethod(cClass, "protectedVar", "()I") + checkPublicMethod(cClass, "protectedVar_$eq", "(I)V") + + // 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", "packagePrivateVar_$eq", "(I)V", false), + Invoke(INVOKEVIRTUAL, "C", "protectedVar_$eq", "(I)V", false), + Invoke(INVOKEVIRTUAL, "C", "packagePrivateVar", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedVar", "()I", false), + )) + } + } + + @Test + def publicInBinaryGiven(): Unit = { + val code = + """import scala.annotation.publicInBinary + |class C: + | @publicInBinary private[C] given packagePrivateGiven1: Int = 1 + | @publicInBinary protected given protectedGiven1: Int = 1 + | @publicInBinary private[C] given packagePrivateGiven2(using Int): Int = 1 + | @publicInBinary protected given protectedGiven2(using Int): Int = 1 + | inline def inlined = + | packagePrivateGiven1 + protectedGiven1 + packagePrivateGiven2(using 1) + protectedGiven2(using 1) + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + checkPublicMethod(cClass, "packagePrivateGiven1", "()I") + checkPublicMethod(cClass, "protectedGiven1", "()I") + + checkPublicMethod(cClass, "packagePrivateGiven2", "(I)I") + checkPublicMethod(cClass, "protectedGiven2", "(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", "packagePrivateGiven1", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedGiven1", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "packagePrivateGiven2", "(I)I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedGiven2", "(I)I", false), + )) + } + } + + @Test + def publicInBinaryClassParam(): Unit = { + val code = + """import scala.annotation.publicInBinary + |class C( + | @publicInBinary private[C] val packagePrivateVal: Int = 1, + | @publicInBinary protected val protectedVal: Int = 1, + |) { + | inline def inlined = + | packagePrivateVal + protectedVal + | def testInlined = inlined + |} + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + checkPublicMethod(cClass, "packagePrivateVal", "()I") + checkPublicMethod(cClass, "protectedVal", "()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", "packagePrivateVal", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedVal", "()I", false), + )) + } + } + + @Test + def publicInBinaryObject(): Unit = { + val code = + """package foo + |import scala.annotation.publicInBinary + |private object PrivateObject + |@publicInBinary private[foo] object PackagePrivateObject + |@publicInBinary protected object ProtectedObject + """.stripMargin + checkBCode(code) { dir => + val privateObject = loadClassNode(dir.subdirectoryNamed("foo").lookupName("PrivateObject$.class", directory = false).input, skipDebugInfo = false) + checkPublicClass(privateObject) + checkPublicField(privateObject, "MODULE$") + + val packagePrivateObject = loadClassNode(dir.subdirectoryNamed("foo").lookupName("PackagePrivateObject$.class", directory = false).input, skipDebugInfo = false) + checkPublicClass(packagePrivateObject) + checkPublicField(packagePrivateObject, "MODULE$") + + val protectedObject = loadClassNode(dir.subdirectoryNamed("foo").lookupName("ProtectedObject$.class", directory = false).input, skipDebugInfo = false) + checkPublicClass(protectedObject) + checkPublicField(protectedObject, "MODULE$") + } + } + + @Test + def publicInBinaryTraitDefs(): Unit = { + val code = + """import scala.annotation.publicInBinary + |trait C: + | @publicInBinary private[C] val packagePrivateVal: Int = 1 + | @publicInBinary protected val protectedVal: Int = 1 + | @publicInBinary private[C] lazy val packagePrivateLazyVal: Int = 1 + | @publicInBinary protected lazy val protectedLazyVal: Int = 1 + | @publicInBinary private[C] var packagePrivateVar: Int = 1 + | @publicInBinary protected var protectedVar: Int = 1 + | @publicInBinary private[C] def packagePrivateDef: Int = 1 + | @publicInBinary protected def protectedDef: Int = 1 + | inline def inlined = + | packagePrivateVar = 1 + | protectedVar = 1 + | packagePrivateVal + + | protectedVal + + | packagePrivateLazyVal + + | protectedLazyVal + + | packagePrivateVar + + | protectedVar + + | packagePrivateDef + + | protectedDef + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cTrait = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPublicMethod(cTrait, "packagePrivateVal", "()I") + checkPublicMethod(cTrait, "protectedVal", "()I") + checkPublicMethod(cTrait, "packagePrivateLazyVal", "()I") + checkPublicMethod(cTrait, "protectedLazyVal", "()I") + checkPublicMethod(cTrait, "packagePrivateVar", "()I") + checkPublicMethod(cTrait, "packagePrivateVar_$eq", "(I)V") + checkPublicMethod(cTrait, "protectedVar", "()I") + checkPublicMethod(cTrait, "protectedVar_$eq", "(I)V") + checkPublicMethod(cTrait, "packagePrivateDef", "()I") + checkPublicMethod(cTrait, "protectedDef", "()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", "packagePrivateVar_$eq", "(I)V", true), + Invoke(INVOKEINTERFACE, "C", "protectedVar_$eq", "(I)V", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateVal", "()I", true), + Invoke(INVOKEINTERFACE, "C", "protectedVal", "()I", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateLazyVal", "()I", true), + Invoke(INVOKEINTERFACE, "C", "protectedLazyVal", "()I", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateVar", "()I", true), + Invoke(INVOKEINTERFACE, "C", "protectedVar", "()I", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateDef", "()I", true), + Invoke(INVOKEINTERFACE, "C", "protectedDef", "()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))) + } + } + + @Test + def noProtectedAccessorsForBinaryInPublic(): Unit = { + val code = + """import scala.annotation.publicInBinary + |package p { + | class A { + | protected def a(): Int = 1 + | @publicInBinary protected def b(): Int = 1 + | } + |} + |package q { + | class B extends p.A { + | trait BInner { + | def test1() = a() // protected accessor for `a` + | def test2() = b() // no protected accessor for `b` + | } + | } + |} + """.stripMargin + checkBCode(code) { dir => + val bClass = loadClassNode(dir.subdirectoryNamed("q").lookupName("B.class", directory = false).input, skipDebugInfo = false) + assert(bClass.methods.asScala.exists(_.name == "protected$a")) + assert(bClass.methods.asScala.forall(_.name != "protected$b")) + + val bInnerClass = loadClassNode(dir.subdirectoryNamed("q").lookupName("B$BInner.class", directory = false).input, skipDebugInfo = false) + + val test1Method = getMethod(bInnerClass, "test1") + val test1Instructions = instructionsFromMethod(test1Method).filter(_.isInstanceOf[Invoke]) + assertSameCode(test1Instructions, List( + Invoke(INVOKEINTERFACE, "q/B$BInner", "q$B$BInner$$$outer", "()Lq/B;", true), + Invoke(INVOKEVIRTUAL, "q/B", "protected$a", "()I", false))) + + val test2Method = getMethod(bInnerClass, "test2") + val test2Instructions = instructionsFromMethod(test2Method).filter(_.isInstanceOf[Invoke]) + assertSameCode(test2Instructions, List( + Invoke(INVOKEINTERFACE, "q/B$BInner", "q$B$BInner$$$outer", "()Lq/B;", true), + Invoke(INVOKEVIRTUAL, "q/B", "b", "()I", false) )) + } + } +} diff --git a/library/src/scala/annotation/publicInBinary.scala b/library/src/scala/annotation/publicInBinary.scala new file mode 100644 index 000000000000..a13e00c08c12 --- /dev/null +++ b/library/src/scala/annotation/publicInBinary.scala @@ -0,0 +1,20 @@ +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`, class constructors, `object`, and `given` definitions. + * A binary API will be publicly available in the bytecode. Tools like TASTy MiMa will take this into account to check + * compatibility. + * + * This annotation cannot be used on `private`/`private[this]` definitions. + * + * `@publicInBinary` can be used to guarantee access to `private[T]`/`protected` definitions: + * - within inline definitions, + * - against previous binary where this definitions was public or less private, + * - or through JVM reflection. + * + * Removing this annotation from a non-public definition is a binary incompatible change. + * Adding this annotation to a non-public definition can also cause binary incompatibilities + * if the definition is accessed in an inline definition (these can be checked using `-WunstableInlineAccessors`). + */ +@experimental +final class publicInBinary extends scala.annotation.StaticAnnotation diff --git a/tests/neg/inline-unstable-accessors.check b/tests/neg/inline-unstable-accessors.check new file mode 100644 index 000000000000..eb226afc376b --- /dev/null +++ b/tests/neg/inline-unstable-accessors.check @@ -0,0 +1,345 @@ +-- [E192] Compatibility Error: tests/neg/inline-unstable-accessors.scala:10:6 ------------------------------------------ +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 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] Compatibility Error: tests/neg/inline-unstable-accessors.scala:11:6 ------------------------------------------ +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 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] 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 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] Compatibility Error: tests/neg/inline-unstable-accessors.scala:23:6 ------------------------------------------ +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 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] Compatibility Error: tests/neg/inline-unstable-accessors.scala:24:6 ------------------------------------------ +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 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] 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 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] Compatibility Error: tests/neg/inline-unstable-accessors.scala:36:6 ------------------------------------------ +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 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] Compatibility Error: tests/neg/inline-unstable-accessors.scala:37:6 ------------------------------------------ +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 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] 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 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] Compatibility Error: tests/neg/inline-unstable-accessors.scala:49:6 ------------------------------------------ +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.valBinaryAPI1 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] Compatibility Error: tests/neg/inline-unstable-accessors.scala:50:6 ------------------------------------------ +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.valBinaryAPI2 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] 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 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] Compatibility Error: tests/neg/inline-unstable-accessors.scala:62:6 ------------------------------------------ +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.valBinaryAPI1 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] Compatibility Error: tests/neg/inline-unstable-accessors.scala:63:6 ------------------------------------------ +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.valBinaryAPI2 + -------------------------------------------------------------------------------------------------------------------- +-- [E192] 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..cf65006daaf8 --- /dev/null +++ b/tests/neg/inline-unstable-accessors.scala @@ -0,0 +1,68 @@ +//> using options -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..5f02d66351c1 --- /dev/null +++ b/tests/neg/publicInBinary-not-accessible.check @@ -0,0 +1,38 @@ +-- [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 the top-level definitions in package foo. + | private[A] value p can only be accessed from class A in package foo. +-- [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 the top-level definitions in package foo. + | private[A] value a can only be accessed from class A in package foo. + | + | 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 the top-level definitions in package foo. + | private[A] lazy value b can only be accessed from class A in package foo. +-- [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 the top-level definitions in package foo. + | private[A] variable c can only be accessed from class A in package foo. +-- [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 the top-level definitions in package foo. + | private[A] method d can only be accessed from class A in package foo. +-- [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 the top-level definitions in package foo. + | private[A] given instance e can only be accessed from class A in package foo. +-- [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 the top-level definitions in package foo. + | private[A] given instance f can only be accessed from class A in package foo. diff --git a/tests/neg/publicInBinary-not-accessible.scala b/tests/neg/publicInBinary-not-accessible.scala new file mode 100644 index 000000000000..23ddbf2db7d0 --- /dev/null +++ b/tests/neg/publicInBinary-not-accessible.scala @@ -0,0 +1,22 @@ +//> using options -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..8cf8690017f1 --- /dev/null +++ b/tests/neg/publicInBinary.check @@ -0,0 +1,40 @@ +-- 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:25:18 ------------------------------------------------------------------------- +25 | @publicInBinary x: Int, // error + | ^ + | @publicInBinary cannot be non `val` constructor parameters +-- Error: tests/neg/publicInBinary.scala:26:31 ------------------------------------------------------------------------- +26 | @publicInBinary private[Bar] y: Int, // error + | ^ + | @publicInBinary cannot be non `val` constructor parameters +-- 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..3034eb65975e --- /dev/null +++ b/tests/neg/publicInBinary.scala @@ -0,0 +1,28 @@ +//> using options -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 + + +class Bar ( + @publicInBinary x: Int, // error + @publicInBinary private[Bar] y: Int, // error + @publicInBinary private[Bar] val z: Int, +) diff --git a/tests/pos-macros/i15413/Macro_1.scala b/tests/pos-macros/i15413/Macro_1.scala new file mode 100644 index 000000000000..56fd4f0f0887 --- /dev/null +++ b/tests/pos-macros/i15413/Macro_1.scala @@ -0,0 +1,10 @@ +//> using options -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..c1e9bab422f8 --- /dev/null +++ b/tests/pos-macros/i15413b/Macro_1.scala @@ -0,0 +1,10 @@ +//> using options -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-special/fatal-warnings/i17735.scala b/tests/pos-special/fatal-warnings/i17735.scala index 56050d8fd5fd..f171d4a028f7 100644 --- a/tests/pos-special/fatal-warnings/i17735.scala +++ b/tests/pos-special/fatal-warnings/i17735.scala @@ -1,4 +1,4 @@ -// scalac: -Wvalue-discard +//> using options -Wvalue-discard import scala.collection.mutable import scala.annotation.nowarn diff --git a/tests/pos-special/fatal-warnings/i17735a.scala b/tests/pos-special/fatal-warnings/i17735a.scala index d089763295e6..fe0ea7e6bc45 100644 --- a/tests/pos-special/fatal-warnings/i17735a.scala +++ b/tests/pos-special/fatal-warnings/i17735a.scala @@ -1,4 +1,4 @@ -// scalac: -Wvalue-discard -Wconf:msg=non-Unit:s +//> using options -Wvalue-discard -Wconf:msg=non-Unit:s import scala.collection.mutable import scala.annotation.nowarn diff --git a/tests/pos-special/fatal-warnings/i17741.scala b/tests/pos-special/fatal-warnings/i17741.scala index 07af8b1abd3d..7171aab83e4b 100644 --- a/tests/pos-special/fatal-warnings/i17741.scala +++ b/tests/pos-special/fatal-warnings/i17741.scala @@ -1,4 +1,4 @@ -// scalac: -Wnonunit-statement +//> using options -Wnonunit-statement class Node() class Elem( diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index 491cc67db5c1..12ea8eb26c47 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -58,6 +58,9 @@ val experimentalDefinitionInLibrary = Set( //// New feature: into "scala.annotation.allowConversions", + //// New feature: @publicInBinary + "scala.annotation.publicInBinary", + //// New feature: Macro annotations "scala.annotation.MacroAnnotation", diff --git a/tests/run/i13215.scala b/tests/run/i13215.scala new file mode 100644 index 000000000000..738eb25d598a --- /dev/null +++ b/tests/run/i13215.scala @@ -0,0 +1,14 @@ +//> using options -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 diff --git a/tests/run/noProtectedSuper.scala b/tests/run/noProtectedSuper.scala new file mode 100644 index 000000000000..41b0615d12ab --- /dev/null +++ b/tests/run/noProtectedSuper.scala @@ -0,0 +1,38 @@ +import scala.annotation.publicInBinary + +package p { + class A { + @publicInBinary protected def foo(): Int = 1 + @publicInBinary protected def fuzz(): Int = 2 + } +} +package q { + class B extends p.A { // protected accessor for foo + trait BInner { + def bar() = foo() + } + } + trait Inner extends p.A { + def bar() = foo() // shared super accessor for foo + // new super accessor for fuzz + class InnerInner { + def bar() = foo() + def baz() = fuzz() + } + } +} +object Test extends App { + val b = new q.B + val bi = new b.BInner {} + assert(bi.bar() == 1) + + class C extends p.A with q.Inner { + // implements super accessors for foo and fuzz + } + val c = new C + assert(c.bar() == 1) + + val d = new c.InnerInner + assert(d.bar() == 1) + assert(d.baz() == 2) +} diff --git a/tests/run/publicInBinary/Lib_1.scala b/tests/run/publicInBinary/Lib_1.scala new file mode 100644 index 000000000000..53cc7ee16c33 --- /dev/null +++ b/tests/run/publicInBinary/Lib_1.scala @@ -0,0 +1,135 @@ +//> using options -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 + + +@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 = 2 + 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] val 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/publicInBinary/Test_2.scala b/tests/run/publicInBinary/Test_2.scala new file mode 100644 index 000000000000..3c3e89419057 --- /dev/null +++ b/tests/run/publicInBinary/Test_2.scala @@ -0,0 +1,26 @@ +import foo.* + +@main def Test: Unit = + val foo: Foo = new Foo(1, 2) + foo.foo + + val bar = new Bar() + bar.foo + bar.bar + + val baz = new Baz() + baz.foo + baz.baz + + val qux = new Qux() + qux.foo + qux.qux + + val c = new inlines.TestPassing + c.foo(1) + c.bar(2) + + testFoo() + localTest() + traits.test() + testConstructors()