Skip to content

Commit 1deaede

Browse files
committed
Scala.js: Unify handling of JS calling conventions
Forward port of the upstream commit scala-js/scala-js@b6ef9a6
1 parent ff402a0 commit 1deaede

File tree

8 files changed

+290
-224
lines changed

8 files changed

+290
-224
lines changed

compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala

Lines changed: 53 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2652,7 +2652,11 @@ class JSCodeGen()(using genCtx: Context) {
26522652
jsSuperClassValue: Option[js.Tree] = None)(
26532653
implicit pos: SourcePosition): js.Tree = {
26542654

2655-
def noSpread = !args.exists(_.isInstanceOf[js.JSSpread])
2655+
def argsNoSpread: List[js.Tree] = {
2656+
assert(!args.exists(_.isInstanceOf[js.JSSpread]), s"Unexpected spread at $pos")
2657+
args.asInstanceOf[List[js.Tree]]
2658+
}
2659+
26562660
val argc = args.size // meaningful only for methods that don't have varargs
26572661

26582662
def requireNotSuper(): Unit = {
@@ -2663,74 +2667,72 @@ class JSCodeGen()(using genCtx: Context) {
26632667
def requireNotSpread(arg: js.TreeOrJSSpread): js.Tree =
26642668
arg.asInstanceOf[js.Tree]
26652669

2666-
def hasExplicitJSEncoding = {
2667-
sym.hasAnnotation(jsdefn.JSNameAnnot) ||
2668-
sym.hasAnnotation(jsdefn.JSBracketAccessAnnot) ||
2669-
sym.hasAnnotation(jsdefn.JSBracketCallAnnot)
2670+
def genSuperReference(propName: js.Tree): js.AssignLhs = {
2671+
jsSuperClassValue.fold[js.AssignLhs] {
2672+
genJSSelectOrGlobalRef(receiver, propName)
2673+
} { superClassValue =>
2674+
js.JSSuperSelect(superClassValue, ruleOutGlobalScope(receiver), propName)
2675+
}
2676+
}
2677+
2678+
def genSelectGet(propName: js.Tree): js.Tree =
2679+
genSuperReference(propName)
2680+
2681+
def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree =
2682+
js.Assign(genSuperReference(propName), value)
2683+
2684+
def genCall(methodName: js.Tree, args: List[js.TreeOrJSSpread]): js.Tree = {
2685+
jsSuperClassValue.fold[js.Tree] {
2686+
genJSMethodApplyOrGlobalRefApply(receiver, methodName, args)
2687+
} { superClassValue =>
2688+
js.JSSuperMethodCall(superClassValue, ruleOutGlobalScope(receiver), methodName, args)
2689+
}
26702690
}
26712691

2672-
val boxedResult = sym.name match {
2673-
case JSUnaryOpMethodName(code) if argc == 0 =>
2692+
val boxedResult = sym.jsCallingConvention match {
2693+
case JSCallingConvention.UnaryOp(code) =>
26742694
requireNotSuper()
2695+
assert(argc == 0, s"bad argument count ($argc) for unary op at $pos")
26752696
js.JSUnaryOp(code, ruleOutGlobalScope(receiver))
26762697

2677-
case JSBinaryOpMethodName(code) if argc == 1 =>
2698+
case JSCallingConvention.BinaryOp(code) =>
26782699
requireNotSuper()
2700+
assert(argc == 1, s"bad argument count ($argc) for binary op at $pos")
26792701
js.JSBinaryOp(code, ruleOutGlobalScope(receiver), requireNotSpread(args.head))
26802702

2681-
case nme.apply if !hasExplicitJSEncoding =>
2703+
case JSCallingConvention.Call =>
26822704
requireNotSuper()
26832705
if (jsdefn.isJSThisFunctionClass(sym.owner))
26842706
js.JSMethodApply(ruleOutGlobalScope(receiver), js.StringLiteral("call"), args)
26852707
else
26862708
js.JSFunctionApply(ruleOutGlobalScope(receiver), args)
26872709

2688-
case _ =>
2689-
def jsFunName = genExpr(jsNameOf(sym))
2690-
2691-
def genSuperReference(propName: js.Tree): js.AssignLhs = {
2692-
jsSuperClassValue.fold[js.AssignLhs] {
2693-
genJSSelectOrGlobalRef(receiver, propName)
2694-
} { superClassValue =>
2695-
js.JSSuperSelect(superClassValue, ruleOutGlobalScope(receiver), propName)
2696-
}
2710+
case JSCallingConvention.Property(jsName) =>
2711+
argsNoSpread match {
2712+
case Nil =>
2713+
genSelectGet(genExpr(jsName))
2714+
case value :: Nil =>
2715+
genSelectSet(genExpr(jsName), value)
2716+
case _ =>
2717+
throw new AssertionError(s"property methods should have 0 or 1 non-varargs arguments at $pos")
26972718
}
26982719

2699-
def genSelectGet(propName: js.Tree): js.Tree =
2700-
genSuperReference(propName)
2701-
2702-
def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree =
2703-
js.Assign(genSuperReference(propName), value)
2704-
2705-
def genCall(methodName: js.Tree, args: List[js.TreeOrJSSpread]): js.Tree = {
2706-
jsSuperClassValue.fold[js.Tree] {
2707-
genJSMethodApplyOrGlobalRefApply(receiver, methodName, args)
2708-
} { superClassValue =>
2709-
js.JSSuperMethodCall(superClassValue, ruleOutGlobalScope(receiver), methodName, args)
2710-
}
2720+
case JSCallingConvention.BracketAccess =>
2721+
argsNoSpread match {
2722+
case keyArg :: Nil =>
2723+
genSelectGet(keyArg)
2724+
case keyArg :: valueArg :: Nil =>
2725+
genSelectSet(keyArg, valueArg)
2726+
case _ =>
2727+
throw new AssertionError(s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments at $pos")
27112728
}
27122729

2713-
if (sym.isJSGetter) {
2714-
assert(noSpread && argc == 0)
2715-
genSelectGet(jsFunName)
2716-
} else if (sym.isJSSetter) {
2717-
assert(noSpread && argc == 1)
2718-
genSelectSet(jsFunName, requireNotSpread(args.head))
2719-
} else if (sym.isJSBracketAccess) {
2720-
assert(noSpread && (argc == 1 || argc == 2),
2721-
s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments")
2722-
(args: @unchecked) match {
2723-
case List(keyArg) =>
2724-
genSelectGet(requireNotSpread(keyArg))
2725-
case List(keyArg, valueArg) =>
2726-
genSelectSet(requireNotSpread(keyArg), requireNotSpread(valueArg))
2727-
}
2728-
} else if (sym.isJSBracketCall) {
2729-
val (methodName, actualArgs) = extractFirstArg(args)
2730-
genCall(methodName, actualArgs)
2731-
} else {
2732-
genCall(jsFunName, args)
2733-
}
2730+
case JSCallingConvention.BracketCall =>
2731+
val (methodName, actualArgs) = extractFirstArg(args)
2732+
genCall(methodName, actualArgs)
2733+
2734+
case JSCallingConvention.Method(jsName) =>
2735+
genCall(genExpr(jsName), args)
27342736
}
27352737

27362738
if (isStat) {
@@ -2743,46 +2745,6 @@ class JSCodeGen()(using genCtx: Context) {
27432745
}
27442746
}
27452747

2746-
private object JSUnaryOpMethodName {
2747-
private val map = Map(
2748-
nme.UNARY_+ -> js.JSUnaryOp.+,
2749-
nme.UNARY_- -> js.JSUnaryOp.-,
2750-
nme.UNARY_~ -> js.JSUnaryOp.~,
2751-
nme.UNARY_! -> js.JSUnaryOp.!
2752-
)
2753-
2754-
def unapply(name: TermName): Option[js.JSUnaryOp.Code] =
2755-
map.get(name)
2756-
}
2757-
2758-
private object JSBinaryOpMethodName {
2759-
private val map = Map(
2760-
nme.ADD -> js.JSBinaryOp.+,
2761-
nme.SUB -> js.JSBinaryOp.-,
2762-
nme.MUL -> js.JSBinaryOp.*,
2763-
nme.DIV -> js.JSBinaryOp./,
2764-
nme.MOD -> js.JSBinaryOp.%,
2765-
2766-
nme.LSL -> js.JSBinaryOp.<<,
2767-
nme.ASR -> js.JSBinaryOp.>>,
2768-
nme.LSR -> js.JSBinaryOp.>>>,
2769-
nme.OR -> js.JSBinaryOp.|,
2770-
nme.AND -> js.JSBinaryOp.&,
2771-
nme.XOR -> js.JSBinaryOp.^,
2772-
2773-
nme.LT -> js.JSBinaryOp.<,
2774-
nme.LE -> js.JSBinaryOp.<=,
2775-
nme.GT -> js.JSBinaryOp.>,
2776-
nme.GE -> js.JSBinaryOp.>=,
2777-
2778-
nme.ZAND -> js.JSBinaryOp.&&,
2779-
nme.ZOR -> js.JSBinaryOp.||
2780-
)
2781-
2782-
def unapply(name: TermName): Option[js.JSBinaryOp.Code] =
2783-
map.get(name)
2784-
}
2785-
27862748
/** Extract the first argument in a list of actual arguments.
27872749
*
27882750
* This is nothing else than decomposing into head and tail, except that

compiler/src/dotty/tools/dotc/transform/sjs/JSSymUtils.scala

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import Types._
2424

2525
import dotty.tools.backend.sjs.JSDefinitions.jsdefn
2626

27+
import org.scalajs.ir.{Trees => js}
28+
2729
/** Additional extensions for `Symbol`s that are only relevant for Scala.js. */
2830
object JSSymUtils {
2931
/** The result type for `sym.jsName`.
@@ -41,6 +43,55 @@ object JSSymUtils {
4143
}
4244
}
4345

46+
enum JSCallingConvention {
47+
case Call, BracketAccess, BracketCall
48+
case Method(name: JSName)
49+
case Property(name: JSName)
50+
case UnaryOp(code: js.JSUnaryOp.Code)
51+
case BinaryOp(code: js.JSBinaryOp.Code)
52+
53+
def displayName(using Context): String = this match {
54+
case Call => "function application"
55+
case BracketAccess => "bracket access"
56+
case BracketCall => "bracket call"
57+
case Method(name) => "method '" + name.displayName + "'"
58+
case Property(name) => "property '" + name.displayName + "'"
59+
case UnaryOp(code) => "unary operator"
60+
case BinaryOp(code) => "binary operator"
61+
}
62+
}
63+
64+
object JSCallingConvention {
65+
def of(sym: Symbol)(using Context): JSCallingConvention = {
66+
assert(sym.isTerm, s"got non-term symbol: $sym")
67+
68+
if (isJSBracketAccess(sym)) {
69+
BracketAccess
70+
} else if (isJSBracketCall(sym)) {
71+
BracketCall
72+
} else {
73+
def default = {
74+
val jsName = sym.jsName
75+
if (sym.isJSProperty) Property(jsName)
76+
else Method(jsName)
77+
}
78+
79+
if (!sym.hasAnnotation(jsdefn.JSNameAnnot)) {
80+
lazy val pc = sym.info.paramNamess.map(_.size).sum
81+
82+
sym.name match {
83+
case nme.apply => Call
84+
case JSUnaryOpMethodName(code) if pc == 0 => UnaryOp(code)
85+
case JSBinaryOpMethodName(code) if pc == 1 => BinaryOp(code)
86+
case _ => default
87+
}
88+
} else {
89+
default
90+
}
91+
}
92+
}
93+
}
94+
4495
extension (sym: Symbol) {
4596
/** Is this symbol a JavaScript type? */
4697
def isJSType(using Context): Boolean =
@@ -66,10 +117,9 @@ object JSSymUtils {
66117

67118
/** Should this symbol be translated into a JS getter? */
68119
def isJSGetter(using Context): Boolean = {
69-
sym.is(Module) || (
70-
sym.is(Method)
71-
&& sym.info.firstParamTypes.isEmpty
72-
&& atPhaseNoLater(erasurePhase)(sym.info.isParameterless))
120+
sym.is(Module)
121+
|| !sym.is(Method)
122+
|| (sym.info.firstParamTypes.isEmpty && atPhaseNoLater(erasurePhase)(sym.info.isParameterless))
73123
}
74124

75125
/** Should this symbol be translated into a JS setter? */
@@ -108,6 +158,9 @@ object JSSymUtils {
108158
}
109159
}
110160

161+
def jsCallingConvention(using Context): JSCallingConvention =
162+
JSCallingConvention.of(sym)
163+
111164
/** Gets the unqualified JS name of the symbol.
112165
*
113166
* If it is not explicitly specified with an `@JSName` annotation, the
@@ -128,4 +181,44 @@ object JSSymUtils {
128181
if (sym.isTerm) sym.asTerm.name.unexpandedName.getterName.toString()
129182
else sym.name.unexpandedName.stripModuleClassSuffix.toString()
130183
}
184+
185+
private object JSUnaryOpMethodName {
186+
private val map = Map(
187+
nme.UNARY_+ -> js.JSUnaryOp.+,
188+
nme.UNARY_- -> js.JSUnaryOp.-,
189+
nme.UNARY_~ -> js.JSUnaryOp.~,
190+
nme.UNARY_! -> js.JSUnaryOp.!
191+
)
192+
193+
def unapply(name: TermName): Option[js.JSUnaryOp.Code] =
194+
map.get(name)
195+
}
196+
197+
private object JSBinaryOpMethodName {
198+
private val map = Map(
199+
nme.ADD -> js.JSBinaryOp.+,
200+
nme.SUB -> js.JSBinaryOp.-,
201+
nme.MUL -> js.JSBinaryOp.*,
202+
nme.DIV -> js.JSBinaryOp./,
203+
nme.MOD -> js.JSBinaryOp.%,
204+
205+
nme.LSL -> js.JSBinaryOp.<<,
206+
nme.ASR -> js.JSBinaryOp.>>,
207+
nme.LSR -> js.JSBinaryOp.>>>,
208+
nme.OR -> js.JSBinaryOp.|,
209+
nme.AND -> js.JSBinaryOp.&,
210+
nme.XOR -> js.JSBinaryOp.^,
211+
212+
nme.LT -> js.JSBinaryOp.<,
213+
nme.LE -> js.JSBinaryOp.<=,
214+
nme.GT -> js.JSBinaryOp.>,
215+
nme.GE -> js.JSBinaryOp.>=,
216+
217+
nme.ZAND -> js.JSBinaryOp.&&,
218+
nme.ZOR -> js.JSBinaryOp.||
219+
)
220+
221+
def unapply(name: TermName): Option[js.JSBinaryOp.Code] =
222+
map.get(name)
223+
}
131224
}

compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ object PrepJSExports {
298298
case _: ExportDestination.TopLevel =>
299299
if (sym.is(Lazy))
300300
report.error("You may not export a lazy val to the top level", exportPos)
301-
else if (!sym.is(Accessor) && sym.isTerm && sym.isJSProperty)
301+
else if (sym.is(Method, butNot = Accessor) && sym.isJSProperty)
302302
report.error("You may not export a getter or a setter to the top level", exportPos)
303303

304304
/* Disallow non-static methods.

0 commit comments

Comments
 (0)