Skip to content

Commit fbbc358

Browse files
sjrdHarrisL2
authored andcommitted
Scala.js: Add support for the JS operator **.
This is a forward port of the compiler changes in the Scala.js commit scala-js/scala-js@5bfa254 It is now possible to use `@JSOperator` on a method named `**` in a JS type. In that case, it will translate to the JavaScript `**` operator. This is only valid when emitting ES 2016 or later, as it is not valid ES 2015 code. Without an explicit `@JSOperator` annotation, such a method still defaults to being a method call, to preserve backward compatibility.
1 parent 91e8c0d commit fbbc358

File tree

2 files changed

+65
-30
lines changed

2 files changed

+65
-30
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ final class JSDefinitions()(using Context) {
8585
def JSGlobalScopeAnnot(using Context) = JSGlobalScopeAnnotType.symbol.asClass
8686
@threadUnsafe lazy val JSNameAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSName")
8787
def JSNameAnnot(using Context) = JSNameAnnotType.symbol.asClass
88+
@threadUnsafe lazy val JSOperatorAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSOperator")
89+
def JSOperatorAnnot(using Context) = JSOperatorAnnotType.symbol.asClass
8890
@threadUnsafe lazy val JSFullNameAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSFullName")
8991
def JSFullNameAnnot(using Context) = JSFullNameAnnotType.symbol.asClass
9092
@threadUnsafe lazy val JSBracketAccessAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSBracketAccess")

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

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,16 @@ object JSSymUtils {
7373
lazy val pc = sym.info.paramNamess.map(_.size).sum
7474

7575
sym.name match {
76-
case nme.apply => Call
77-
case JSUnaryOpMethodName(code) if pc == 0 => UnaryOp(code)
78-
case JSBinaryOpMethodName(code) if pc == 1 => BinaryOp(code)
79-
case _ => default
76+
case nme.apply =>
77+
Call
78+
case JSUnaryOpMethodName(code, defaultsToOp)
79+
if (defaultsToOp || sym.hasAnnotation(jsdefn.JSOperatorAnnot)) && pc == 0 =>
80+
UnaryOp(code)
81+
case JSBinaryOpMethodName(code, defaultsToOp)
82+
if (defaultsToOp || sym.hasAnnotation(jsdefn.JSOperatorAnnot)) && pc == 1 =>
83+
BinaryOp(code)
84+
case _ =>
85+
default
8086
}
8187
} else {
8288
default
@@ -230,43 +236,70 @@ object JSSymUtils {
230236
end sjsNeedsField
231237
}
232238

239+
/** Extractor for a `TermName` that *may* be a JS unary operator.
240+
*
241+
* If it may be a JS unary operator, then a method with that name may have
242+
* the `@JSOperator` annotation, and it will be treated as such.
243+
*
244+
* If a method has neither `@JSName` nor `@JSOperator`, then a default is
245+
* chosen. If the `Boolean` value is `true`, the default is to treat the
246+
* method as if it had `@JSOperator`. If it is `false`, the default is *not*
247+
* to treat it as an operator.
248+
*
249+
* Currently, all JS unary operators default to `@JSOperator`.
250+
*/
233251
private object JSUnaryOpMethodName {
234252
private val map = Map(
235-
nme.UNARY_+ -> js.JSUnaryOp.+,
236-
nme.UNARY_- -> js.JSUnaryOp.-,
237-
nme.UNARY_~ -> js.JSUnaryOp.~,
238-
nme.UNARY_! -> js.JSUnaryOp.!
253+
nme.UNARY_+ -> (js.JSUnaryOp.+, true),
254+
nme.UNARY_- -> (js.JSUnaryOp.-, true),
255+
nme.UNARY_~ -> (js.JSUnaryOp.~, true),
256+
nme.UNARY_! -> (js.JSUnaryOp.!, true),
239257
)
240258

241-
def unapply(name: TermName): Option[js.JSUnaryOp.Code] =
259+
def unapply(name: TermName): Option[(js.JSUnaryOp.Code, Boolean)] =
242260
map.get(name)
243261
}
244262

263+
/** Extractor for a `TermName` that *may* be a JS binary operator.
264+
*
265+
* If it may be a JS binary operator, then a method with that name may have
266+
* the `@JSOperator` annotation, and it will be treated as such.
267+
*
268+
* If a method has neither `@JSName` nor `@JSOperator`, then a default is
269+
* chosen. If the `Boolean` value is `true`, the default is to treat the
270+
* method as if it had `@JSOperator`. If it is `false`, the default is *not*
271+
* to treat it as an operator.
272+
*
273+
* Most JS binary operators default to `@JSOperator`. Currently, the only
274+
* exception is `**`, for backward compatibility reasons.
275+
*/
245276
private object JSBinaryOpMethodName {
246277
private val map = Map(
247-
nme.ADD -> js.JSBinaryOp.+,
248-
nme.SUB -> js.JSBinaryOp.-,
249-
nme.MUL -> js.JSBinaryOp.*,
250-
nme.DIV -> js.JSBinaryOp./,
251-
nme.MOD -> js.JSBinaryOp.%,
252-
253-
nme.LSL -> js.JSBinaryOp.<<,
254-
nme.ASR -> js.JSBinaryOp.>>,
255-
nme.LSR -> js.JSBinaryOp.>>>,
256-
nme.OR -> js.JSBinaryOp.|,
257-
nme.AND -> js.JSBinaryOp.&,
258-
nme.XOR -> js.JSBinaryOp.^,
259-
260-
nme.LT -> js.JSBinaryOp.<,
261-
nme.LE -> js.JSBinaryOp.<=,
262-
nme.GT -> js.JSBinaryOp.>,
263-
nme.GE -> js.JSBinaryOp.>=,
264-
265-
nme.ZAND -> js.JSBinaryOp.&&,
266-
nme.ZOR -> js.JSBinaryOp.||
278+
nme.ADD -> (js.JSBinaryOp.+, true),
279+
nme.SUB -> (js.JSBinaryOp.-, true),
280+
nme.MUL -> (js.JSBinaryOp.*, true),
281+
nme.DIV -> (js.JSBinaryOp./, true),
282+
nme.MOD -> (js.JSBinaryOp.%, true),
283+
284+
nme.LSL -> (js.JSBinaryOp.<<, true),
285+
nme.ASR -> (js.JSBinaryOp.>>, true),
286+
nme.LSR -> (js.JSBinaryOp.>>>, true),
287+
nme.OR -> (js.JSBinaryOp.|, true),
288+
nme.AND -> (js.JSBinaryOp.&, true),
289+
nme.XOR -> (js.JSBinaryOp.^, true),
290+
291+
nme.LT -> (js.JSBinaryOp.<, true),
292+
nme.LE -> (js.JSBinaryOp.<=, true),
293+
nme.GT -> (js.JSBinaryOp.>, true),
294+
nme.GE -> (js.JSBinaryOp.>=, true),
295+
296+
nme.ZAND -> (js.JSBinaryOp.&&, true),
297+
nme.ZOR -> (js.JSBinaryOp.||, true),
298+
299+
termName("**") -> (js.JSBinaryOp.**, false),
267300
)
268301

269-
def unapply(name: TermName): Option[js.JSBinaryOp.Code] =
302+
def unapply(name: TermName): Option[(js.JSBinaryOp.Code, Boolean)] =
270303
map.get(name)
271304
}
272305
}

0 commit comments

Comments
 (0)