summaryrefslogtreecommitdiff
path: root/src/compiler/scala/reflect/macros/compiler/Validators.scala
blob: 02c1f7c4310979f4bbe1980053fc2cdd33d2c72c (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
package scala.reflect.macros
package compiler

import java.util.UUID.randomUUID
import scala.reflect.internal.Flags._
import scala.reflect.macros.TypecheckException

trait Validators {
  self: DefaultMacroCompiler =>

  import global._
  import analyzer._
  import definitions._
  private val runDefinitions = currentRun.runDefinitions
  import runDefinitions.{Predef_???, _}

  def validateMacroImplRef() = {
    sanityCheck()
    if (macroImpl != Predef_???) checkMacroDefMacroImplCorrespondence()
  }

  private def sanityCheck() = {
    if (!macroImpl.isMethod) MacroImplReferenceWrongShapeError()
    if (macroImpl.typeParams.length != targs.length) MacroImplWrongNumberOfTypeArgumentsError()
    if (!macroImpl.isPublic) MacroImplNotPublicError()
    if (macroImpl.isOverloaded) MacroImplOverloadedError()
    val implicitParams = aparamss.flatten filter (_.isImplicit)
    if (implicitParams.nonEmpty) MacroImplNonTagImplicitParameters(implicitParams)
    val effectiveOwner = if (isImplMethod) macroImplOwner else macroImplOwner.owner
    val declaredInStaticObject = effectiveOwner.isStaticOwner || effectiveOwner.moduleClass.isStaticOwner
    if (!declaredInStaticObject) MacroImplReferenceWrongShapeError()
  }

  private def checkMacroDefMacroImplCorrespondence() = {
    val atvars = atparams map freshVar
    def atpeToRtpe(atpe: Type) = atpe.substSym(aparamss.flatten, rparamss.flatten).instantiateTypeParams(atparams, atvars)

    // we only check strict correspondence between value parameterss
    // type parameters of macro defs and macro impls don't have to coincide with each other
    if (aparamss.length != rparamss.length) MacroImplParamssMismatchError()
    map2(aparamss, rparamss)((aparams, rparams) => {
      if (aparams.length < rparams.length) MacroImplMissingParamsError(aparams, rparams)
      if (rparams.length < aparams.length) MacroImplExtraParamsError(aparams, rparams)
    })

    try {
      // cannot fuse this map2 and the map2 above because if aparamss.flatten != rparamss.flatten
      // then `atpeToRtpe` is going to fail with an unsound substitution
      map2(aparamss.flatten, rparamss.flatten)((aparam, rparam) => {
        if (aparam.name != rparam.name && !rparam.isSynthetic) MacroImplParamNameMismatchError(aparam, rparam)
        if (isRepeated(aparam) ^ isRepeated(rparam)) MacroImplVarargMismatchError(aparam, rparam)
        val aparamtpe = aparam.tpe match {
          case MacroContextType(tpe) => tpe
          case tpe => tpe
        }
        checkMacroImplParamTypeMismatch(atpeToRtpe(aparamtpe), rparam)
      })

      checkMacroImplResultTypeMismatch(atpeToRtpe(aret), rret)

      val maxLubDepth = lubDepth(aparamss.flatten map (_.tpe)) max lubDepth(rparamss.flatten map (_.tpe))
      val atargs = solvedTypes(atvars, atparams, atparams map varianceInType(aret), upper = false, maxLubDepth)
      val boundsOk = typer.silent(_.infer.checkBounds(macroDdef, NoPrefix, NoSymbol, atparams, atargs, ""))
      boundsOk match {
        case SilentResultValue(true) => // do nothing, success
        case SilentResultValue(false) | SilentTypeError(_) => MacroImplTargMismatchError(atargs, atparams)
      }
    } catch {
      case ex: NoInstance => MacroImplTparamInstantiationError(atparams, ex)
    }
  }

  // aXXX (e.g. aparamss) => characteristics of the actual macro impl signature extracted from the macro impl ("a" stands for "actual")
  // rXXX (e.g. rparamss) => characteristics of the reference macro impl signature synthesized from the macro def ("r" stands for "reference")
  // FIXME: cannot write this concisely because of SI-7507
  //lazy val MacroImplSig(atparams, aparamss, aret) = macroImplSig
  //lazy val MacroImplSig(_, rparamss, rret) = referenceMacroImplSig
  lazy val atparams = macroImplSig.tparams
  lazy val aparamss = macroImplSig.paramss
  lazy val aret = macroImplSig.ret
  lazy val rparamss = referenceMacroImplSig.paramss
  lazy val rret = referenceMacroImplSig.ret

  // Technically this can be just an alias to MethodType, but promoting it to a first-class entity
  // provides better encapsulation and convenient syntax for pattern matching.
  private case class MacroImplSig(tparams: List[Symbol], paramss: List[List[Symbol]], ret: Type) {
    private def tparams_s = if (tparams.isEmpty) "" else tparams.map(_.defString).mkString("[", ", ", "]")
    private def paramss_s = paramss map (ps => ps.map(s => s"${s.name}: ${s.tpe_*}").mkString("(", ", ", ")")) mkString ""
    override def toString = "MacroImplSig(" + tparams_s + paramss_s + ret + ")"
  }

  /** An actual macro implementation signature extracted from a macro implementation method.
   *
   *  For the following macro impl:
   *    def fooBar[T: c.WeakTypeTag]
   *           (c: scala.reflect.macros.blackbox.Context)
   *           (xs: c.Expr[List[T]])
   *           : c.Expr[T] = ...
   *
   *  This function will return:
   *    (c: scala.reflect.macros.blackbox.Context)(xs: c.Expr[List[T]])c.Expr[T]
   *
   *  Note that type tag evidence parameters are not included into the result.
   *  Type tag context bounds for macro impl tparams are optional.
   *  Therefore compatibility checks ignore such parameters, and we don't need to bother about them here.
   *
   *  This method cannot be reduced to just macroImpl.info, because macro implementations might
   *  come in different shapes. If the implementation is an apply method of a *box.Macro-compatible object,
   *  then it won't have (c: *box.Context) in its parameters, but will rather refer to *boxMacro.c.
   *
   *  @param macroImpl The macro implementation symbol
   */
  private lazy val macroImplSig: MacroImplSig = {
    val tparams = macroImpl.typeParams
    val paramss = transformTypeTagEvidenceParams(macroImplRef, (param, tparam) => NoSymbol)
    val ret = macroImpl.info.finalResultType
    MacroImplSig(tparams, paramss, ret)
  }

  /** A reference macro implementation signature extracted from a given macro definition.
   *
   *  For the following macro def:
   *    def foo[T](xs: List[T]): T = macro fooBar
   *
   *  This function will return:
   *    (c: scala.reflect.macros.blackbox.Context)(xs: c.Expr[List[T]])c.Expr[T] or
   *    (c: scala.reflect.macros.whitebox.Context)(xs: c.Expr[List[T]])c.Expr[T]
   *
   *  Note that type tag evidence parameters are not included into the result.
   *  Type tag context bounds for macro impl tparams are optional.
   *  Therefore compatibility checks ignore such parameters, and we don't need to bother about them here.
   *
   *  Also note that we need a DefDef, not the corresponding MethodSymbol, because that symbol would be of no use for us.
   *  Macro signatures are verified when typechecking macro defs, which means that at that moment inspecting macroDef.info
   *  means asking for cyclic reference errors.
   *
   *  We need macro implementation symbol as well, because the return type of the macro definition might be omitted,
   *  and in that case we'd need to infer it from the return type of the macro implementation. Luckily for us, we can
   *  use that symbol without a risk of running into cycles.
   *
   *  @param typer     Typechecker of `macroDdef`
   *  @param macroDdef The macro definition tree
   *  @param macroImpl The macro implementation symbol
   */
  private lazy val referenceMacroImplSig: MacroImplSig = {
    // had to move method's body to an object because of the recursive dependencies between sigma and param
    object SigGenerator {
      val cache = scala.collection.mutable.Map[Symbol, Symbol]()
      val ctxTpe = if (isImplBlackbox) BlackboxContextClass.tpe else WhiteboxContextClass.tpe
      val ctxPrefix =
        if (isImplMethod) singleType(NoPrefix, makeParam(nme.macroContext, macroDdef.pos, ctxTpe, SYNTHETIC))
        else singleType(ThisType(macroImpl.owner), macroImpl.owner.tpe.member(nme.c))
      val paramss =
        if (isImplMethod) List(ctxPrefix.termSymbol) :: mmap(macroDdef.vparamss)(param)
        else mmap(macroDdef.vparamss)(param)
      val macroDefRet =
        if (!macroDdef.tpt.isEmpty) typer.typedType(macroDdef.tpt).tpe
        else computeMacroDefTypeFromMacroImplRef(macroDdef, macroImplRef) orElse AnyTpe
      val implReturnType = sigma(increaseMetalevel(ctxPrefix, macroDefRet))

      object SigmaTypeMap extends TypeMap {
        def mapPrefix(pre: Type) = pre match {
          case ThisType(sym) if sym == macroDef.owner =>
            singleType(singleType(ctxPrefix, MacroContextPrefix), ExprValue)
          case SingleType(NoPrefix, sym) =>
            mfind(macroDdef.vparamss)(_.symbol == sym).fold(pre)(p => singleType(singleType(NoPrefix, param(p)), ExprValue))
          case _ =>
            mapOver(pre)
        }
        def apply(tp: Type): Type = tp match {
          case TypeRef(pre, sym, args) =>
            val pre1  = mapPrefix(pre)
            val args1 = mapOverArgs(args, sym.typeParams)
            if ((pre eq pre1) && (args eq args1)) tp
            else typeRef(pre1, sym, args1)
          case _ =>
            mapOver(tp)
        }
      }
      def sigma(tpe: Type): Type = SigmaTypeMap(tpe)

      def makeParam(name: Name, pos: Position, tpe: Type, flags: Long) =
        macroDef.newValueParameter(name.toTermName, pos, flags) setInfo tpe
      def param(tree: Tree): Symbol = (
        cache.getOrElseUpdate(tree.symbol, {
          val sym = tree.symbol
          assert(sym.isTerm, s"sym = $sym, tree = $tree")
          makeParam(sym.name, sym.pos, sigma(increaseMetalevel(ctxPrefix, sym.tpe)), sym.flags)
        })
      )
    }

    import SigGenerator._
    macroLogVerbose(s"generating macroImplSigs for: $macroDdef")
    val result = MacroImplSig(macroDdef.tparams map (_.symbol), paramss, implReturnType)
    macroLogVerbose(s"result is: $result")
    result
  }
}