scala.scalanative.nscplugin.GenNativeExports.scala Maven / Gradle / Ivy
The newest version!
package scala.scalanative
package nscplugin
import scala.language.implicitConversions
import dotty.tools.dotc.ast.tpd._
import dotty.tools.dotc.core
import core.Contexts._
import core.Symbols._
import core.Flags._
import core.Annotations.*
import dotty.tools.dotc.report
import scala.scalanative.nscplugin.CompilerCompat.SymUtilsCompat.*
import scala.scalanative.util.ScopedVar.scoped
trait GenNativeExports(using Context):
self: NirCodeGen =>
import self.positionsConversions.given
opaque type OwnerSymbol = Symbol
case class ExportedSymbol(symbol: Symbol, defn: nir.Defn.Define)
def isExported(s: Symbol) =
s.hasAnnotation(defnNir.ExportedClass) ||
s.hasAnnotation(defnNir.ExportAccessorsClass)
def genTopLevelExports(td: TypeDef): Seq[nir.Defn] =
given owner: OwnerSymbol = td.symbol
val generated =
for
member <- owner.denot.info.allMembers.map(_.symbol)
if isExported(member)
if !checkAndReportWhenIsExtern(member)
// Externs combined with exports are not allowed, exception is handled in externs
exported <-
if owner.isStaticModule then genModuleMember(member)
else genClassExport(member)
yield exported
generated.groupBy(_.defn.name).foreach {
case (name, exported) if exported.size > 1 =>
val duplicatedSymbols = exported.map(_.symbol)
val showDuplicates = duplicatedSymbols.map(_.show).mkString(" and ")
duplicatedSymbols.foreach { sym =>
report.error(
s"Names of the exported functions needs to be unique, found duplicated generating name $name in $showDuplicates",
sym.srcPos
)
}
case (_, _) => ()
}
generated.map(_.defn)
end genTopLevelExports
private def genClassExport(member: Symbol): Seq[ExportedSymbol] =
// In the future we might implement also class exports, by assuming that given class instance can be passed as an opaque pointer
// In such case extern method would take an opaque pointer to an instance and arguments
report.error(
"Exported members must be statically reachable, definition within class or trait is currently unsupported",
member.srcPos
)
Nil
private def isMethod(s: Symbol): Boolean =
s.isOneOf(Method | Module) && s.isTerm
private def checkAndReportWhenIsExtern(s: Symbol) =
val isExtern = s.isExtern
if isExtern then
report.error(
"Member cannot be defined both exported and extern, use `@extern` for symbols with external definition, and `@exported` for symbols that should be accessible via library",
s.srcPos
)
isExtern
private def checkIsPublic(s: Symbol): Unit =
if !s.isPublic then
report.error(
"Exported members needs to be defined in public scope",
s.srcPos
)
private def checkMethodAnnotation(s: Symbol): Unit =
if !s.hasAnnotation(defnNir.ExportedClass) then
report.error(
"Incorrect annotation found, to export method use `@exported` annotation",
s.srcPos
)
private def checkAccessorAnnotation(s: Symbol): Unit =
if !s.hasAnnotation(defnNir.ExportAccessorsClass) then
report.error(
"Cannot export field, use `@exportAccessors()` annotation to generate external accessors",
s.srcPos
)
private def genModuleMember(
member: Symbol
)(using owner: OwnerSymbol): Seq[ExportedSymbol] =
if isMethod(member) then
checkMethodAnnotation(member)
val name = member
.getAnnotation(defnNir.ExportedClass)
.flatMap(_.argumentConstantString(0))
.map(nir.Sig.Extern(_))
.getOrElse(genExternSig(member))
Seq(genModuleMethod(member, name))
else
checkAccessorAnnotation(member)
member.getAnnotation(defnNir.ExportAccessorsClass) match {
case None => Nil
case Some(annotation) =>
def accessorExternSig(prefix: String) =
val nir.Sig.Extern(id) = genExternSig(member)
nir.Sig.Extern(prefix + id)
def getterName = annotation
.argumentConstantString(0)
.map(nir.Sig.Extern(_))
.getOrElse(accessorExternSig("get_"))
def setterName = annotation
.argumentConstantString(1)
.map(nir.Sig.Extern(_))
.getOrElse(accessorExternSig("set_"))
def externGetter = genModuleMethod(member.getter, getterName)
def externSetter = genModuleMethod(member.setter, setterName)
if member.is(Mutable) then Seq(externGetter, externSetter)
else if !member.getter.exists then
// this can only happend in case of private val
checkIsPublic(member)
Nil
else
if annotation.argument(1).isDefined then
report.warning(
"Unused explicit setter name, annotated field in not mutable it would never use its explicit exported setter name",
member.srcPos
)
Seq(externGetter)
}
end genModuleMember
private def genModuleMethod(member: Symbol, externSig: nir.Sig.Extern)(using
owner: OwnerSymbol
): ExportedSymbol =
checkIsPublic(member)
given nir.SourcePosition = member.span
val originalName = genMethodName(member)
val externName = originalName.top.member(externSig)
val nir.Type.Function(_ +: paramTypes, retType) =
genMethodSig(member): @unchecked
val exportedFunctionType @ nir.Type.Function(
externParamTypes,
externRetType
) = genExternMethodSig(member)
val defn = new nir.Defn.Define(
attrs = nir.Attrs(inlineHint = nir.Attr.NoInline, isExtern = true),
name = externName,
ty = exportedFunctionType,
insts = withFreshExprBuffer { buf ?=>
val fresh = curFresh.get
scoped(
curScopeId := nir.ScopeId.TopLevel,
curUnwindHandler := None,
curMethodThis := None
) {
val entryParams = externParamTypes.map(nir.Val.Local(fresh(), _))
buf.label(fresh(), entryParams)
val boxedParams = paramTypes
.zip(entryParams)
.map(buf.fromExtern(_, _))
val argsp = boxedParams.map(ValTree(_)(member.span))
val res = buf.genApplyModuleMethod(owner, member, argsp)
val unboxedRes = buf.toExtern(externRetType, res)
buf.ret(unboxedRes)
}
buf.toSeq
}
)
ExportedSymbol(member, defn)