summaryrefslogtreecommitdiff
path: root/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala
blob: 437576ac970e6854622820a68a8626625891a4b8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
/* Scala.js compiler
 * Copyright 2013 LAMP/EPFL
 * @author Tobias Schlatter
 */

package scala.scalajs.compiler

import scala.tools.nsc
import nsc._

import scala.collection.immutable.ListMap
import scala.collection.mutable

/** Prepares classes extending js.Any for JavaScript interop
 *
 * This phase does:
 * - Sanity checks for js.Any hierarchy
 * - Annotate subclasses of js.Any to be treated specially
 * - Rewrite calls to scala.Enumeration.Value (include name string)
 * - Create JSExport methods: Dummy methods that are propagated
 *   through the whole compiler chain to mark exports. This allows
 *   exports to have the same semantics than methods.
 *
 * @author Tobias Schlatter
 */
abstract class PrepJSInterop extends plugins.PluginComponent
                                with PrepJSExports
                                with transform.Transform
                                with Compat210Component {
  val jsAddons: JSGlobalAddons {
    val global: PrepJSInterop.this.global.type
  }

  val scalaJSOpts: ScalaJSOptions

  import global._
  import jsAddons._
  import definitions._
  import rootMirror._
  import jsDefinitions._

  val phaseName = "jsinterop"

  override def newPhase(p: nsc.Phase) = new JSInteropPhase(p)
  class JSInteropPhase(prev: nsc.Phase) extends Phase(prev) {
    override def name = phaseName
    override def description = "Prepare ASTs for JavaScript interop"
    override def run(): Unit = {
      jsPrimitives.initPrepJSPrimitives()
      super.run()
    }
  }

  override protected def newTransformer(unit: CompilationUnit) =
    new JSInteropTransformer(unit)

  private object jsnme {
    val hasNext  = newTermName("hasNext")
    val next     = newTermName("next")
    val nextName = newTermName("nextName")
    val x        = newTermName("x")
    val Value    = newTermName("Value")
    val Val      = newTermName("Val")
  }

  private object jstpnme {
    val scala_ = newTypeName("scala") // not defined in 2.10's tpnme
  }

  class JSInteropTransformer(unit: CompilationUnit) extends Transformer {

    // Force evaluation of JSDynamicLiteral: Strangely, we are unable to find
    // nested objects in the JSCode phase (probably after flatten).
    // Therefore we force the symbol of js.Dynamic.literal here in order to
    // have access to it in JSCode.
    JSDynamicLiteral

    var inJSAnyMod = false
    var inJSAnyCls = false
    var inScalaCls = false
    /** are we inside a subclass of scala.Enumeration */
    var inScalaEnum = false
    /** are we inside the implementation of scala.Enumeration? */
    var inEnumImpl = false

    def jsAnyClassOnly = !inJSAnyCls && allowJSAny
    def allowImplDef   = !inJSAnyCls && !inJSAnyMod
    def allowJSAny     = !inScalaCls
    def inJSAny        = inJSAnyMod || inJSAnyCls

    /** DefDefs in class templates that export methods to JavaScript */
    val exporters = mutable.Map.empty[Symbol, mutable.ListBuffer[Tree]]

    override def transform(tree: Tree): Tree = postTransform { tree match {
      // Catch special case of ClassDef in ModuleDef
      case cldef: ClassDef if jsAnyClassOnly && isJSAny(cldef) =>
        transformJSAny(cldef)

      // Catch forbidden implDefs
      case idef: ImplDef if !allowImplDef =>
        reporter.error(idef.pos, "Traits, classes and objects extending " +
            "js.Any may not have inner traits, classes or objects")
        super.transform(tree)

      // Handle js.Anys
      case idef: ImplDef if isJSAny(idef) =>
        transformJSAny(idef)

      // Catch the definition of scala.Enumeration itself
      case cldef: ClassDef if cldef.symbol == ScalaEnumClass =>
        enterEnumImpl { super.transform(cldef) }

      // Catch Scala Enumerations to transform calls to scala.Enumeration.Value
      case cldef: ClassDef if isScalaEnum(cldef) =>
        enterScalaCls {
          enterScalaEnum {
            super.transform(cldef)
          }
        }
      case idef: ImplDef if isScalaEnum(idef) =>
        enterScalaEnum { super.transform(idef) }

      // Catch (Scala) ClassDefs to forbid js.Anys
      case cldef: ClassDef =>
        enterScalaCls { super.transform(cldef) }

      // Catch ValorDefDef in js.Any
      case vddef: ValOrDefDef if inJSAny =>
        transformValOrDefDefInRawJSType(vddef)

      // Catch ValDefs in enumerations with simple calls to Value
      case ValDef(mods, name, tpt, ScalaEnumValue.NoName(optPar)) if inScalaEnum =>
        val nrhs = ScalaEnumValName(tree.symbol.owner, tree.symbol, optPar)
        treeCopy.ValDef(tree, mods, name, transform(tpt), nrhs)

      // Catch Select on Enumeration.Value we couldn't transform but need to
      // we ignore the implementation of scala.Enumeration itself
      case ScalaEnumValue.NoName(_) if !inEnumImpl =>
        reporter.warning(tree.pos,
                     """Couldn't transform call to Enumeration.Value.
                       |The resulting program is unlikely to function properly as this
                       |operation requires reflection.""".stripMargin)
        super.transform(tree)

      case ScalaEnumValue.NullName() if !inEnumImpl =>
        reporter.warning(tree.pos,
                     """Passing null as name to Enumeration.Value
                       |requires reflection at runtime. The resulting
                       |program is unlikely to function properly.""".stripMargin)
        super.transform(tree)

      case ScalaEnumVal.NoName(_) if !inEnumImpl =>
        reporter.warning(tree.pos,
                     """Calls to the non-string constructors of Enumeration.Val
                       |require reflection at runtime. The resulting
                       |program is unlikely to function properly.""".stripMargin)
        super.transform(tree)

      case ScalaEnumVal.NullName() if !inEnumImpl =>
        reporter.warning(tree.pos,
                     """Passing null as name to a constructor of Enumeration.Val
                       |requires reflection at runtime. The resulting
                       |program is unlikely to function properly.""".stripMargin)
        super.transform(tree)

      // Catch calls to Predef.classOf[T]. These should NEVER reach this phase
      // but unfortunately do. In normal cases, the typer phase replaces these
      // calls by a literal constant of the given type. However, when we compile
      // the scala library itself and Predef.scala is in the sources, this does
      // not happen.
      //
      // The trees reach this phase under the form:
      //
      //   scala.this.Predef.classOf[T]
      //
      // If we encounter such a tree, depending on the plugin options, we fail
      // here or silently fix those calls.
      case TypeApply(
          classOfTree @ Select(Select(This(jstpnme.scala_), nme.Predef), nme.classOf),
          List(tpeArg)) =>
        if (scalaJSOpts.fixClassOf) {
          // Replace call by literal constant containing type
          if (typer.checkClassType(tpeArg)) {
            typer.typed { Literal(Constant(tpeArg.tpe.dealias.widen)) }
          } else {
            reporter.error(tpeArg.pos, s"Type ${tpeArg} is not a class type")
            EmptyTree
          }
        } else {
          reporter.error(classOfTree.pos,
              """This classOf resulted in an unresolved classOf in the jscode
                |phase. This is most likely a bug in the Scala compiler. ScalaJS
                |is probably able to work around this bug. Enable the workaround
                |by passing the fixClassOf option to the plugin.""".stripMargin)
          EmptyTree
        }

      // Exporter generation
      case ddef: DefDef =>
        // Generate exporters for this ddef if required
        exporters.getOrElseUpdate(ddef.symbol.owner,
            mutable.ListBuffer.empty) ++= genExportMember(ddef)

        super.transform(tree)

      // Module export sanity check (export generated in JSCode phase)
      case modDef: ModuleDef =>
        val sym = modDef.symbol

        def condErr(msg: String) = {
          for (exp <- jsInterop.exportsOf(sym)) {
            reporter.error(exp.pos, msg)
          }
        }

        if (!hasLegalExportVisibility(sym))
          condErr("You may only export public and protected objects")
        else if (sym.isLocalToBlock)
          condErr("You may not export a local object")
        else if (!sym.owner.hasPackageFlag)
          condErr("You may not export a nested object")

        super.transform(modDef)

      // Fix for issue with calls to js.Dynamic.x()
      // Rewrite (obj: js.Dynamic).x(...) to obj.applyDynamic("x")(...)
      case Select(Select(trg, jsnme.x), nme.apply) if isJSDynamic(trg) =>
        val newTree = atPos(tree.pos) {
          Apply(
              Select(super.transform(trg), newTermName("applyDynamic")),
              List(Literal(Constant("x")))
          )
        }
        typer.typed(newTree, Mode.FUNmode, tree.tpe)


      // Fix for issue with calls to js.Dynamic.x()
      // Rewrite (obj: js.Dynamic).x to obj.selectDynamic("x")
      case Select(trg, jsnme.x) if isJSDynamic(trg) =>
        val newTree = atPos(tree.pos) {
          Apply(
              Select(super.transform(trg), newTermName("selectDynamic")),
              List(Literal(Constant("x")))
          )
        }
        typer.typed(newTree, Mode.FUNmode, tree.tpe)

      case _ => super.transform(tree)
    } }

    private def postTransform(tree: Tree) = tree match {
      case Template(parents, self, body) =>
        val clsSym = tree.symbol.owner
        val exports = exporters.get(clsSym).toIterable.flatten
        // Add exports to the template
        treeCopy.Template(tree, parents, self, body ++ exports)

      case memDef: MemberDef =>
        val sym = memDef.symbol
        if (sym.isLocalToBlock && !sym.owner.isCaseApplyOrUnapply) {
          // We exclude case class apply (and unapply) to work around SI-8826
          for (exp <- jsInterop.exportsOf(sym)) {
            val msg = {
              val base = "You may not export a local definition"
              if (sym.owner.isPrimaryConstructor)
                base + ". To export a (case) class field, use the " +
                "meta-annotation scala.annotation.meta.field like this: " +
                "@(JSExport @field)."
              else
                base
            }
            reporter.error(exp.pos, msg)
          }
        }
        memDef

      case _ => tree
    }

    /**
     * Performs checks and rewrites specific to classes / objects extending
     * js.Any
     */
    private def transformJSAny(implDef: ImplDef) = {
      val sym = implDef.symbol

      lazy val badParent = sym.info.parents.find(t => !(t <:< JSAnyClass.tpe))
      val inScalaJSJSPackage =
        sym.enclosingPackage == ScalaJSJSPackage ||
        sym.enclosingPackage == ScalaJSJSPrimPackage

      implDef match {
        // Check that we do not have a case modifier
        case _ if implDef.mods.hasFlag(Flag.CASE) =>
          reporter.error(implDef.pos, "Classes and objects extending " +
              "js.Any may not have a case modifier")

        // Check that we do not extends a trait that does not extends js.Any
        case _ if !inScalaJSJSPackage && !badParent.isEmpty &&
          !isJSLambda(sym) =>
          val badName = badParent.get.typeSymbol.fullName
          reporter.error(implDef.pos, s"${sym.nameString} extends ${badName} " +
              "which does not extend js.Any.")

        // Check that we are not an anonymous class
        case cldef: ClassDef
          if cldef.symbol.isAnonymousClass && !isJSLambda(sym) =>
          reporter.error(implDef.pos, "Anonymous classes may not " +
              "extend js.Any")

        // Check if we may have a js.Any here
        case cldef: ClassDef if !allowJSAny && !jsAnyClassOnly &&
          !isJSLambda(sym) =>
          reporter.error(implDef.pos, "Classes extending js.Any may not be " +
              "defined inside a class or trait")

        case _: ModuleDef if !allowJSAny =>
          reporter.error(implDef.pos, "Objects extending js.Any may not be " +
              "defined inside a class or trait")

        case _ if sym.isLocalToBlock && !isJSLambda(sym) =>
          reporter.error(implDef.pos, "Local classes and objects may not " +
              "extend js.Any")

        // Check that this is not a class extending js.GlobalScope
        case _: ClassDef if isJSGlobalScope(implDef) &&
          implDef.symbol != JSGlobalScopeClass =>
          reporter.error(implDef.pos, "Only objects may extend js.GlobalScope")

        case _ =>
          // We cannot use sym directly, since the symbol
          // of a module is not its type's symbol but the value it declares
          val tSym = sym.tpe.typeSymbol

          tSym.setAnnotations(rawJSAnnot :: sym.annotations)

      }

      if (implDef.isInstanceOf[ModuleDef])
        enterJSAnyMod { super.transform(implDef) }
      else
        enterJSAnyCls { super.transform(implDef) }
    }

    /** Verify a ValOrDefDef inside a js.Any */
    private def transformValOrDefDefInRawJSType(tree: ValOrDefDef) = {
      val sym = tree.symbol

      val exports = jsInterop.exportsOf(sym)

      if (exports.nonEmpty) {
        val memType = if (sym.isConstructor) "constructor" else "method"
        reporter.error(exports.head.pos,
            s"You may not export a $memType of a subclass of js.Any")
      }

      if (isNonJSScalaSetter(sym)) {
        // Forbid setters with non-unit return type
        reporter.error(tree.pos, "Setters that do not return Unit are " +
            "not allowed in types extending js.Any")
      }

      if (sym.hasAnnotation(NativeAttr)) {
        // Native methods are not allowed
        reporter.error(tree.pos, "Methods in a js.Any may not be @native")
      }

      for {
        annot <- sym.getAnnotation(JSNameAnnotation)
        if annot.stringArg(0).isEmpty
      } {
        reporter.error(annot.pos,
          "The argument to JSName must be a literal string")
      }

      if (sym.isPrimaryConstructor || sym.isValueParameter ||
          sym.isParamWithDefault || sym.isAccessor && !sym.isDeferred ||
          sym.isParamAccessor || sym.isSynthetic ||
          AllJSFunctionClasses.contains(sym.owner)) {
        /* Ignore (i.e. allow) primary ctor, parameters, default parameter
         * getters, accessors, param accessors, synthetic methods (to avoid
         * double errors with case classes, e.g. generated copy method) and
         * js.Functions and js.ThisFunctions (they need abstract methods for SAM
         * treatment.
         */
      } else if (jsPrimitives.isJavaScriptPrimitive(sym)) {
        // Force rhs of a primitive to be `sys.error("stub")` except for the
        // js.native primitive which displays an elaborate error message
        if (sym != JSPackage_native) {
          tree.rhs match {
            case Apply(trg, Literal(Constant("stub")) :: Nil)
                if trg.symbol == definitions.Sys_error =>
            case _ =>
              reporter.error(tree.pos,
                  "The body of a primitive must be `sys.error(\"stub\")`.")
          }
        }
      } else if (sym.isConstructor) {
        // Force secondary ctor to have only a call to the primary ctor inside
        tree.rhs match {
          case Block(List(Apply(trg, _)), Literal(Constant(())))
              if trg.symbol.isPrimaryConstructor &&
                 trg.symbol.owner == sym.owner =>
            // everything is fine here
          case _ =>
            reporter.error(tree.pos, "A secondary constructor of a class " +
                "extending js.Any may only call the primary constructor")
        }
      } else {
        // Check that the tree's body is either empty or calls js.native
        tree.rhs match {
          case sel: Select if sel.symbol == JSPackage_native =>
          case _ =>
            val pos = if (tree.rhs != EmptyTree) tree.rhs.pos else tree.pos
            reporter.warning(pos, "Members of traits, classes and objects " +
              "extending js.Any may only contain members that call js.native. " +
              "This will be enforced in 1.0.")
        }

        if (sym.tpe.resultType.typeSymbol == NothingClass &&
            tree.tpt.asInstanceOf[TypeTree].original == null) {
          // Warn if resultType is Nothing and not ascribed
          val name = sym.name.decoded.trim
          reporter.warning(tree.pos, s"The type of $name got inferred " +
              "as Nothing. To suppress this warning, explicitly ascribe " +
              "the type.")
        }
      }

      super.transform(tree)
    }

    private def enterJSAnyCls[T](body: =>T) = {
      val old = inJSAnyCls
      inJSAnyCls = true
      val res = body
      inJSAnyCls = old
      res
    }

    private def enterJSAnyMod[T](body: =>T) = {
      val old = inJSAnyMod
      inJSAnyMod = true
      val res = body
      inJSAnyMod = old
      res
    }

    private def enterScalaCls[T](body: =>T) = {
      val old = inScalaCls
      inScalaCls = true
      val res = body
      inScalaCls = old
      res
    }

    private def enterScalaEnum[T](body: =>T) = {
      val old = inScalaEnum
      inScalaEnum = true
      val res = body
      inScalaEnum = old
      res
    }

    private def enterEnumImpl[T](body: =>T) = {
      val old = inEnumImpl
      inEnumImpl = true
      val res = body
      inEnumImpl = old
      res
    }

  }

  def isJSAny(sym: Symbol): Boolean =
    sym.tpe.typeSymbol isSubClass JSAnyClass

  private def isJSAny(implDef: ImplDef): Boolean = isJSAny(implDef.symbol)

  private def isJSGlobalScope(implDef: ImplDef) =
    implDef.symbol.tpe.typeSymbol isSubClass JSGlobalScopeClass

  private def isJSLambda(sym: Symbol) = sym.isAnonymousClass &&
    AllJSFunctionClasses.exists(sym.tpe.typeSymbol isSubClass _)

  private def isScalaEnum(implDef: ImplDef) =
    implDef.symbol.tpe.typeSymbol isSubClass ScalaEnumClass

  private def isJSDynamic(tree: Tree) = tree.tpe.typeSymbol == JSDynamicClass

  /**
   * is this symbol a setter that has a non-unit return type
   *
   * these setters don't make sense in JS (in JS, assignment returns
   * the assigned value) and are therefore not allowed in facade types
   */
  private def isNonJSScalaSetter(sym: Symbol) = nme.isSetterName(sym.name) && {
    sym.tpe.paramss match {
      case List(List(arg)) =>
        !isScalaRepeatedParamType(arg.tpe) &&
        sym.tpe.resultType.typeSymbol != UnitClass
      case _ => false
    }
  }

  trait ScalaEnumFctExtractors {
    protected val methSym: Symbol

    protected def resolve(ptpes: Symbol*) = {
      val res = methSym suchThat {
        _.tpe.params.map(_.tpe.typeSymbol) == ptpes.toList
      }
      assert(res != NoSymbol)
      res
    }

    protected val noArg    = resolve()
    protected val nameArg  = resolve(StringClass)
    protected val intArg   = resolve(IntClass)
    protected val fullMeth = resolve(IntClass, StringClass)

    /**
     * Extractor object for calls to the targeted symbol that do not have an
     * explicit name in the parameters
     *
     * Extracts:
     * - `sel: Select` where sel.symbol is targeted symbol (no arg)
     * - Apply(meth, List(param)) where meth.symbol is targeted symbol (i: Int)
     */
    object NoName {
      def unapply(t: Tree) = t match {
        case sel: Select if sel.symbol == noArg =>
          Some(None)
        case Apply(meth, List(param)) if meth.symbol == intArg =>
          Some(Some(param))
        case _ =>
          None
      }
    }

    object NullName {
      def unapply(tree: Tree) = tree match {
        case Apply(meth, List(Literal(Constant(null)))) =>
          meth.symbol == nameArg
        case Apply(meth, List(_, Literal(Constant(null)))) =>
          meth.symbol == fullMeth
        case _ => false
      }
    }

  }

  private object ScalaEnumValue extends {
    protected val methSym = getMemberMethod(ScalaEnumClass, jsnme.Value)
  } with ScalaEnumFctExtractors

  private object ScalaEnumVal extends {
    protected val methSym = {
      val valSym = getMemberClass(ScalaEnumClass, jsnme.Val)
      valSym.tpe.member(nme.CONSTRUCTOR)
    }
  } with ScalaEnumFctExtractors

  /**
   * Construct a call to Enumeration.Value
   * @param thisSym  ClassSymbol of enclosing class
   * @param nameOrig Symbol of ValDef where this call will be placed
   * 			       (determines the string passed to Value)
   * @param intParam Optional tree with Int passed to Value
   * @return Typed tree with appropriate call to Value
   */
  private def ScalaEnumValName(
      thisSym: Symbol,
      nameOrig: Symbol,
      intParam: Option[Tree]) = {

    val defaultName = nameOrig.asTerm.getterName.encoded


    // Construct the following tree
    //
    //   if (nextName != null && nextName.hasNext)
    //     nextName.next()
    //   else
    //     <defaultName>
    //
    val nextNameTree = Select(This(thisSym), jsnme.nextName)
    val nullCompTree =
      Apply(Select(nextNameTree, nme.NE), Literal(Constant(null)) :: Nil)
    val hasNextTree = Select(nextNameTree, jsnme.hasNext)
    val condTree = Apply(Select(nullCompTree, nme.ZAND), hasNextTree :: Nil)
    val nameTree = If(condTree,
        Apply(Select(nextNameTree, jsnme.next), Nil),
        Literal(Constant(defaultName)))
    val params = intParam.toList :+ nameTree

    typer.typed {
      Apply(Select(This(thisSym), jsnme.Value), params)
    }
  }

  private def rawJSAnnot =
    Annotation(RawJSTypeAnnot.tpe, List.empty, ListMap.empty)

  private lazy val ScalaEnumClass = getRequiredClass("scala.Enumeration")

  /** checks if the primary constructor of the ClassDef `cldef` does not
   *  take any arguments
   */
  private def primCtorNoArg(cldef: ClassDef) =
    getPrimCtor(cldef.symbol.tpe).map(_.paramss == List(List())).getOrElse(true)

  /** return the MethodSymbol of the primary constructor of the given type
   *  if it exists
   */
  private def getPrimCtor(tpe: Type) =
    tpe.declaration(nme.CONSTRUCTOR).alternatives.collectFirst {
      case ctor: MethodSymbol if ctor.isPrimaryConstructor => ctor
    }

}