aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/typer/Dynamic.scala
blob: 25fca546e6db27a2eb16ca3dbf34d302e90e44f4 (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
package dotty.tools
package dotc
package typer

import dotty.tools.dotc.ast.Trees._
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.untpd
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Names.{Name, TermName}
import dotty.tools.dotc.core.StdNames._
import dotty.tools.dotc.core.Types._
import dotty.tools.dotc.core.Decorators._
import core.Symbols._
import core.Definitions
import Inferencing._
import ErrorReporting._

object Dynamic {
  def isDynamicMethod(name: Name): Boolean =
    name == nme.applyDynamic || name == nme.selectDynamic || name == nme.updateDynamic || name == nme.applyDynamicNamed
}

/** Handles programmable member selections of `Dynamic` instances and values
 *  with structural types. Two functionalities:
 *
 * 1. Translates selection that does not typecheck according to the scala.Dynamic rules:
 *    foo.bar(baz) = quux                   ~~> foo.selectDynamic(bar).update(baz, quux)
 *    foo.bar = baz                         ~~> foo.updateDynamic("bar")(baz)
 *    foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...)
 *    foo.bar(baz0, baz1, ...)              ~~> foo.applyDynamic(bar)(baz0, baz1, ...)
 *    foo.bar                               ~~> foo.selectDynamic(bar)
 *
 *  The first matching rule of is applied.
 *
 * 2. Translates member selections on structural types to calls of `selectDynamic`
 *    or `selectDynamicMethod` on a `Selectable` instance. @See handleStructural.
 *
 */
trait Dynamic { self: Typer with Applications =>
  import Dynamic._
  import tpd._

  /** Translate selection that does not typecheck according to the normal rules into a applyDynamic/applyDynamicNamed.
   *    foo.bar(baz0, baz1, ...)                       ~~> foo.applyDynamic(bar)(baz0, baz1, ...)
   *    foo.bar[T0, ...](baz0, baz1, ...)              ~~> foo.applyDynamic[T0, ...](bar)(baz0, baz1, ...)
   *    foo.bar(x = bazX, y = bazY, baz, ...)          ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...)
   *    foo.bar[T0, ...](x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed[T0, ...]("bar")(("x", bazX), ("y", bazY), ("", baz), ...)
   */
  def typedDynamicApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = {
    def typedDynamicApply(qual: untpd.Tree, name: Name, targs: List[untpd.Tree]): Tree = {
      def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true; case _ => false }
      val args = tree.args
      val dynName = if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic
      if (dynName == nme.applyDynamicNamed && untpd.isWildcardStarArgList(args))
        errorTree(tree, "applyDynamicNamed does not support passing a vararg parameter")
      else {
        def namedArgTuple(name: String, arg: untpd.Tree) = untpd.Tuple(List(Literal(Constant(name)), arg))
        def namedArgs = args.map {
          case NamedArg(argName, arg) => namedArgTuple(argName.toString, arg)
          case arg => namedArgTuple("", arg)
        }
        val args1 = if (dynName == nme.applyDynamic) args else namedArgs
        typedApply(untpd.Apply(coreDynamic(qual, dynName, name, targs), args1), pt)
      }
    }

     tree.fun match {
      case Select(qual, name) if !isDynamicMethod(name) =>
        typedDynamicApply(qual, name, Nil)
      case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) =>
        typedDynamicApply(qual, name, targs)
      case TypeApply(fun, targs) =>
        typedDynamicApply(fun, nme.apply, targs)
      case fun =>
        typedDynamicApply(fun, nme.apply, Nil)
    }
  }

  /** Translate selection that does not typecheck according to the normal rules into a selectDynamic.
   *    foo.bar          ~~> foo.selectDynamic(bar)
   *    foo.bar[T0, ...] ~~> foo.selectDynamic[T0, ...](bar)
   *
   *  Note: inner part of translation foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux) is achieved
   *  through an existing transformation of in typedAssign [foo.bar(baz) = quux ~~> foo.bar.update(baz, quux)].
   */
  def typedDynamicSelect(tree: untpd.Select, targs: List[Tree], pt: Type)(implicit ctx: Context): Tree =
    typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name, targs), pt)

  /** Translate selection that does not typecheck according to the normal rules into a updateDynamic.
   *    foo.bar = baz ~~> foo.updateDynamic(bar)(baz)
   */
  def typedDynamicAssign(tree: untpd.Assign, pt: Type)(implicit ctx: Context): Tree = {
    def typedDynamicAssign(qual: untpd.Tree, name: Name, targs: List[untpd.Tree]): Tree =
      typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name, targs), tree.rhs), pt)
    tree.lhs match {
      case Select(qual, name) if !isDynamicMethod(name) =>
        typedDynamicAssign(qual, name, Nil)
      case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) =>
        typedDynamicAssign(qual, name, targs)
      case _ =>
        errorTree(tree, "reassignment to val")
    }
  }

  private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name, targs: List[untpd.Tree])(implicit ctx: Context): untpd.Apply = {
    val select = untpd.Select(qual, dynName)
    val selectWithTypes =
      if (targs.isEmpty) select
      else untpd.TypeApply(select, targs)
    untpd.Apply(selectWithTypes, Literal(Constant(name.toString)))
  }

  /** Handle reflection-based dispatch for members of structural types.
   *  Given `x.a`, where `x` is of (widened) type `T` and `x.a` is of type `U`:
   *
   *  If `U` is a value type, map `x.a` to the equivalent of:
   *
   *     (x: Selectable).selectDynamic(x, "a").asInstanceOf[U]
   *
   *  If `U` is a method type (T1,...,Tn)R, map `x.a` to the equivalent of:
   *
   *     (x: Selectable).selectDynamicMethod("a", CT1, ..., CTn).asInstanceOf[(T1,...,Tn) => R]
   *
   *  where CT1,...,CTn are the class tags representing the erasure of T1,...,Tn.
   *
   *  It's an error if U is neither a value nor a method type, or a dependent method
   *  type, or of too large arity (limit is Definitions.MaxStructuralMethodArity).
   */
  def handleStructural(tree: Tree)(implicit ctx: Context): Tree = {
    val Select(qual, name) = tree

    def structuralCall(selectorName: TermName, formals: List[Tree]) = {
      val selectable = adapt(qual, defn.SelectableType)
      val scall = untpd.Apply(
        untpd.TypedSplice(selectable.select(selectorName)),
        (Literal(Constant(name.toString)) :: formals).map(untpd.TypedSplice(_)))
      typed(scall)
    }

    def fail(reason: String) =
      errorTree(tree, em"Structural access not allowed on method $name because it $reason")

    tree.tpe.widen match {
      case tpe: MethodType =>
        if (tpe.isDependent)
          fail(i"has a dependent method type")
        else if (tpe.paramNames.length > Definitions.MaxStructuralMethodArity)
          fail(i"""takes too many parameters.
                  |Structural types only support methods taking up to ${Definitions.MaxStructuralMethodArity} arguments""")
        else {
          def issueError(msgFn: String => String): Unit = ctx.error(msgFn(""), tree.pos)
          val ctags = tpe.paramInfos.map(pt =>
            inferImplicitArg(defn.ClassTagType.appliedTo(pt :: Nil), issueError, tree.pos.endPos))
          structuralCall(nme.selectDynamicMethod, ctags).asInstance(tpe.toFunctionType())
        }
      case tpe: ValueType =>
        structuralCall(nme.selectDynamic, Nil).asInstance(tpe)
      case tpe: PolyType =>
        fail("is polymorphic")
      case tpe =>
        fail(i"has an unsupported type: $tpe")
    }
  }
}