diff --git a/community-build/community-projects/scodec b/community-build/community-projects/scodec index 96a77ecaaf91..07f814f5f0d3 160000 --- a/community-build/community-projects/scodec +++ b/community-build/community-projects/scodec @@ -1 +1 @@ -Subproject commit 96a77ecaaf913f195bb4079966a2e9fb41ce214e +Subproject commit 07f814f5f0d3e889b0b79cbb50a928fb62d8a955 diff --git a/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala b/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala index 80ee68bc94c3..7992f77e5508 100644 --- a/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala +++ b/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala @@ -9,6 +9,8 @@ import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.report import dotty.tools.dotc.core.Phases +import scala.annotation.binaryAPIAccessor + /** * Functionality needed in the post-processor whose implementation depends on the compiler * frontend. All methods are synchronized. @@ -20,6 +22,7 @@ sealed abstract class PostProcessorFrontendAccess { def backendReporting: BackendReporting def getEntryPoints: List[String] + @binaryAPIAccessor private val frontendLock: AnyRef = new Object() inline final def frontendSynch[T](inline x: => T): T = frontendLock.synchronized(x) } diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index a6118732d4ae..851d825352f6 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 BinaryAPIAnnotations, // Makes @binaryAPI 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/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 9ffe2bda73cb..e58456ec61a6 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -15,6 +15,8 @@ import NameKinds.AvoidNameKind import util.SimpleIdentitySet import NullOpsDecorator.stripNull +import scala.annotation.{binaryAPI, binaryAPIAccessor} + /** Methods for adding constraints and solving them. * * What goes into a Constraint as opposed to a ConstrainHandler? @@ -39,12 +41,12 @@ trait ConstraintHandling { private var addConstraintInvocations = 0 /** If the constraint is frozen we cannot add new bounds to the constraint. */ - protected var frozenConstraint: Boolean = false + @binaryAPI protected var frozenConstraint: Boolean = false /** Potentially a type lambda that is still instantiatable, even though the constraint * is generally frozen. */ - protected var caseLambda: Type = NoType + @binaryAPI protected var caseLambda: Type = NoType /** If set, align arguments `S1`, `S2`when taking the glb * `T1 { X = S1 } & T2 { X = S2 }` of a constraint upper bound for some type parameter. @@ -56,7 +58,7 @@ trait ConstraintHandling { /** We are currently comparing type lambdas. Used as a flag for * optimization: when `false`, no need to do an expensive `pruneLambdaParams` */ - protected var comparedTypeLambdas: Set[TypeLambda] = Set.empty + @binaryAPI protected var comparedTypeLambdas: Set[TypeLambda] = Set.empty /** Used for match type reduction: If false, we don't recognize an abstract type * to be a subtype type of any of its base classes. This is in place only at the @@ -110,6 +112,7 @@ trait ConstraintHandling { * of `1`. So the lower bound is `1 | x.M` and when we level-avoid that we * get `1 | Int & String`, which simplifies to `Int`. */ + @binaryAPIAccessor private var myTrustBounds = true inline def withUntrustedBounds(op: => Type): Type = diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index e0e43169820a..5e7f5642988d 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -30,6 +30,7 @@ import classfile.ReusableDataReader import StdNames.nme import compiletime.uninitialized +import scala.annotation.{binaryAPI, binaryAPIAccessor} import scala.annotation.internal.sharable import DenotTransformers.DenotTransformer @@ -805,6 +806,7 @@ object Contexts { * Note: plain TypeComparers always take on the kind of the outer comparer if they are in the same context. * In other words: tracking or explaining is a sticky property in the same context. */ + @binaryAPIAccessor private def comparer(using Context): TypeComparer = util.Stats.record("comparing") val base = ctx.base @@ -980,7 +982,7 @@ object Contexts { private[core] var phasesPlan: List[List[Phase]] = uninitialized /** Phases by id */ - private[dotc] var phases: Array[Phase] = uninitialized + @binaryAPI private[dotc] var phases: Array[Phase] = uninitialized /** Phases with consecutive Transforms grouped into a single phase, Empty array if fusion is disabled */ private[core] var fusedPhases: Array[Phase] = Array.empty[Phase] @@ -1019,7 +1021,7 @@ object Contexts { val generalContextPool = ContextPool() private[Contexts] val comparers = new mutable.ArrayBuffer[TypeComparer] - private[Contexts] var comparersInUse: Int = 0 + @binaryAPI private[Contexts] var comparersInUse: Int = 0 private var charArray = new Array[Char](256) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b7211b3ce5e3..1615b413fc44 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1039,6 +1039,8 @@ 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.binaryAPI") + @tu lazy val BinaryAPIAccessorAnnot: ClassSymbol = requiredClass("scala.annotation.binaryAPIAccessor") @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 aa97435d64bb..eba60b7c65ec 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1038,6 +1038,17 @@ 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)) + ) + + /** Is this a member that will have an accessor in the generated binary */ + def isBinaryAPIAccessor(using Context): Boolean = + isTerm && hasAnnotation(defn.BinaryAPIAccessorAnnot) + /** ()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/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 6857e3da38ed..2275ca2721df 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -25,6 +25,8 @@ import reporting.trace import annotation.constructorOnly import cc.{CapturingType, derivedCapturingType, CaptureSet, stripCapturing, isBoxedCapturing, boxed, boxedUnlessFun, boxedIfTypeParam, isAlwaysPure} +import scala.annotation.{binaryAPI, binaryAPIAccessor} + /** Provides methods to compare types. */ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling, PatternTypeConstrainer { @@ -34,7 +36,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling private var myContext: Context = initctx def comparerContext: Context = myContext - protected given [DummySoItsADef]: Context = myContext + @binaryAPI protected given [DummySoItsADef]: Context = myContext protected var state: TyperState = compiletime.uninitialized def constraint: Constraint = state.constraint @@ -115,7 +117,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling private def isBottom(tp: Type) = tp.widen.isRef(NothingClass) - protected def gadtBounds(sym: Symbol)(using Context) = ctx.gadt.bounds(sym) + @binaryAPI protected def gadtBounds(sym: Symbol)(using Context) = ctx.gadt.bounds(sym) protected def gadtAddBound(sym: Symbol, b: Type, isUpper: Boolean): Boolean = ctx.gadtState.addBound(sym, b, isUpper) protected def typeVarInstance(tvar: TypeVar)(using Context): Type = tvar.underlying @@ -156,6 +158,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling private [this] var leftRoot: Type | Null = null /** Are we forbidden from recording GADT constraints? */ + @binaryAPIAccessor private var frozenGadt = false private inline def inFrozenGadt[T](inline op: T): T = inFrozenGadtIf(true)(op) @@ -187,7 +190,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling inFrozenGadtIf(true)(inFrozenConstraint(op)) extension (sym: Symbol) - private inline def onGadtBounds(inline op: TypeBounds => Boolean): Boolean = + @binaryAPIAccessor private inline def onGadtBounds(inline op: TypeBounds => Boolean): Boolean = val bounds = gadtBounds(sym) bounds != null && op(bounds) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index f000fe53f239..456eb732b71c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -39,6 +39,7 @@ import compiletime.uninitialized import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, EventuallyCapturingType, boxedUnlessFun} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} +import scala.annotation.binaryAPI import scala.annotation.internal.sharable import scala.annotation.threadUnsafe @@ -5542,7 +5543,7 @@ object Types { /** Common base class of TypeMap and TypeAccumulator */ abstract class VariantTraversal: - protected[dotc] var variance: Int = 1 + @binaryAPI protected[dotc] var variance: Int = 1 inline protected def atVariance[T](v: Int)(op: => T): T = { val saved = variance diff --git a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala index 060c8d21f390..4c21275f82ef 100644 --- a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala @@ -21,6 +21,7 @@ import transform.{AccessProxies, Splicer} import staging.CrossStageSafety import transform.SymUtils.* import config.Printers.inlining +import util.SrcPos import util.Property import staging.StagingLevel @@ -35,6 +36,9 @@ object PrepareInlineable { def makeInlineable(tree: Tree)(using Context): Tree = ctx.property(InlineAccessorsKey).get.makeInlineable(tree) + def makePrivateBinaryAPIAccessor(sym: Symbol)(using Context): Unit = + ctx.property(InlineAccessorsKey).get.makePrivateBinaryAPIAccessor(sym) + def addAccessorDefs(cls: Symbol, body: List[Tree])(using Context): List[Tree] = ctx.property(InlineAccessorsKey) match case Some(inlineAccessors) => inlineAccessors.addAccessorDefs(cls, body) @@ -51,12 +55,12 @@ object PrepareInlineable { case _ => false } - /** A tree map which inserts accessors for non-public term members accessed from inlined code. - */ - abstract class MakeInlineableMap(val inlineSym: Symbol) extends TreeMap with Insert { - def accessorNameOf(name: TermName, site: Symbol)(using Context): TermName = - val accName = InlineAccessorName(name) - if site.isExtensibleClass then accName.expandedName(site) else accName + trait InsertInlineAccessors extends Insert { + + def accessorNameOf(accessed: Symbol, site: Symbol)(using Context): TermName = + val accName = InlineAccessorName(accessed.name.asTermName) + if site.isExtensibleClass || (accessed.isBinaryAPIAccessor && !site.is(Module)) then accName.expandedName(site) + else accName /** A definition needs an accessor if it is private, protected, or qualified private * and it is not part of the tree that gets inlined. The latter test is implemented @@ -71,10 +75,20 @@ object PrepareInlineable { def needsAccessor(sym: Symbol)(using Context): Boolean = 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) + } + + object InsertPrivateBinaryAPIAccessors extends InsertInlineAccessors + + /** A tree map which inserts accessors for non-public term members accessed from inlined code. + */ + abstract class MakeInlineableMap(val inlineSym: Symbol) extends TreeMap with InsertInlineAccessors { + + override def needsAccessor(sym: Symbol)(using Context): Boolean = + !sym.isContainedIn(inlineSym) && super.needsAccessor(sym) def preTransform(tree: Tree)(using Context): Tree @@ -87,6 +101,41 @@ object PrepareInlineable { override def transform(tree: Tree)(using Context): Tree = postTransform(super.transform(preTransform(tree))) + + protected def unstableAccessorWarning(accessor: Symbol, accessed: Symbol, srcPos: SrcPos)(using Context): Unit = + val accessorDefTree = accessorDef(accessor, accessed) + val annot = if accessed.is(Private) then "@binaryAPIAccessor" else "@binaryAPI" + val solution = + if accessed.is(Private) then + s"Annotate ${accessed.name} with `$annot` to generate a stable accessor." + else + s"Annotate ${accessed.name} with `$annot` to make it accessible." + + val binaryCompat = + val accessorClass = AccessProxies.hostForAccessorOf(accessed: Symbol) + val inlineAccessorMatches = + def binaryAccessorName = + if accessed.owner.is(Module) then InlineAccessorName(accessed.name.asTermName) + else InlineAccessorName(accessed.name.asTermName).expandedName(accessorClass) + accessor.owner == accessed.owner && accessor.name == binaryAccessorName + if !inlineAccessorMatches then + val within = + if accessor.owner.name.isPackageObjectName then accessor.owner.owner.name.stripModuleClassSuffix + else accessor.owner.name.stripModuleClassSuffix + s"""Adding $annot may break binary compatibility if a previous version of this + |library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + |using MiMa. To keep binary compatibility you can add the following accessor to ${accessor.owner.showKind} ${accessor.owner.name.stripModuleClassSuffix}: + | @binaryAPI private[$within] ${accessorDefTree.show} + | + |""".stripMargin + else if !accessed.is(Private) then + s"""Adding $annot may break binary compatibility if a previous version of this + |library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + |using MiMa. To keep binary compatibility you can use @binaryAPIAccessor on + |$accessed.""".stripMargin + else + "" + report.warning(em"Generated unstable inline accessor for $accessed defined in ${accessed.owner}.\n\n$solution\n\n$binaryCompat", srcPos) } /** Direct approach: place the accessor with the accessed symbol. This has the @@ -101,7 +150,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 accessorTree = useAccessor(tree) + if !tree.symbol.isBinaryAPI && !tree.symbol.isBinaryAPIAccessor && tree.symbol != accessorTree.symbol then + unstableAccessorWarning(accessorTree.symbol, tree.symbol, tree.srcPos) + accessorTree case _ => tree } @@ -118,13 +171,14 @@ object PrepareInlineable { * 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("") + * 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("") + * } * } * * `C` could be compiled separately, so we cannot place the inline accessor in it. @@ -185,7 +239,9 @@ object PrepareInlineable { localRefs.map(TypeTree(_)) ++ leadingTypeArgs, // TODO: pass type parameters in two sections? (qual :: Nil) :: otherArgss ) - ref(accessor).appliedToArgss(argss1).withSpan(tree.span) + val accessorTree = ref(accessor).appliedToArgss(argss1).withSpan(tree.span) + + unstableAccessorWarning(accessorTree.symbol, tree.symbol, tree.srcPos) // TODO: Handle references to non-public types. // This is quite tricky, as such types can appear anywhere, including as parts @@ -197,6 +253,7 @@ object PrepareInlineable { // myAccessors += TypeDef(accessor).withPos(tree.pos.focus) // ref(accessor).withSpan(tree.span) // + accessorTree case _: TypeDef if tree.symbol.is(Case) => report.error(reporting.CaseClassInInlinedCode(tree), tree) tree @@ -205,6 +262,14 @@ object PrepareInlineable { } } + /** Create an inline accessor for this definition. */ + def makePrivateBinaryAPIAccessor(sym: Symbol)(using Context): Unit = + if !sym.is(Accessor) && sym.owner.isClass then + val ref = tpd.ref(sym).asInstanceOf[RefTree] + val accessor = InsertPrivateBinaryAPIAccessors.useAccessor(ref) + if sym.is(Mutable) then + InsertPrivateBinaryAPIAccessors.useSetter(accessor) + /** Adds accessors for all non-public term members accessed * from `tree`. Non-public type members are currently left as they are. * This means that references to a private type will lead to typing failures diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 933661b7c5c9..c94743581e6e 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -21,6 +21,8 @@ import config.Feature.{migrateTo3, fewerBracesEnabled} import config.SourceVersion.`3.0` import reporting.{NoProfile, Profile, Message} +import scala.annotation.binaryAPIAccessor + import java.util.Objects object Scanners { @@ -1597,6 +1599,7 @@ object Scanners { protected def coversIndent(w: IndentWidth): Boolean = knownWidth != null && w == indentWidth + @binaryAPIAccessor private var myCommasExpected: Boolean = false inline def withCommasExpected[T](inline op: => T): T = diff --git a/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala b/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala index b3f41fab9eaa..c737df682e43 100644 --- a/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala @@ -19,6 +19,7 @@ import Decorators.{em, toMessage} import util.SourceFile import Utility._ +import scala.annotation.binaryAPIAccessor // XXX/Note: many/most of the functions in here are almost direct cut and pastes // from another file - scala.xml.parsing.MarkupParser, it looks like. @@ -52,7 +53,7 @@ object MarkupParsers { override def getMessage: String = "input ended while parsing XML" } - class MarkupParser(parser: Parser, final val preserveWS: Boolean)(using Context) extends MarkupParserCommon { + class MarkupParser(@binaryAPIAccessor parser: Parser, final val preserveWS: Boolean)(using @binaryAPIAccessor c: Context) extends MarkupParserCommon { import Tokens.{ LBRACE, RBRACE } @@ -93,8 +94,8 @@ object MarkupParsers { var xEmbeddedBlock: Boolean = false private var debugLastStartElement = List.empty[(Int, String)] - private def debugLastPos = debugLastStartElement.head._1 - private def debugLastElem = debugLastStartElement.head._2 + @binaryAPIAccessor private def debugLastPos = debugLastStartElement.head._1 + @binaryAPIAccessor private def debugLastElem = debugLastStartElement.head._2 private def errorBraces() = { reportSyntaxError("in XML content, please use '}}' to express '}'") diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 93de33778750..59d5ce1b4cb5 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -31,11 +31,13 @@ import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} import cc.{CaptureSet, toCaptureSet, IllegalCaptureRef} +import scala.annotation.binaryAPIAccessor + class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { /** A stack of enclosing DefDef, TypeDef, or ClassDef, or ModuleDefs nodes */ private var enclosingDef: untpd.Tree = untpd.EmptyTree - private var myCtx: Context = super.printerContext + @binaryAPIAccessor private var myCtx: Context = super.printerContext private var printPos = ctx.settings.YprintPos.value private val printLines = ctx.settings.printLines.value diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index 8e8d3efb8b40..7ebec9238a45 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -8,6 +8,7 @@ import core.*, Contexts.*, Decorators.* import config.* import printing.Formatting.* +import scala.annotation.binaryAPIAccessor import scala.compiletime.* /** Exposes the {{{ trace("question") { op } }}} syntax. @@ -76,8 +77,10 @@ trait TraceSyntax: inline def apply[T](inline question: String)(inline op: T)(using Context): T = apply[T](question, false)(op) + @binaryAPIAccessor private val alwaysToString = (x: Any) => String.valueOf(x) + @binaryAPIAccessor private def doTrace[T](question: => String, printer: Printers.Printer = Printers.default, showOp: T => String) diff --git a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala index 3175ffceae49..a199ebd3f93b 100644 --- a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala +++ b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala @@ -32,29 +32,33 @@ 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 => { - def numTypeParams = accessed.info match { - case info: PolyType => info.paramNames.length - case _ => 0 - } - val (targs, argss) = splitArgs(prefss) - val (accessRef, forwardedTpts, forwardedArgss) = - 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)), - targs, argss) - val rhs = - if (accessor.name.isSetterName && - forwardedArgss.nonEmpty && forwardedArgss.head.nonEmpty) // defensive conditions - accessRef.becomes(forwardedArgss.head.head) - else - accessRef - .appliedToTypeTrees(forwardedTpts) - .appliedToArgss(forwardedArgss) - .etaExpandCFT(using ctx.withOwner(accessor)) - rhs.withSpan(accessed.span) - }) + accessorDef(accessor, accessed) + + /** The accessor definitions that need to be added to class `cls` */ + 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 + } + val (targs, argss) = splitArgs(prefss) + val (accessRef, forwardedTpts, forwardedArgss) = + if (passReceiverAsArg(accessor.name)) + (argss.head.head.select(accessed), targs.takeRight(numTypeParams), argss.tail) + else + (if (accessed.isStatic) ref(accessed) else ref(TermRef(accessor.owner.thisType, accessed)), + targs, argss) + val rhs = + if (accessor.name.isSetterName && + forwardedArgss.nonEmpty && forwardedArgss.head.nonEmpty) // defensive conditions + accessRef.becomes(forwardedArgss.head.head) + else + accessRef + .appliedToTypeTrees(forwardedTpts) + .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] = { @@ -67,7 +71,7 @@ abstract class AccessProxies { import ast.tpd._ /** The name of the accessor for definition with given `name` in given `site` */ - def accessorNameOf(name: TermName, site: Symbol)(using Context): TermName + def accessorNameOf(accessed: Symbol, site: Symbol)(using Context): TermName def needsAccessor(sym: Symbol)(using Context): Boolean def ifNoHost(reference: RefTree)(using Context): Tree = { @@ -136,7 +140,7 @@ abstract class AccessProxies { if (accessorClass.exists) { if accessorClass.is(Package) then accessorClass = ctx.owner.topLevelClass - val accessorName = accessorNameOf(accessed.name, accessorClass) + val accessorName = accessorNameOf(accessed, accessorClass) val accessorInfo = accessed.info.ensureMethodic.asSeenFrom(accessorClass.thisType, accessed.owner) val accessor = accessorSymbol(accessorClass, accessorName, accessorInfo, accessed) @@ -149,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 @binaryAPI 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/BinaryAPIAnnotations.scala b/compiler/src/dotty/tools/dotc/transform/BinaryAPIAnnotations.scala new file mode 100644 index 000000000000..857598deb00d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/BinaryAPIAnnotations.scala @@ -0,0 +1,32 @@ +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 @binaryAPI definitions public */ +class BinaryAPIAnnotations extends MiniPhase with SymTransformer: + + override def runsAfterGroupsOf: Set[String] = Set(RefChecks.name) + + override def phaseName: String = BinaryAPIAnnotations.name + override def description: String = BinaryAPIAnnotations.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 BinaryAPIAnnotations: + val name: String = "binaryAPIAnnotations" + val description: String = "makes @binaryAPI definitions public" diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index d74392f201ba..d54fa0c91b4d 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -165,10 +165,15 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase atPhase(thisPhase)(cls.annotationsCarrying(Set(defn.CompanionMethodMetaAnnot))) ++ sym.annotations) else + val binaryAPIAnnotOpt = sym.getAnnotation(defn.BinaryAPIAnnot) + val binaryAPIWithPrivateAccessorAnnotOpt = sym.getAnnotation(defn.BinaryAPIAccessorAnnot) 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.BinaryAPIAccessorAnnot)) + for binaryAPIAnnot <- binaryAPIAnnotOpt do sym.addAnnotation(binaryAPIAnnot) + for binaryAPIWithPrivateAccessorAnnot <- binaryAPIWithPrivateAccessorAnnotOpt do sym.addAnnotation(binaryAPIWithPrivateAccessorAnnot) // TODO is this one necessary? 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 6d8f7bdb32cb..b093cf9038d7 100644 --- a/compiler/src/dotty/tools/dotc/transform/ProtectedAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/ProtectedAccessors.scala @@ -64,7 +64,7 @@ class ProtectedAccessors extends MiniPhase { private class Accessors extends AccessProxies { val insert: Insert = new Insert { - def accessorNameOf(name: TermName, site: Symbol)(using Context): TermName = ProtectedAccessorName(name) + def accessorNameOf(accessed: Symbol, site: Symbol)(using Context): TermName = ProtectedAccessorName(accessed.name.asTermName) def needsAccessor(sym: Symbol)(using Context) = ProtectedAccessors.needsAccessor(sym) override def ifNoHost(reference: RefTree)(using Context): Tree = { diff --git a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala index a2f9a0fb45a3..46abd92fd234 100644 --- a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala +++ b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala @@ -2,6 +2,7 @@ package dotty.tools.dotc package transform package sjs +import scala.annotation.binaryAPIAccessor import scala.collection.mutable import ast.tpd @@ -1153,7 +1154,7 @@ object PrepJSInterop { val name: String = "prepjsinterop" val description: String = "additional checks and transformations for Scala.js" - private final class OwnerKind private (private val baseKinds: Int) extends AnyVal { + private final class OwnerKind private (@binaryAPIAccessor private val baseKinds: Int) extends AnyVal { inline def isBaseKind: Boolean = Integer.lowestOneBit(baseKinds) == baseKinds && baseKinds != 0 // exactly 1 bit on diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 436721a6774b..2b62d4a9c63a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -526,6 +526,16 @@ 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"@binaryAPI cannot be used on enum definitions.") + else if sym.isType && !sym.is(Module) && !(sym.is(Given) || sym.companionModule.is(Given)) then fail(em"@binaryAPI cannot be used on ${sym.showKind} definitions") + else if !sym.owner.isClass && !(sym.is(Param) && sym.owner.isConstructor) then fail(em"@binaryAPI cannot be used on local definitions.") + else if sym.is(Private) && !sym.isConstructor then fail(em"@binaryAPI cannot be used on private definitions.\n\nCould the definition `private[${sym.owner.name}]` or `protected` instead or use `@binaryAPIAccessor` instead of `@binaryAPI`.") + if sym.hasAnnotation(defn.BinaryAPIAccessorAnnot) then + if sym.is(Enum) then fail(em"@binaryAPIAccessor cannot be used on enum definitions.") + else if sym.isConstructor then fail(em"@binaryAPIAccessor cannot be used on constructors.") + else if sym.isType && !sym.is(Module) && !(sym.is(Given) || sym.companionModule.is(Given)) then fail(em"@binaryAPIAccessor cannot be used on ${sym.showKind} definitions") + else if !sym.owner.isClass && !(sym.is(Param) && sym.owner.isConstructor) then fail(em"@binaryAPIAccessor cannot be used on local definitions.") if (sym.hasAnnotation(defn.NativeAnnot)) { if (!sym.is(Deferred)) fail(NativeMembersMayNotHaveImplementation(sym)) diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index b1513df777ec..380085cc2b70 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -15,6 +15,8 @@ import util.Property import collection.mutable import Trees._ +import scala.annotation.binaryAPIAccessor + /** A class that handles argument lifting. Argument lifting is needed in the following * scenarios: * - eta expansion @@ -163,7 +165,7 @@ object LiftComplex extends LiftComplex object LiftCoverage extends LiftImpure { // Property indicating whether we're currently lifting the arguments of an application - private val LiftingArgs = new Property.Key[Boolean] + @binaryAPIAccessor private val LiftingArgs = new Property.Key[Boolean] private inline def liftingArgs(using Context): Boolean = ctx.property(LiftingArgs).contains(true) diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 3a5ea05726a5..ff63c887ef55 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -22,6 +22,8 @@ import dotty.tools.dotc.typer.Inferencing._ import dotty.tools.dotc.util.Spans._ import dotty.tools.dotc.util.Stats.record import dotty.tools.dotc.reporting.IllegalVariableInPatternAlternative + +import scala.annotation.binaryAPIAccessor import scala.collection.mutable @@ -213,7 +215,7 @@ trait QuotesAndSplices { }) object splitter extends tpd.TreeMap { - private var variance: Int = 1 + @binaryAPIAccessor private var variance: Int = 1 inline private def atVariance[T](v: Int)(op: => T): T = { val saved = variance diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 6ac45cbcf04d..40220f0ae577 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -99,7 +99,9 @@ trait TypeAssigner { val tpe1 = accessibleType(tpe, superAccess) if tpe1.exists then tpe1 else tpe match - case tpe: NamedType => inaccessibleErrorType(tpe, superAccess, pos) + case tpe: NamedType => + if tpe.termSymbol.isBinaryAPI then tpe + else inaccessibleErrorType(tpe, superAccess, pos) case NoType => tpe /** Return a potentially skolemized version of `qualTpe` to be used diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 7eb8519739c6..a3162b6b7fa1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2429,6 +2429,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) postProcessInfo(sym) + binaryAPIAccessors(sym) vdef1.setDefTree } @@ -2532,6 +2533,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val ddef2 = assignType(cpy.DefDef(ddef)(name, paramss1, tpt1, rhs1), sym) postProcessInfo(sym) + binaryAPIAccessors(sym) ddef2.setDefTree //todo: make sure dependent method types do not depend on implicits or by-name params } @@ -2545,6 +2547,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if !sym.is(Module) && !sym.isConstructor && sym.info.finalResultType.isErasedClass then sym.setFlag(Erased) + /** Generate inline accessors for definitions annotated with @inlineAccessible */ + def binaryAPIAccessors(sym: Symbol)(using Context): Unit = + if !ctx.isAfterTyper && !sym.is(Param) && sym.hasAnnotation(defn.BinaryAPIAccessorAnnot) then + PrepareInlineable.makePrivateBinaryAPIAccessor(sym) + def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(using Context): Tree = { val TypeDef(name, rhs) = tdef completeAnnotations(tdef, sym) diff --git a/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala b/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala index 4dd897dd082a..da27d7b9d2a7 100644 --- a/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala +++ b/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala @@ -3,6 +3,8 @@ package dotty.tools.dotc.util import scala.collection.mutable.ArrayBuffer import scala.util.chaining._ +import scala.annotation.binaryAPIAccessor + /** A wrapper for a list of cached instances of a type `T`. * The wrapper is recursion-reentrant: several instances are kept, so * at each depth of reentrance we are reusing the instance for that. @@ -14,8 +16,10 @@ import scala.util.chaining._ * * Ported from scala.reflect.internal.util.ReusableInstance */ -final class ReusableInstance[T <: AnyRef] private (make: => T) { +final class ReusableInstance[T <: AnyRef] private (@binaryAPIAccessor make: => T) { + @binaryAPIAccessor private[this] val cache = new ArrayBuffer[T](ReusableInstance.InitialSize).tap(_.addOne(make)) + @binaryAPIAccessor private[this] var taken = 0 inline def withInstance[R](action: T => R): R ={ diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index df7ec3da51af..569d6030c5e2 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -22,6 +22,7 @@ import scala.quoted.runtime.{QuoteUnpickler, QuoteMatching} import scala.quoted.runtime.impl.printers._ import scala.reflect.TypeTest +import scala.annotation.binaryAPIAccessor object QuotesImpl { @@ -38,6 +39,7 @@ object QuotesImpl { class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler, QuoteMatching: + @binaryAPIAccessor private val xCheckMacro: Boolean = ctx.settings.XcheckMacros.value extension [T](self: scala.quoted.Expr[T]) diff --git a/compiler/test/dotty/tools/backend/jvm/BinaryAPITests.scala b/compiler/test/dotty/tools/backend/jvm/BinaryAPITests.scala new file mode 100644 index 000000000000..751f2f94cbcb --- /dev/null +++ b/compiler/test/dotty/tools/backend/jvm/BinaryAPITests.scala @@ -0,0 +1,475 @@ +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 BinaryAPITests 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 binaryAPIDef(): Unit = { + val code = + """import scala.annotation.{binaryAPI, binaryAPIAccessor} + |class C: + | @binaryAPIAccessor private def privateBinaryAPI: Int = 1 + | @binaryAPI private[C] def packagePrivateBinaryAPI: Int = 1 + | @binaryAPI protected def protectedBinaryAPI: Int = 1 + | inline def inlined = privateBinaryAPI + packagePrivateBinaryAPI + protectedBinaryAPI + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPrivateMethod(cClass, "privateBinaryAPI", "()I") + checkPublicMethod(cClass, "C$$inline$privateBinaryAPI", "()I") + checkPublicMethod(cClass, "packagePrivateBinaryAPI", "()I") + checkPublicMethod(cClass, "protectedBinaryAPI", "()I") + + // Check that the @binaryAPI annotated method is called + val testInlined = getMethod(cClass, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEVIRTUAL, "C", "C$$inline$privateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "packagePrivateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedBinaryAPI", "()I", false), + )) + } + } + + @Test + def binaryAPIVal(): Unit = { + val code = + """import scala.annotation.{binaryAPI, binaryAPIAccessor} + |class C: + | @binaryAPIAccessor private val privateBinaryAPI: Int = 1 + | @binaryAPI private[C] val packagePrivateBinaryAPI: Int = 1 + | @binaryAPI protected val protectedBinaryAPI: Int = 1 + | @binaryAPIAccessor private lazy val lazyPrivateBinaryAPI: Int = 1 + | @binaryAPI private[C] lazy val lazyPackagePrivateBinaryAPI: Int = 1 + | @binaryAPI protected lazy val lazyProtectedBinaryAPI: Int = 1 + | inline def inlined = privateBinaryAPI + packagePrivateBinaryAPI + protectedBinaryAPI + lazyPrivateBinaryAPI + lazyPackagePrivateBinaryAPI + lazyProtectedBinaryAPI + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPrivateField(cClass, "privateBinaryAPI") + checkPublicMethod(cClass, "C$$inline$privateBinaryAPI", "()I") + checkPublicMethod(cClass, "packagePrivateBinaryAPI", "()I") + checkPublicMethod(cClass, "protectedBinaryAPI", "()I") + + checkPrivateMethod(cClass, "lazyPrivateBinaryAPI", "()I") + checkPublicMethod(cClass, "C$$inline$lazyPrivateBinaryAPI", "()I") + checkPublicMethod(cClass, "lazyPackagePrivateBinaryAPI", "()I") + checkPublicMethod(cClass, "lazyProtectedBinaryAPI", "()I") + + // Check that the @binaryAPI annotated method is called + val testInlined = getMethod(cClass, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEVIRTUAL, "C", "C$$inline$privateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "packagePrivateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "C$$inline$lazyPrivateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "lazyPackagePrivateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "lazyProtectedBinaryAPI", "()I", false), + )) + } + } + + @Test + def binaryAPIVar(): Unit = { + val code = + """import scala.annotation.{binaryAPI, binaryAPIAccessor} + |class C: + | @binaryAPIAccessor private var privateBinaryAPI: Int = 1 + | @binaryAPI private[C] var packagePrivateBinaryAPI: Int = 1 + | @binaryAPI protected var protectedBinaryAPI: Int = 1 + | inline def inlined = + | privateBinaryAPI = 1 + | packagePrivateBinaryAPI = 1 + | protectedBinaryAPI = 1 + | privateBinaryAPI + packagePrivateBinaryAPI + protectedBinaryAPI + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPrivateField(cClass, "privateBinaryAPI") + checkPublicMethod(cClass, "C$$inline$privateBinaryAPI", "()I") + checkPublicMethod(cClass, "C$$inline$privateBinaryAPI_$eq", "(I)V") + checkPublicMethod(cClass, "packagePrivateBinaryAPI", "()I") + checkPublicMethod(cClass, "packagePrivateBinaryAPI_$eq", "(I)V") + checkPublicMethod(cClass, "protectedBinaryAPI", "()I") + checkPublicMethod(cClass, "protectedBinaryAPI_$eq", "(I)V") + + // Check that the @binaryAPI annotated method is called + val testInlined = getMethod(cClass, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEVIRTUAL, "C", "C$$inline$privateBinaryAPI_$eq", "(I)V", false), + Invoke(INVOKEVIRTUAL, "C", "packagePrivateBinaryAPI_$eq", "(I)V", false), + Invoke(INVOKEVIRTUAL, "C", "protectedBinaryAPI_$eq", "(I)V", false), + Invoke(INVOKEVIRTUAL, "C", "C$$inline$privateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "packagePrivateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedBinaryAPI", "()I", false), + )) + } + } + + @Test + def binaryAPIGiven(): Unit = { + val code = + """import scala.annotation.{binaryAPI, binaryAPIAccessor} + |class C: + | @binaryAPIAccessor private given privateBinaryAPI1: Int = 1 + | @binaryAPI private[C] given packagePrivateBinaryAPI1: Int = 1 + | @binaryAPI protected given protectedBinaryAPI1: Int = 1 + | @binaryAPIAccessor private given privateBinaryAPI2(using Int): Int = 1 + | @binaryAPI private[C] given packagePrivateBinaryAPI2(using Int): Int = 1 + | @binaryAPI 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) + checkPrivateMethod(cClass, "privateBinaryAPI1", "()I") + checkPublicMethod(cClass, "C$$inline$privateBinaryAPI1", "()I") + checkPublicMethod(cClass, "packagePrivateBinaryAPI1", "()I") + checkPublicMethod(cClass, "protectedBinaryAPI1", "()I") + + checkPrivateMethod(cClass, "privateBinaryAPI2", "(I)I") + checkPublicMethod(cClass, "C$$inline$privateBinaryAPI2", "(I)I") + checkPublicMethod(cClass, "packagePrivateBinaryAPI2", "(I)I") + checkPublicMethod(cClass, "protectedBinaryAPI2", "(I)I") + + // Check that the @binaryAPI 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 binaryAPIClassParam(): Unit = { + val code = + """import scala.annotation.{binaryAPI, binaryAPIAccessor} + |class C( + | @binaryAPIAccessor private val privateBinaryAPI: Int = 1, + | @binaryAPI private[C] val packagePrivateBinaryAPI: Int = 1, + | @binaryAPI protected val protectedBinaryAPI: Int = 1, + | @binaryAPIAccessor private var privateVarBinaryAPI: Int = 1 + |) { + | inline def inlined = + | privateVarBinaryAPI = 1 + | privateBinaryAPI + packagePrivateBinaryAPI + protectedBinaryAPI + privateVarBinaryAPI + | def testInlined = inlined + |} + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPrivateMethod(cClass, "privateBinaryAPI", "()I") + checkPublicMethod(cClass, "C$$inline$privateBinaryAPI", "()I") + checkPublicMethod(cClass, "packagePrivateBinaryAPI", "()I") + checkPublicMethod(cClass, "protectedBinaryAPI", "()I") + + // Check that the @binaryAPI annotated method is called + val testInlined = getMethod(cClass, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEVIRTUAL, "C", "C$$inline$privateVarBinaryAPI_$eq", "(I)V", false), + Invoke(INVOKEVIRTUAL, "C", "C$$inline$privateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "packagePrivateBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "protectedBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "C$$inline$privateVarBinaryAPI", "()I", false), + )) + } + } + + @Test + def binaryAPIObject(): Unit = { + val code = + """package foo + |import scala.annotation.{binaryAPI, binaryAPIAccessor} + |@binaryAPIAccessor private object PrivateBinaryAPI + |@binaryAPI private[foo] object PackagePrivateBinaryAPI + |@binaryAPI 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 binaryAPITraitDefs(): Unit = { + val code = + """import scala.annotation.{binaryAPI, binaryAPIAccessor} + |trait C: + | @binaryAPIAccessor private val privateValBinaryAPI: Int = 1 + | @binaryAPI private[C] val packagePrivateValBinaryAPI: Int = 1 + | @binaryAPI protected val protectedValBinaryAPI: Int = 1 + | @binaryAPIAccessor private lazy val privateLazyValBinaryAPI: Int = 1 + | @binaryAPI private[C] lazy val packagePrivateLazyValBinaryAPI: Int = 1 + | @binaryAPI protected lazy val protectedLazyValBinaryAPI: Int = 1 + | @binaryAPIAccessor private var privateVarBinaryAPI: Int = 1 + | @binaryAPI private[C] var packagePrivateVarBinaryAPI: Int = 1 + | @binaryAPI protected var protectedVarBinaryAPI: Int = 1 + | @binaryAPIAccessor private def privateDefBinaryAPI: Int = 1 + | @binaryAPI private[C] def packagePrivateDefBinaryAPI: Int = 1 + | @binaryAPI protected def protectedDefBinaryAPI: Int = 1 + | inline def inlined = + | privateVarBinaryAPI = 1 + | packagePrivateVarBinaryAPI = 1 + | protectedVarBinaryAPI = 1 + | privateValBinaryAPI + + | packagePrivateValBinaryAPI + + | protectedValBinaryAPI + + | privateLazyValBinaryAPI + + | packagePrivateLazyValBinaryAPI + + | protectedLazyValBinaryAPI + + | privateVarBinaryAPI + + | packagePrivateVarBinaryAPI + + | protectedVarBinaryAPI + + | privateDefBinaryAPI + + | packagePrivateDefBinaryAPI + + | protectedDefBinaryAPI + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cTrait = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPublicMethod(cTrait, "C$$privateValBinaryAPI", "()I") + checkPublicMethod(cTrait, "packagePrivateValBinaryAPI", "()I") + checkPublicMethod(cTrait, "protectedValBinaryAPI", "()I") + checkPublicMethod(cTrait, "C$$privateLazyValBinaryAPI", "()I") + checkPublicMethod(cTrait, "packagePrivateLazyValBinaryAPI", "()I") + checkPublicMethod(cTrait, "protectedLazyValBinaryAPI", "()I") + checkPublicMethod(cTrait, "C$$privateVarBinaryAPI", "()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 @binaryAPI annotated method is called + val testInlined = getMethod(cTrait, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEINTERFACE, "C", "C$$inline$privateVarBinaryAPI_$eq", "(I)V", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateVarBinaryAPI_$eq", "(I)V", true), + Invoke(INVOKEINTERFACE, "C", "protectedVarBinaryAPI_$eq", "(I)V", true), + Invoke(INVOKEINTERFACE, "C", "C$$inline$privateValBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateValBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "protectedValBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "C$$inline$privateLazyValBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateLazyValBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "protectedLazyValBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "C$$inline$privateVarBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateVarBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "protectedVarBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "C$$inline$privateDefBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "packagePrivateDefBinaryAPI", "()I", true), + Invoke(INVOKEINTERFACE, "C", "protectedDefBinaryAPI", "()I", true) + )) + } + } + + @Test + def binaryAPIDefFinalPrivateAccessors(): Unit = { + val code = + """import scala.annotation.binaryAPIAccessor + |final class C(@binaryAPIAccessor paramBinaryAPI: Int): + | @binaryAPIAccessor private val valBinaryAPI: Int = 1 + | @binaryAPIAccessor private def defBinaryAPI: Int = 1 + | @binaryAPIAccessor private var varBinaryAPI: Int = 1 + | inline def inlined = + | varBinaryAPI = 1 + | paramBinaryAPI + valBinaryAPI + defBinaryAPI + varBinaryAPI + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPrivateField(cClass, "paramBinaryAPI") + checkPublicMethod(cClass, "C$$inline$paramBinaryAPI", "()I") + checkPrivateField(cClass, "valBinaryAPI") + checkPublicMethod(cClass, "C$$inline$valBinaryAPI", "()I") + checkPrivateMethod(cClass, "defBinaryAPI", "()I") + checkPublicMethod(cClass, "C$$inline$defBinaryAPI", "()I") + checkPrivateField(cClass, "varBinaryAPI") + checkPublicMethod(cClass, "C$$inline$varBinaryAPI", "()I") + checkPublicMethod(cClass, "C$$inline$varBinaryAPI_$eq", "(I)V") + + // Check that the @binaryAPI annotated method is called + val testInlined = getMethod(cClass, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEVIRTUAL, "C", "C$$inline$varBinaryAPI_$eq", "(I)V", false), + Invoke(INVOKEVIRTUAL, "C", "C$$inline$paramBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "C$$inline$valBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "C$$inline$defBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "C$$inline$varBinaryAPI", "()I", false), + )) + } + } + + @Test + def binaryAPIDefFinalPrivateAccessorsOnPublic(): Unit = { + val code = + """import scala.annotation.binaryAPIAccessor + |final class C(@binaryAPIAccessor val paramBinaryAPI: Int): + | @binaryAPIAccessor val valBinaryAPI: Int = 1 + | @binaryAPIAccessor def defBinaryAPI: Int = 1 + | @binaryAPIAccessor var varBinaryAPI: Int = 1 + | inline def inlined = + | varBinaryAPI = 1 + | paramBinaryAPI + valBinaryAPI + defBinaryAPI + varBinaryAPI + | def testInlined = inlined + """.stripMargin + checkBCode(code) { dir => + val cClass = loadClassNode(dir.lookupName("C.class", directory = false).input, skipDebugInfo = false) + + checkPrivateField(cClass, "paramBinaryAPI") + checkPublicMethod(cClass, "paramBinaryAPI", "()I") + checkPublicMethod(cClass, "C$$inline$paramBinaryAPI", "()I") + checkPrivateField(cClass, "valBinaryAPI") + checkPublicMethod(cClass, "valBinaryAPI", "()I") + checkPublicMethod(cClass, "C$$inline$valBinaryAPI", "()I") + checkPublicMethod(cClass, "defBinaryAPI", "()I") + checkPublicMethod(cClass, "C$$inline$defBinaryAPI", "()I") + checkPrivateField(cClass, "varBinaryAPI") + checkPublicMethod(cClass, "varBinaryAPI", "()I") + checkPublicMethod(cClass, "C$$inline$varBinaryAPI", "()I") + checkPublicMethod(cClass, "varBinaryAPI_$eq", "(I)V") + checkPublicMethod(cClass, "C$$inline$varBinaryAPI_$eq", "(I)V") + + // Check that the @binaryAPI annotated method is called + val testInlined = getMethod(cClass, "testInlined") + val testInlinedInstructions = instructionsFromMethod(testInlined).filter(_.isInstanceOf[Invoke]) + assertSameCode(testInlinedInstructions, List( + Invoke(INVOKEVIRTUAL, "C", "varBinaryAPI_$eq", "(I)V", false), + Invoke(INVOKEVIRTUAL, "C", "paramBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "valBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "defBinaryAPI", "()I", false), + Invoke(INVOKEVIRTUAL, "C", "varBinaryAPI", "()I", false), + )) + } + } + + @Test + def i13215(): Unit = { + val code = + """import scala.annotation.binaryAPI + |package foo: + | trait Bar: + | inline def baz = Baz + | def testInlined = baz + | @binaryAPI 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.binaryAPIAccessor + |package foo: + | trait Bar: + | inline def baz = Baz + | def testInlined = baz + | @binaryAPIAccessor 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.binaryAPI + |class Macro: + | inline def foo = Macro.fooImpl + | def test = foo + |object Macro: + | @binaryAPI 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.binaryAPI + |class C: + | inline def baz = D.bazImpl + | def test = baz + |object D: + | @binaryAPI 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/binaryAPI.scala b/library/src/scala/annotation/binaryAPI.scala new file mode 100644 index 000000000000..1a946347e44a --- /dev/null +++ b/library/src/scala/annotation/binaryAPI.scala @@ -0,0 +1,13 @@ +package scala.annotation + +/** A binary API is a definition that is annotated with `@binaryAPI` or overrides a definition annotated with `@binaryAPI`. + * 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. See `scala.annotation.binaryAPIAccessor`. + * + * This can be used to access private/protected definitions within inline definitions. + * + * Removing this annotation from a non-public definition is a binary incompatible change. + */ +final class binaryAPI extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/annotation/binaryAPIAccessor.scala b/library/src/scala/annotation/binaryAPIAccessor.scala new file mode 100644 index 000000000000..673d27cafab5 --- /dev/null +++ b/library/src/scala/annotation/binaryAPIAccessor.scala @@ -0,0 +1,12 @@ +package scala.annotation + +/** A binary API with accessor is a definition that is annotated with `@binaryAPIAccessor`. + * This annotation can be placed on `def`, `val`, `lazy val`, `var`, `object`, and `given` definitions. + * The annotated definition will get a public accessor. + * + * This can be used to access `private`/`private[this]` definitions within inline definitions. + * To access other private/protected definition see `scala.annotation.binaryAPI`. + * + * Removing this annotation is a binary incompatible change. + */ +final class binaryAPIAccessor extends scala.annotation.StaticAnnotation diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index d53eeb7077a4..ca200f4e76f4 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -17,6 +17,8 @@ object MiMaFilters { ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary$Break"), ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary$Label"), ProblemFilters.exclude[MissingClassProblem]("scala.quoted.runtime.QuoteMatching$"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.binaryAPI"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.binaryAPIAccessor"), // Scala.js only: new runtime support class in 3.2.3; not available to users ProblemFilters.exclude[MissingClassProblem]("scala.scalajs.runtime.AnonFunctionXXL"), diff --git a/tests/init/pos/i15465.scala b/tests/init/pos/i15465.scala index 5b99670e9027..67e486032ae8 100644 --- a/tests/init/pos/i15465.scala +++ b/tests/init/pos/i15465.scala @@ -1,10 +1,12 @@ +import scala.annotation.binaryAPIAccessor + class TestSuite: protected val it = new ItWord protected final class ItWord: def should(string: String) = new ItVerbString("should", string) - private def registerTestToRun(fun: => Any): Unit = () + @binaryAPIAccessor private def registerTestToRun(fun: => Any): Unit = () protected final class ItVerbString(verb: String, name: String): inline def in(testFun: => Any): Unit = registerTestToRun(testFun) diff --git a/tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.check b/tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.check new file mode 100644 index 000000000000..50de1d9fc1fc --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.check @@ -0,0 +1,164 @@ +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:9:4 ------------------------------------- +9 | valBinaryAPI1 + // error + | ^^^^^^^^^^^^^ + | Generated unstable inline accessor for value valBinaryAPI1 defined in class A. + | + | Annotate valBinaryAPI1 with `@binaryAPIAccessor` to generate a stable accessor. + | +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:10:4 ------------------------------------ +10 | valBinaryAPI2 + // error + | ^^^^^^^^^^^^^ + | Generated unstable inline accessor for value valBinaryAPI2 defined in class A. + | + | Annotate valBinaryAPI2 with `@binaryAPI` to make it accessible. + | + | Adding @binaryAPI may break binary compatibility if a previous version of this + | library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + | using MiMa. To keep binary compatibility you can use @binaryAPIAccessor on + | val valBinaryAPI2. +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:15:6 ------------------------------------ +15 | a.valBinaryAPI2 + // error + | ^^^^^^^^^^^^^^^ + | Generated unstable inline accessor for value valBinaryAPI2 defined in class A. + | + | Annotate valBinaryAPI2 with `@binaryAPI` to make it accessible. + | + | Adding @binaryAPI may break binary compatibility if a previous version of this + | library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + | using MiMa. To keep binary compatibility you can add the following accessor to class B: + | @binaryAPI private[B] def inline$valBinaryAPI2$i1(x$0: foo.A): Int = x$0.valBinaryAPI2 + | +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:24:4 ------------------------------------ +24 | valBinaryAPI1 + // error + | ^^^^^^^^^^^^^ + | Generated unstable inline accessor for value valBinaryAPI1 defined in class C. + | + | Annotate valBinaryAPI1 with `@binaryAPIAccessor` to generate a stable accessor. + | + | Adding @binaryAPIAccessor may break binary compatibility if a previous version of this + | library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + | using MiMa. To keep binary compatibility you can add the following accessor to class C: + | @binaryAPI private[C] final def inline$valBinaryAPI1: Int = this.valBinaryAPI1 + | +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:25:4 ------------------------------------ +25 | valBinaryAPI2 + // error + | ^^^^^^^^^^^^^ + | Generated unstable inline accessor for value valBinaryAPI2 defined in class C. + | + | Annotate valBinaryAPI2 with `@binaryAPI` to make it accessible. + | + | Adding @binaryAPI may break binary compatibility if a previous version of this + | library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + | using MiMa. To keep binary compatibility you can add the following accessor to class C: + | @binaryAPI private[C] def inline$valBinaryAPI2: Int = this.valBinaryAPI2 + | +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:30:6 ------------------------------------ +30 | c.valBinaryAPI2 + // error + | ^^^^^^^^^^^^^^^ + | Generated unstable inline accessor for value valBinaryAPI2 defined in class C. + | + | Annotate valBinaryAPI2 with `@binaryAPI` to make it accessible. + | + | Adding @binaryAPI may break binary compatibility if a previous version of this + | library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + | using MiMa. To keep binary compatibility you can add the following accessor to class D: + | @binaryAPI private[D] def inline$valBinaryAPI2$i2(x$0: foo.C): Int = x$0.valBinaryAPI2 + | +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:39:4 ------------------------------------ +39 | valBinaryAPI1 + // error + | ^^^^^^^^^^^^^ + | Generated unstable inline accessor for value valBinaryAPI1 defined in object E. + | + | Annotate valBinaryAPI1 with `@binaryAPIAccessor` to generate a stable accessor. + | +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:40:4 ------------------------------------ +40 | valBinaryAPI2 + // error + | ^^^^^^^^^^^^^ + | Generated unstable inline accessor for value valBinaryAPI2 defined in object E. + | + | Annotate valBinaryAPI2 with `@binaryAPI` to make it accessible. + | + | Adding @binaryAPI may break binary compatibility if a previous version of this + | library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + | using MiMa. To keep binary compatibility you can use @binaryAPIAccessor on + | val valBinaryAPI2. +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:45:6 ------------------------------------ +45 | E.valBinaryAPI2 + // error + | ^^^^^^^^^^^^^^^ + | Generated unstable inline accessor for value valBinaryAPI2 defined in object E. + | + | Annotate valBinaryAPI2 with `@binaryAPI` to make it accessible. + | + | Adding @binaryAPI may break binary compatibility if a previous version of this + | library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + | using MiMa. To keep binary compatibility you can add the following accessor to object F: + | @binaryAPI private[F] def inline$valBinaryAPI2$i3(x$0: foo.E): Int = x$0.valBinaryAPI2 + | +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:54:4 ------------------------------------ +54 | valBinaryAPI1 + // error + | ^^^^^^^^^^^^^ + | Generated unstable inline accessor for value valBinaryAPI1 defined in package object G. + | + | Annotate valBinaryAPI1 with `@binaryAPI` to make it accessible. + | + | Adding @binaryAPI may break binary compatibility if a previous version of this + | library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + | using MiMa. To keep binary compatibility you can use @binaryAPIAccessor on + | val valBinaryAPI1. +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:55:4 ------------------------------------ +55 | valBinaryAPI2 + // error + | ^^^^^^^^^^^^^ + | Generated unstable inline accessor for value valBinaryAPI2 defined in package object G. + | + | Annotate valBinaryAPI2 with `@binaryAPI` to make it accessible. + | + | Adding @binaryAPI may break binary compatibility if a previous version of this + | library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + | using MiMa. To keep binary compatibility you can use @binaryAPIAccessor on + | val valBinaryAPI2. +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:60:6 ------------------------------------ +60 | G.valBinaryAPI2 + // error + | ^^^^^^^^^^^^^^^ + | Generated unstable inline accessor for value valBinaryAPI2 defined in package object G. + | + | Annotate valBinaryAPI2 with `@binaryAPI` to make it accessible. + | + | Adding @binaryAPI may break binary compatibility if a previous version of this + | library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + | using MiMa. To keep binary compatibility you can add the following accessor to package object package: + | @binaryAPI private[H] def inline$valBinaryAPI2$i4(x$0: foo.G): Int = x$0.valBinaryAPI2 + | +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:69:4 ------------------------------------ +69 | valBinaryAPI1 + // error + | ^^^^^^^^^^^^^ + |Generated unstable inline accessor for value valBinaryAPI1 defined in package object inline-unstable-accessors$package. + | + |Annotate valBinaryAPI1 with `@binaryAPI` to make it accessible. + | + |Adding @binaryAPI may break binary compatibility if a previous version of this + |library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + |using MiMa. To keep binary compatibility you can use @binaryAPIAccessor on + |val valBinaryAPI1. +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:70:4 ------------------------------------ +70 | valBinaryAPI2 + // error + | ^^^^^^^^^^^^^ + |Generated unstable inline accessor for value valBinaryAPI2 defined in package object inline-unstable-accessors$package. + | + |Annotate valBinaryAPI2 with `@binaryAPI` to make it accessible. + | + |Adding @binaryAPI may break binary compatibility if a previous version of this + |library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + |using MiMa. To keep binary compatibility you can use @binaryAPIAccessor on + |val valBinaryAPI2. +-- Error: tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala:75:6 ------------------------------------ +75 | I.valBinaryAPI2 + // error + | ^^^^^^^^^^^^^^^ + |Generated unstable inline accessor for value valBinaryAPI2 defined in package object inline-unstable-accessors$package. + | + |Annotate valBinaryAPI2 with `@binaryAPI` to make it accessible. + | + |Adding @binaryAPI may break binary compatibility if a previous version of this + |library was compiled with Scala 3.0-3.3, Binary compatibility should be checked + |using MiMa. To keep binary compatibility you can add the following accessor to package object inline-unstable-accessors$package: + | @binaryAPI private[J] def inline$valBinaryAPI2$i5(x$0: foo.I): Int = x$0.valBinaryAPI2 + | diff --git a/tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala b/tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala new file mode 100644 index 000000000000..0eb673aa5b81 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/inline-unstable-accessors.scala @@ -0,0 +1,76 @@ +package foo +import scala.annotation.{binaryAPI, binaryAPIAccessor} +class A: + private val valBinaryAPI1: Int = 1 + private[foo] val valBinaryAPI2: Int = 1 + @binaryAPIAccessor private val valBinaryAPI3: Int = 1 + @binaryAPI private[foo] val valBinaryAPI4: Int = 1 + inline def inlined = + valBinaryAPI1 + // error + valBinaryAPI2 + // error + valBinaryAPI3 + + valBinaryAPI4 +class B(val a: A): + inline def inlined = + a.valBinaryAPI2 + // error + a.valBinaryAPI4 + +final class C: + private val valBinaryAPI1: Int = 1 + private[foo] val valBinaryAPI2: Int = 1 + @binaryAPIAccessor private val valBinaryAPI3: Int = 1 + @binaryAPI private[foo] val valBinaryAPI4: Int = 1 + inline def inlined = + valBinaryAPI1 + // error + valBinaryAPI2 + // error + valBinaryAPI3 + + valBinaryAPI4 +final class D(val c: C): + inline def inlined = + c.valBinaryAPI2 + // error + c.valBinaryAPI4 + +object E: + private val valBinaryAPI1: Int = 1 + private[foo] val valBinaryAPI2: Int = 1 + @binaryAPIAccessor private val valBinaryAPI3: Int = 1 + @binaryAPI private[foo] val valBinaryAPI4: Int = 1 + inline def inlined = + valBinaryAPI1 + // error + valBinaryAPI2 + // error + valBinaryAPI3 + + valBinaryAPI4 +object F: + inline def inlined = + E.valBinaryAPI2 + // error + E.valBinaryAPI4 + +package object G: + private val valBinaryAPI1: Int = 1 + private[foo] val valBinaryAPI2: Int = 1 + @binaryAPIAccessor private val valBinaryAPI3: Int = 1 + @binaryAPI private[foo] val valBinaryAPI4: Int = 1 + inline def inlined = + valBinaryAPI1 + // error + valBinaryAPI2 + // error + valBinaryAPI3 + + valBinaryAPI4 +package object H: + inline def inlined = + G.valBinaryAPI2 + // error + G.valBinaryAPI4 + +package I: + private val valBinaryAPI1: Int = 1 + private[foo] val valBinaryAPI2: Int = 1 + @binaryAPIAccessor private val valBinaryAPI3: Int = 1 + @binaryAPI private[foo] val valBinaryAPI4: Int = 1 + inline def inlined = + valBinaryAPI1 + // error + valBinaryAPI2 + // error + valBinaryAPI3 + + valBinaryAPI4 +package J: + inline def inlined = + I.valBinaryAPI2 + // error + I.valBinaryAPI4 diff --git a/tests/neg-macros/delegate-match-1/Macro_1.scala b/tests/neg-macros/delegate-match-1/Macro_1.scala index 7c5702b56fc6..63eff61b4017 100644 --- a/tests/neg-macros/delegate-match-1/Macro_1.scala +++ b/tests/neg-macros/delegate-match-1/Macro_1.scala @@ -1,9 +1,9 @@ import scala.quoted.* - +import scala.annotation.binaryAPI inline def f: Any = ${ fImpl } -private def fImpl(using Quotes): Expr[Unit] = { +@binaryAPI private def fImpl(using Quotes): Expr[Unit] = { import quotes.reflect.* Implicits.search(TypeRepr.of[A]) match { case x: ImplicitSearchSuccess => diff --git a/tests/neg-macros/delegate-match-2/Macro_1.scala b/tests/neg-macros/delegate-match-2/Macro_1.scala index 6c7e6917900e..5c006cfddc54 100644 --- a/tests/neg-macros/delegate-match-2/Macro_1.scala +++ b/tests/neg-macros/delegate-match-2/Macro_1.scala @@ -1,9 +1,9 @@ import scala.quoted.* - +import scala.annotation.binaryAPI inline def f: Any = ${ fImpl } -private def fImpl (using Quotes) : Expr[Unit] = { +@binaryAPI private def fImpl (using Quotes) : Expr[Unit] = { import quotes.reflect.* Implicits.search(TypeRepr.of[A]) match { case x: ImplicitSearchSuccess => diff --git a/tests/neg-macros/delegate-match-3/Macro_1.scala b/tests/neg-macros/delegate-match-3/Macro_1.scala index 89e467388a6f..9fa7e5ba31c5 100644 --- a/tests/neg-macros/delegate-match-3/Macro_1.scala +++ b/tests/neg-macros/delegate-match-3/Macro_1.scala @@ -1,9 +1,9 @@ import scala.quoted.* - +import scala.annotation.binaryAPI inline def f: Any = ${ fImpl } -private def fImpl(using Quotes) : Expr[Unit] = { +@binaryAPI private def fImpl(using Quotes) : Expr[Unit] = { import quotes.reflect.* Implicits.search(TypeRepr.of[A]) match { case x: ImplicitSearchSuccess => diff --git a/tests/neg-macros/ill-abort.check b/tests/neg-macros/ill-abort.check index c267c2e79ecf..3e449cda0f08 100644 --- a/tests/neg-macros/ill-abort.check +++ b/tests/neg-macros/ill-abort.check @@ -6,7 +6,7 @@ |--------------------------------------------------------------------------------------------------------------------- |Inline stack trace |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from quoted_1.scala:3 -3 |inline def fail(): Unit = ${ impl } + |This location contains code that was inlined from quoted_1.scala:4 +4 |inline def fail(): Unit = ${ impl } | ^^^^^^^^^ --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/ill-abort/quoted_1.scala b/tests/neg-macros/ill-abort/quoted_1.scala index 0392ef5ae571..88dc19115882 100644 --- a/tests/neg-macros/ill-abort/quoted_1.scala +++ b/tests/neg-macros/ill-abort/quoted_1.scala @@ -1,7 +1,8 @@ import scala.quoted.* +import scala.annotation.binaryAPI inline def fail(): Unit = ${ impl } -private def impl(using Quotes) : Expr[Unit] = +@binaryAPI private def impl(using Quotes) : Expr[Unit] = // should never be done without reporting error before (see docs) throw new scala.quoted.runtime.StopMacroExpansion diff --git a/tests/neg/binaryAPI-not-visible.check b/tests/neg/binaryAPI-not-visible.check new file mode 100644 index 000000000000..de0f9dc00108 --- /dev/null +++ b/tests/neg/binaryAPI-not-visible.check @@ -0,0 +1,31 @@ +-- [E173] Reference Error: tests/neg/binaryAPI-not-visible.scala:15:4 -------------------------------------------------- +15 | a.p // error + | ^^^ + | value p cannot be accessed as a member of (a : foo.A) from module class binaryAPI-not-visible$package$. +-- [E173] Reference Error: tests/neg/binaryAPI-not-visible.scala:16:4 -------------------------------------------------- +16 | a.a // error + | ^^^ + | value a cannot be accessed as a member of (a² : foo.A) from module class binaryAPI-not-visible$package$. + | + | where: a is a value in class A + | a² is a parameter in method test +-- [E173] Reference Error: tests/neg/binaryAPI-not-visible.scala:17:4 -------------------------------------------------- +17 | a.b // error + | ^^^ + | lazy value b cannot be accessed as a member of (a : foo.A) from module class binaryAPI-not-visible$package$. +-- [E173] Reference Error: tests/neg/binaryAPI-not-visible.scala:18:4 -------------------------------------------------- +18 | a.c // error + | ^^^ + | variable c cannot be accessed as a member of (a : foo.A) from module class binaryAPI-not-visible$package$. +-- [E173] Reference Error: tests/neg/binaryAPI-not-visible.scala:19:4 -------------------------------------------------- +19 | a.d // error + | ^^^ + | method d cannot be accessed as a member of (a : foo.A) from module class binaryAPI-not-visible$package$. +-- [E173] Reference Error: tests/neg/binaryAPI-not-visible.scala:20:4 -------------------------------------------------- +20 | a.e // error + | ^^^ + |given instance e cannot be accessed as a member of (a : foo.A) from module class binaryAPI-not-visible$package$. +-- [E173] Reference Error: tests/neg/binaryAPI-not-visible.scala:21:4 -------------------------------------------------- +21 | a.f(using 1.0) // error + | ^^^ + |given instance f cannot be accessed as a member of (a : foo.A) from module class binaryAPI-not-visible$package$. diff --git a/tests/neg/binaryAPI-not-visible.scala b/tests/neg/binaryAPI-not-visible.scala new file mode 100644 index 000000000000..8c5f47ab977c --- /dev/null +++ b/tests/neg/binaryAPI-not-visible.scala @@ -0,0 +1,21 @@ +package foo + +import scala.annotation.binaryAPI + +class A(@binaryAPI private[A] val p: Int): + @binaryAPI private[A] val a: Int = 1 + @binaryAPI private[A] lazy val b: Int = 1 + @binaryAPI private[A] var c: Int = 1 + @binaryAPI private[A] def d: Int = 1 + @binaryAPI private[A] given e: Int = 1 + @binaryAPI 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/binaryAPI.check b/tests/neg/binaryAPI.check new file mode 100644 index 000000000000..134b77e96bc7 --- /dev/null +++ b/tests/neg/binaryAPI.check @@ -0,0 +1,32 @@ +-- Error: tests/neg/binaryAPI.scala:6:17 ------------------------------------------------------------------------------- +6 |@binaryAPI class C: // error + | ^ + | @binaryAPI cannot be used on class definitions +-- Error: tests/neg/binaryAPI.scala:8:19 ------------------------------------------------------------------------------- +8 | @binaryAPI def g = () // error + | ^ + | @binaryAPI cannot be used on local definitions. +-- Error: tests/neg/binaryAPI.scala:10:19 ------------------------------------------------------------------------------ +10 |class D[@binaryAPI T] // error + | ^ + | @binaryAPI cannot be used on type definitions +-- Error: tests/neg/binaryAPI.scala:14:16 ------------------------------------------------------------------------------ +14 |@binaryAPI enum Enum1: // error + | ^ + | @binaryAPI cannot be used on enum definitions. +-- Error: tests/neg/binaryAPI.scala:18:18 ------------------------------------------------------------------------------ +18 | @binaryAPI case A // error + | ^ + | @binaryAPI cannot be used on enum definitions. +-- Error: tests/neg/binaryAPI.scala:19:18 ------------------------------------------------------------------------------ +19 | @binaryAPI case B(a: Int) // error + | ^ + | @binaryAPI cannot be used on enum definitions. +-- Error: tests/neg/binaryAPI.scala:5:16 ------------------------------------------------------------------------------- +5 |@binaryAPI type A // error + | ^ + | @binaryAPI cannot be used on type definitions +-- Error: tests/neg/binaryAPI.scala:12:17 ------------------------------------------------------------------------------ +12 |def f(@binaryAPI x: Int) = 3 // error + | ^ + | @binaryAPI cannot be used on local definitions. diff --git a/tests/neg/binaryAPI.scala b/tests/neg/binaryAPI.scala new file mode 100644 index 000000000000..a6b4223b12a3 --- /dev/null +++ b/tests/neg/binaryAPI.scala @@ -0,0 +1,19 @@ +package foo + +import scala.annotation.binaryAPI + +@binaryAPI type A // error +@binaryAPI class C: // error + def f: Unit = + @binaryAPI def g = () // error + () +class D[@binaryAPI T] // error + +def f(@binaryAPI x: Int) = 3 // error + +@binaryAPI enum Enum1: // error + case A + +enum Enum2: + @binaryAPI case A // error + @binaryAPI case B(a: Int) // error diff --git a/tests/neg/binaryAPIAccessor.check b/tests/neg/binaryAPIAccessor.check new file mode 100644 index 000000000000..c2fdd20e2a07 --- /dev/null +++ b/tests/neg/binaryAPIAccessor.check @@ -0,0 +1,48 @@ +-- Error: tests/neg/binaryAPIAccessor.scala:6:25 ----------------------------------------------------------------------- +6 |@binaryAPIAccessor class C: // error + | ^ + | @binaryAPIAccessor cannot be used on class definitions +-- Error: tests/neg/binaryAPIAccessor.scala:8:27 ----------------------------------------------------------------------- +8 | @binaryAPIAccessor def g = () // error + | ^ + | @binaryAPIAccessor cannot be used on local definitions. +-- Error: tests/neg/binaryAPIAccessor.scala:10:27 ---------------------------------------------------------------------- +10 |class D[@binaryAPIAccessor T] // error + | ^ + | @binaryAPIAccessor cannot be used on type definitions +-- Error: tests/neg/binaryAPIAccessor.scala:14:24 ---------------------------------------------------------------------- +14 |@binaryAPIAccessor enum Enum1: // error + | ^ + | @binaryAPIAccessor cannot be used on enum definitions. +-- Error: tests/neg/binaryAPIAccessor.scala:18:26 ---------------------------------------------------------------------- +18 | @binaryAPIAccessor case A // error + | ^ + | @binaryAPIAccessor cannot be used on enum definitions. +-- Error: tests/neg/binaryAPIAccessor.scala:19:26 ---------------------------------------------------------------------- +19 | @binaryAPIAccessor case B(a: Int) // error + | ^ + | @binaryAPIAccessor cannot be used on enum definitions. +-- Error: tests/neg/binaryAPIAccessor.scala:21:9 ----------------------------------------------------------------------- +21 |class Foo @binaryAPIAccessor private (x: Int): // error + | ^ + | @binaryAPIAccessor cannot be used on constructors. +-- Error: tests/neg/binaryAPIAccessor.scala:22:33 ---------------------------------------------------------------------- +22 | @binaryAPIAccessor private def this(x: Int, y: Int) = this(x + y) // error + | ^ + | @binaryAPIAccessor cannot be used on constructors. +-- Error: tests/neg/binaryAPIAccessor.scala:24:9 ----------------------------------------------------------------------- +24 |class Bar @binaryAPIAccessor private[this] (x: Int): // error + | ^ + | @binaryAPIAccessor cannot be used on constructors. +-- Error: tests/neg/binaryAPIAccessor.scala:25:39 ---------------------------------------------------------------------- +25 | @binaryAPIAccessor private[this] def this(x: Int, y: Int) = this(x + y) // error + | ^ + | @binaryAPIAccessor cannot be used on constructors. +-- Error: tests/neg/binaryAPIAccessor.scala:5:24 ----------------------------------------------------------------------- +5 |@binaryAPIAccessor type A // error + | ^ + | @binaryAPIAccessor cannot be used on type definitions +-- Error: tests/neg/binaryAPIAccessor.scala:12:25 ---------------------------------------------------------------------- +12 |def f(@binaryAPIAccessor x: Int) = 3 // error + | ^ + | @binaryAPIAccessor cannot be used on local definitions. diff --git a/tests/neg/binaryAPIAccessor.scala b/tests/neg/binaryAPIAccessor.scala new file mode 100644 index 000000000000..a7a0fd6b22a4 --- /dev/null +++ b/tests/neg/binaryAPIAccessor.scala @@ -0,0 +1,25 @@ +package foo + +import scala.annotation.binaryAPIAccessor + +@binaryAPIAccessor type A // error +@binaryAPIAccessor class C: // error + def f: Unit = + @binaryAPIAccessor def g = () // error + () +class D[@binaryAPIAccessor T] // error + +def f(@binaryAPIAccessor x: Int) = 3 // error + +@binaryAPIAccessor enum Enum1: // error + case A + +enum Enum2: + @binaryAPIAccessor case A // error + @binaryAPIAccessor case B(a: Int) // error + +class Foo @binaryAPIAccessor private (x: Int): // error + @binaryAPIAccessor private def this(x: Int, y: Int) = this(x + y) // error + +class Bar @binaryAPIAccessor private[this] (x: Int): // error + @binaryAPIAccessor private[this] def this(x: Int, y: Int) = this(x + y) // error diff --git a/tests/pos-custom-args/i13405/Macro.scala b/tests/pos-custom-args/i13405/Macro.scala index 2996555a6e0c..8a2539e7391e 100644 --- a/tests/pos-custom-args/i13405/Macro.scala +++ b/tests/pos-custom-args/i13405/Macro.scala @@ -1,9 +1,9 @@ import scala.quoted.* +import scala.annotation.binaryAPI sealed class Foo() inline def hh(): Unit = ${ interpMacro() } - -private def interpMacro()(using Quotes): Expr[Unit] = +@binaryAPI private def interpMacro()(using Quotes): Expr[Unit] = import quotes.reflect.* '{ val res: Either[String, (Foo, Foo)] = diff --git a/tests/pos-macros/i15413/Macro_1.scala b/tests/pos-macros/i15413/Macro_1.scala new file mode 100644 index 000000000000..116095d0b71f --- /dev/null +++ b/tests/pos-macros/i15413/Macro_1.scala @@ -0,0 +1,8 @@ +import scala.quoted.* +import scala.annotation.binaryAPI + +class Macro: + inline def foo = ${ Macro.fooImpl } + +object Macro: + @binaryAPI 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..05e67eca7f83 --- /dev/null +++ b/tests/pos-macros/i15413b/Macro_1.scala @@ -0,0 +1,8 @@ +package bar + +import scala.quoted.* +import scala.annotation.binaryAPI + +inline def foo = ${ fooImpl } + +@binaryAPI 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-macros/i9570.scala b/tests/pos-macros/i9570.scala index 295969813df6..e5644a3effa0 100644 --- a/tests/pos-macros/i9570.scala +++ b/tests/pos-macros/i9570.scala @@ -1,4 +1,5 @@ import scala.quoted.* +import scala.annotation.binaryAPIAccessor object Macros { @@ -7,6 +8,7 @@ object Macros { case class HCons[+HD, TL <: HList](hd: HD, tl: TL) extends HList case object HNil extends HList + @binaryAPIAccessor private def sizeImpl(e: Expr[HList], n:Int)(using qctx:Quotes): Expr[Int] = { import quotes.reflect.* e match { diff --git a/tests/pos/binaryAPI.scala b/tests/pos/binaryAPI.scala new file mode 100644 index 000000000000..6ae93b800666 --- /dev/null +++ b/tests/pos/binaryAPI.scala @@ -0,0 +1,142 @@ +package foo + +import scala.annotation.{binaryAPI, binaryAPIAccessor} + +class Foo(@binaryAPIAccessor param: Int, @binaryAPI private[Foo] val paramVal: Int, @binaryAPI private[Foo] var paramVar: Int): + @binaryAPIAccessor + private val privateVal: Int = 2 + @binaryAPI + protected val protectedVal: Int = 2 + @binaryAPI + private[foo] val packagePrivateVal: Int = 2 + @binaryAPIAccessor + private val privateVar: Int = 2 + @binaryAPI + protected var protectedVar: Int = 2 + @binaryAPI + private[foo] var packagePrivateVar: Int = 2 + + inline def foo: Int = + paramVar = 3 + protectedVar = 3 + packagePrivateVar = 3 + param + paramVal + paramVar + privateVal + protectedVal + packagePrivateVal + protectedVar + packagePrivateVar + +class Bar() extends Foo(3, 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, 4): + @binaryAPI // TODO warn? Not needed because Foo.protectedVal is already @binaryAPI + override protected val protectedVal: Int = 2 + + @binaryAPI + override private[foo] val packagePrivateVal: Int = 2 + + inline def baz: Int = protectedVal + packagePrivateVal + + +class Qux() extends Foo(5, 5, 5): + inline def qux: Int = protectedVal + packagePrivateVal + +def test = + Foo(3, 3, 3).foo + Bar().bar + Baz().baz + Qux().qux + +@binaryAPI given Int = 1 +@binaryAPI given (using Double): Int = 1 + +trait A[T]: + def f: T +@binaryAPI given A[Int] with + def f: Int = 1 +@binaryAPI 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) { + @binaryAPI 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 { + private object Foo: + @binaryAPI private[foo] def x: Int = 1 + inline def f: Int = Foo.x +} +def testFoo = foo.f + +def localTest = + class Foo: + @binaryAPI private[Foo] val a: Int = 1 + @binaryAPI protected val b: Int = 1 + +package traits { + trait Trait: + @binaryAPIAccessor private val myVal = 1 + @binaryAPIAccessor private lazy val myLazyVl = 2 + @binaryAPIAccessor private var myVar = 2 + @binaryAPIAccessor private def myDef = 3 + @binaryAPIAccessor private given myGiven: Int = 4 + + @binaryAPI protected val myVal2 = 1 + @binaryAPI protected lazy val myLazyVl2 = 2 + @binaryAPI protected var myVar2 = 2 + @binaryAPI protected def myDef2 = 3 + @binaryAPI 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 + @binaryAPIAccessor private def bar: Any = ??? + end Foo + + def test = + Baz.foo + (new Baz).foo + val baz = new Baz + baz.foo +} + +package constructors { + class Foo @binaryAPI private[constructors] (x: Int): + @binaryAPI private[constructors] def this(x: Int, y: Int) = this(x + y) + + class Bar @binaryAPI(x: Int): + @binaryAPI 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-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index 644efb54c32e..3353b2647269 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -36,7 +36,6 @@ val experimentalDefinitionInLibrary = Set( "scala.annotation.MainAnnotation$.Parameter", "scala.annotation.MainAnnotation$.ParameterAnnotation", - //// New feature: prototype of new version of @main // This will never be stabilized. When it is ready it should replace the old @main annotation (requires scala.annotation.MainAnnotation). // Needs user feedback. diff --git a/tests/run/i13215.scala b/tests/run/i13215.scala new file mode 100644 index 000000000000..d2055c2490dc --- /dev/null +++ b/tests/run/i13215.scala @@ -0,0 +1,12 @@ +import scala.annotation.binaryAPI + +package foo { + trait Bar: + inline def baz = Baz + + @binaryAPI private[foo] object Baz +} + +@main def Test: Unit = + val bar = new foo.Bar {} + bar.baz