@@ -313,16 +313,32 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
313
313
private def transformStatOrExpr (tree : Tree ): Tree = {
314
314
tree match {
315
315
/* Anonymous function, need to check that it is not used as a SAM for a
316
- * JS type, unless it is js.FunctionN or js.ThisFunctionN .
316
+ * JS type, unless it is a JS function type .
317
317
* See #2921.
318
318
*/
319
319
case tree : Function =>
320
+ // tpeSym is the type of the target SAM type (not the to-be-generated anonymous class)
320
321
val tpeSym = tree.tpe.typeSymbol
321
- if (isJSAny(tpeSym) && ! AllJSFunctionClasses .contains(tpeSym)) {
322
- reporter.error(tree.pos,
323
- " Using an anonymous function as a SAM for the JavaScript " +
324
- " type " + tpeSym.fullNameString + " is not allowed. " +
325
- " Use an anonymous class instead." )
322
+ if (isJSAny(tpeSym)) {
323
+ def reportError (reasonAndExplanation : String ): Unit = {
324
+ reporter.error(tree.pos,
325
+ " Using an anonymous function as a SAM for the JavaScript " +
326
+ s " type ${tpeSym.fullNameString} is not allowed because " +
327
+ reasonAndExplanation)
328
+ }
329
+ if (! tpeSym.isTrait || tpeSym.superClass != JSFunctionClass ) {
330
+ reportError(
331
+ " it is not a trait extending js.Function. " +
332
+ " Use an anonymous class instead." )
333
+ } else if (tpeSym.hasAnnotation(JSNativeAnnotation )) {
334
+ reportError(
335
+ " it is a native JS type. " +
336
+ " It is not possible to directly implement it." )
337
+ } else if (! JSCallingConvention .isCall(samOf(tree.tpe))) {
338
+ reportError(
339
+ " its single abstract method is not named `apply`. " +
340
+ " Use an anonymous class instead." )
341
+ }
326
342
}
327
343
super .transform(tree)
328
344
@@ -550,48 +566,11 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
550
566
551
567
sym.addAnnotation(JSTypeAnnot )
552
568
553
- val isJSLambda = {
554
- /* Under 2.11, sym.isAnonymousFunction does not properly recognize
555
- * anonymous functions here (because they seem to not be marked as
556
- * synthetic).
557
- */
558
- sym.name == tpnme.ANON_FUN_NAME &&
559
- sym.info.member(nme.apply).isSynthetic &&
560
- AllJSFunctionClasses .exists(sym.isSubClass(_))
561
- }
562
-
563
- if (isJSLambda)
564
- transformJSLambdaImplDef(implDef)
565
- else
566
- transformNonLambdaJSImplDef(implDef)
567
- }
568
-
569
- /** Performs checks and rewrites specific to JS lambdas, i.e., anonymous
570
- * classes extending one of the JS function types.
571
- *
572
- * JS lambdas are special because they are completely hijacked by the
573
- * back-end, so although at this phase they look like normal anonymous
574
- * classes, they do not behave like ones.
575
- */
576
- private def transformJSLambdaImplDef (implDef : ImplDef ): Tree = {
577
- /* For the purposes of checking inner members, a JS lambda acts as a JS
578
- * native class owner.
579
- *
580
- * TODO This is probably not right, but historically it has always been
581
- * that way. It should be revisited.
582
- */
583
- enterOwner(OwnerKind .JSNativeClass ) {
584
- super .transform(implDef)
585
- }
586
- }
587
-
588
- /** Performs checks and rewrites for all JS classes, traits and objects
589
- * except JS lambdas.
590
- */
591
- private def transformNonLambdaJSImplDef (implDef : ImplDef ): Tree = {
592
- val sym = moduleToModuleClass(implDef.symbol)
593
569
val isJSNative = sym.hasAnnotation(JSNativeAnnotation )
594
570
571
+ val isJSFunctionSAMInScala211 =
572
+ isScala211 && sym.name == tpnme.ANON_FUN_NAME && sym.superClass == JSFunctionClass
573
+
595
574
// Forbid @EnableReflectiveInstantiation on JS types
596
575
sym.getAnnotation(EnableReflectiveInstantiationAnnotation ).foreach {
597
576
annot =>
@@ -634,6 +613,12 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
634
613
* and similar constructs.
635
614
* This causes the unsoundness filed as #1385.
636
615
*/
616
+ ()
617
+ case SerializableClass if isJSFunctionSAMInScala211 =>
618
+ /* Ignore the scala.Serializable trait that Scala 2.11 adds on all
619
+ * SAM classes when on a JS function SAM.
620
+ */
621
+ ()
637
622
case parentSym =>
638
623
/* This is a Scala class or trait other than AnyRef and Dynamic,
639
624
* which is never valid.
@@ -644,6 +629,23 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
644
629
}
645
630
}
646
631
632
+ // Require that the SAM of a JS function def be `apply` (2.11-only here)
633
+ if (isJSFunctionSAMInScala211) {
634
+ if (! sym.info.decl(nme.apply).filter(JSCallingConvention .isCall(_)).exists) {
635
+ val samType = sym.parentSymbols.find(_ != JSFunctionClass ).getOrElse {
636
+ /* This shouldn't happen, but fall back on this symbol (which has a
637
+ * compiler-generated name) not to crash.
638
+ */
639
+ sym
640
+ }
641
+ reporter.error(implDef.pos,
642
+ " Using an anonymous function as a SAM for the JavaScript type " +
643
+ s " ${samType.fullNameString} is not allowed because its single " +
644
+ " abstract method is not named `apply`. " +
645
+ " Use an anonymous class instead." )
646
+ }
647
+ }
648
+
647
649
// Disallow bracket access / bracket call
648
650
if (jsInterop.isJSBracketAccess(sym)) {
649
651
reporter.error(implDef.pos,
@@ -982,10 +984,43 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
982
984
case JSCallingConvention .Property (_) => // checked above
983
985
case JSCallingConvention .Method (_) => // no checks needed
984
986
985
- case JSCallingConvention .Call =>
986
- reporter.error(sym.pos,
987
- " A non-native JS class cannot declare a method " +
988
- " named `apply` without `@JSName`" )
987
+ case JSCallingConvention .Call if ! sym.isDeferred =>
988
+ /* Concrete `def apply` methods are normally rejected because we
989
+ * cannot implement them in JavaScript. However, we do allow a
990
+ * synthetic `apply` method if it is in a SAM for a JS function
991
+ * type.
992
+ */
993
+ val isJSFunctionSAM = {
994
+ /* Under 2.11, sym.owner.isAnonymousFunction does not properly
995
+ * recognize anonymous functions here (because they seem to not
996
+ * be marked as synthetic).
997
+ */
998
+ sym.isSynthetic &&
999
+ sym.owner.name == tpnme.ANON_FUN_NAME &&
1000
+ sym.owner.superClass == JSFunctionClass
1001
+ }
1002
+ if (! isJSFunctionSAM) {
1003
+ reporter.error(sym.pos,
1004
+ " A non-native JS class cannot declare a concrete method " +
1005
+ " named `apply` without `@JSName`" )
1006
+ }
1007
+
1008
+ case JSCallingConvention .Call => // if sym.isDeferred
1009
+ /* Allow an abstract `def apply` only if the owner is a plausible
1010
+ * JS function SAM trait.
1011
+ */
1012
+ val owner = sym.owner
1013
+ val isPlausibleJSFunctionType = {
1014
+ owner.isTrait &&
1015
+ owner.superClass == JSFunctionClass &&
1016
+ samOf(owner.toTypeConstructor) == sym
1017
+ }
1018
+ if (! isPlausibleJSFunctionType) {
1019
+ reporter.error(sym.pos,
1020
+ " A non-native JS type can only declare an abstract " +
1021
+ " method named `apply` without `@JSName` if it is the " +
1022
+ " SAM of a trait that extends js.Function" )
1023
+ }
989
1024
990
1025
case JSCallingConvention .BracketAccess =>
991
1026
reporter.error(tree.pos,
@@ -1160,14 +1195,11 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
1160
1195
if (sym.isPrimaryConstructor || sym.isValueParameter ||
1161
1196
sym.isParamWithDefault || sym.isAccessor ||
1162
1197
sym.isParamAccessor || sym.isDeferred || sym.isSynthetic ||
1163
- AllJSFunctionClasses .contains(sym.owner) ||
1164
1198
(enclosingOwner is OwnerKind .JSNonNative )) {
1165
1199
/* Ignore (i.e. allow) primary ctor, parameters, default parameter
1166
1200
* getters, accessors, param accessors, abstract members, synthetic
1167
1201
* methods (to avoid double errors with case classes, e.g. generated
1168
- * copy method), js.Functions and js.ThisFunctions (they need abstract
1169
- * methods for SAM treatment), and any member of a non-native JS
1170
- * class/trait.
1202
+ * copy method), and any member of a non-native JS class/trait.
1171
1203
*/
1172
1204
} else if (jsPrimitives.isJavaScriptPrimitive(sym)) {
1173
1205
// No check for primitives. We trust our own standard library.
0 commit comments