aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/transform/CollectEntryPoints.scala
blob: e82be4378059ac15e6bbd18d77835b58c6adb6f7 (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
package dotty.tools.dotc.transform

import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer, MiniPhaseTransform}
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Contexts.Context
import scala.collection.mutable.ListBuffer
import dotty.tools.dotc.core.{Scopes, Flags}
import dotty.tools.dotc.core.Symbols.NoSymbol
import scala.annotation.tailrec
import dotty.tools.dotc.core._
import Symbols._
import dotty.tools.dotc.transform.TreeTransforms.{NXTransformations, TransformerInfo, TreeTransform, TreeTransformer}
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Contexts.Context
import scala.collection.mutable
import dotty.tools.dotc.core.Names.Name
import NameOps._
import Types._
import scala.collection.SortedSet
import Decorators._
import StdNames._
import dotty.tools.dotc.util.Positions.Position
import dotty.tools.dotc.config.JavaPlatform

class CollectEntryPoints extends MiniPhaseTransform {

  /** perform context-dependant initialization */
  override def prepareForUnit(tree: tpd.Tree)(implicit ctx: Context) = {
    entryPoints = collection.immutable.TreeSet.empty[Symbol](new SymbolOrdering())
    assert(ctx.platform.isInstanceOf[JavaPlatform], "Java platform specific phase")
    this
  }

  private var entryPoints: Set[Symbol] = _

  def getEntryPoints = entryPoints.toList

  override def phaseName: String = "collectEntryPoints"
  override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
    if (tree.symbol.owner.isClass && isJavaEntryPoint(tree.symbol)) {
      // collecting symbols for entry points here (as opposed to GenBCode where they are used)
      // has the advantage of saving an additional pass over all ClassDefs.
      entryPoints += tree.symbol
    }
    tree
  }

  def isJavaEntryPoint(sym: Symbol)(implicit ctx: Context): Boolean = {
    def fail(msg: String, pos: Position = sym.pos) = {
      ctx.warning(sym.name +
        s" has a main method with parameter type Array[String], but ${sym.fullName} will not be a runnable program.\n  Reason: $msg",
        sourcePos(sym.pos)
        // TODO: make this next claim true, if possible
        //   by generating valid main methods as static in module classes
        //   not sure what the jvm allows here
        // + "  You can still run the program by calling it as " + javaName(sym) + " instead."
      )
      false
    }
    def failNoForwarder(msg: String) = {
      fail(s"$msg, which means no static forwarder can be generated.\n")
    }
    val possibles = if (sym.flags is Flags.Module) (sym.info nonPrivateMember nme.main).alternatives else Nil
    val hasApproximate = possibles exists {
      m =>
        m.info match {
          case MethodTpe(_, p :: Nil, _) =>
            p.typeSymbol == defn.ArrayClass
          case _ => false
        }
    }
    def precise(implicit ctx: Context) = {
      val companion = sym.companionClass //sym.asClass.linkedClassOfClass
      val javaPlatform = ctx.platform.asInstanceOf[JavaPlatform]
      if (javaPlatform.hasJavaMainMethod(companion))
        failNoForwarder("companion contains its own main method")
      else if (companion.exists && companion.info.member(nme.main).exists)
      // this is only because forwarders aren't smart enough yet
        failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)")
      else if (companion.flags is Flags.Trait)
        failNoForwarder("companion is a trait")
      // Now either succeed, or issue some additional warnings for things which look like
      // attempts to be java main methods.
      else (possibles exists (x => javaPlatform.isJavaMainMethod(x.symbol))) || {
        possibles exists {
          m =>
            m.symbol.info match {
              case t: PolyType =>
                fail("main methods cannot be generic.")
              case t: MethodType =>
                if (t.resultType :: t.paramInfos exists (_.typeSymbol.isAbstractType))
                  fail("main methods cannot refer to type parameters or abstract types.", m.symbol.pos)
                else
                  javaPlatform.isJavaMainMethod(m.symbol) || fail("main method must have exact signature (Array[String])Unit", m.symbol.pos)
              case tp =>
                fail(s"don't know what this is: $tp", m.symbol.pos)
            }
        }
      }
    }

  // At this point it's a module with a main-looking method, so either succeed or warn that it isn't.
  hasApproximate && precise(ctx.withPhase(ctx.erasurePhase))
    // Before erasure so we can identify generic mains.


}

}

class SymbolOrdering(implicit ctx: Context) extends Ordering[Symbol] {
  override def compare(x: Symbol, y: Symbol): Int = {
    x.fullName.toString.compareTo(y.fullName.toString)
  }
}