diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 5daee6fc9b46..81085b9d32ab 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -737,22 +737,11 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case _ => This(ctx.owner.enclosingClass.asClass) } - /** Is this an application on a structural selection? - * - * @see isStructuralTermSelect - */ - def isStructuralTermApply(tree: Tree)(implicit ctx: Context): Boolean = tree match { - case Apply(fun, _) => - isStructuralTermSelect(fun) - case _ => - false - } - - /** Is this a selection of a member of a structural type that is not a member - * of an underlying class or trait? + /** Is this a (potentially applied) selection of a member of a structural type + * that is not a member of an underlying class or trait? */ - def isStructuralTermSelect(tree: Tree)(implicit ctx: Context): Boolean = tree match { - case tree: Select => + def isStructuralTermSelectOrApply(tree: Tree)(implicit ctx: Context): Boolean = { + def isStructuralTermSelect(tree: Select) = { def hasRefinement(qualtpe: Type): Boolean = qualtpe.dealias match { case RefinedType(parent, rname, rinfo) => rname == tree.name || hasRefinement(parent) @@ -766,8 +755,16 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => false } !tree.symbol.exists && tree.isTerm && hasRefinement(tree.qualifier.tpe) - case _ => - false + } + def loop(tree: Tree): Boolean = tree match { + case Apply(fun, _) => + loop(fun) + case tree: Select => + isStructuralTermSelect(tree) + case _ => + false + } + loop(tree) } /** Is this call a call to a method that is marked as Inline */ diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 889232902567..ac452780ccc4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -117,33 +117,39 @@ trait Dynamic { self: Typer with Applications => * Given `x.a`, where `x` is of (widened) type `T` (a value type or a nullary method type), * and `x.a` is of type `U`, map `x.a` to the equivalent of: * - * (x: Selectable).selectDynamic("a").asInstanceOf[U] + * ```scala + * (x: Selectable).selectDynamic("a").asInstanceOf[U] + * ``` * - * Given `x.a(arg1, ..., argn)`, where `x.a` is of (widened) type (T1, ..., Tn)R, - * map `x.a(arg1, ..., argn)` to the equivalent of: + * Given `x.a(a11, ..., a1n)...(aN1, ..., aNn)`, where `x.a` is of (widened) type + * `(T11, ..., T1n)...(TN1, ..., TNn) => R`, it is desugared to: * - * (x:selectable).applyDynamic("a", CT1, ..., CTn)(arg1, ..., argn).asInstanceOf[R] + * ```scala + * (x:selectable).applyDynamic("a", CT11, ..., CT1n, ..., CTN1, ... CTNn) + * (a11, ..., a1n, ..., aN1, ..., aNn) + * .asInstanceOf[R] + * ``` * - * where CT1, ..., CTn are the class tags representing the erasure of T1, ..., Tn. + * where CT11, ..., CTNn are the class tags representing the erasure of T11, ..., TNn. * * It's an error if U is neither a value nor a method type, or a dependent method * type. */ def handleStructural(tree: Tree)(implicit ctx: Context): Tree = { + val (fun @ Select(qual, name), targs, vargss) = decomposeCall(tree) - def structuralCall(qual: Tree, name: Name, selectorName: TermName, ctags: List[Tree], args: Option[List[Tree]]) = { + def structuralCall(selectorName: TermName, ctags: List[Tree]) = { val selectable = adapt(qual, defn.SelectableType) // ($qual: Selectable).$selectorName("$name", ..$ctags) val base = untpd.Apply( - untpd.TypedSplice(selectable.select(selectorName)).withPos(tree.pos), + untpd.TypedSplice(selectable.select(selectorName)).withPos(fun.pos), (Literal(Constant(name.toString)) :: ctags).map(untpd.TypedSplice(_))) - val scall = args match { - case None => base - case Some(args) => untpd.Apply(base, args) - } + val scall = + if (vargss.isEmpty) base + else untpd.Apply(base, vargss.flatten) typed(scall) } @@ -151,27 +157,33 @@ trait Dynamic { self: Typer with Applications => def fail(name: Name, reason: String) = errorTree(tree, em"Structural access not allowed on method $name because it $reason") - val tpe = tree.tpe.widen - tree match { - case Apply(fun @ Select(qual, name), args) => - val funTpe = fun.tpe.widen.asInstanceOf[MethodType] - if (funTpe.isParamDependent) + fun.tpe.widen match { + case tpe: ValueType => + structuralCall(nme.selectDynamic, Nil).asInstance(tpe) + + case tpe: MethodType => + def isDependentMethod(tpe: Type): Boolean = tpe match { + case tpe: MethodType => + tpe.isParamDependent || + tpe.isResultDependent || + isDependentMethod(tpe.resultType) + case _ => + false + } + + if (isDependentMethod(tpe)) fail(name, i"has a method type with inter-parameter dependencies") else { - val ctags = funTpe.paramInfos.map(pt => - implicitArgTree(defn.ClassTagType.appliedTo(pt :: Nil), tree.pos.endPos)) - structuralCall(qual, name, nme.applyDynamic, ctags, Some(args)).asInstance(tpe.resultType) + val ctags = tpe.paramInfoss.flatten.map(pt => + implicitArgTree(defn.ClassTagType.appliedTo(pt.widenDealias :: Nil), fun.pos.endPos)) + structuralCall(nme.applyDynamic, ctags).asInstance(tpe.finalResultType) } - case Select(qual, name) if tpe.isValueType => - structuralCall(qual, name, nme.selectDynamic, Nil, None).asInstance(tpe) - case Select(_, _) if !tpe.isParameterless => - // We return the tree unchanged; The structural call will be handled when we take care of the - // enclosing application. - tree - case Select(_, name) if tpe.isInstanceOf[PolyType] => + + // (@allanrenucci) I think everything below is dead code + case _: PolyType => fail(name, "is polymorphic") - case Select(_, name) => - fail(name, i"has an unsupported type: ${tree.tpe.widen}") + case tpe => + fail(name, i"has an unsupported type: $tpe") } } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6e6340856cb9..42cd10f1e219 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2640,9 +2640,8 @@ class Typer extends Namer convertNewGenericArray(readapt(tree.appliedToTypeTrees(typeArgs))) } case wtp => - if (isStructuralTermApply(tree)) - readaptSimplified(handleStructural(tree)) - else if (isStructuralTermSelect(tree) && tree.tpe.widen.isValueType) + val isStructuralCall = wtp.isValueType && isStructuralTermSelectOrApply(tree) + if (isStructuralCall) readaptSimplified(handleStructural(tree)) else pt match { case pt: FunProto => diff --git a/docs/docs/reference/changed/structural-types-spec.md b/docs/docs/reference/changed/structural-types-spec.md index 8739b7f23ca0..abf9ce889736 100644 --- a/docs/docs/reference/changed/structural-types-spec.md +++ b/docs/docs/reference/changed/structural-types-spec.md @@ -47,17 +47,19 @@ consider three distinct cases: (v: Selectable).selectDynamic("a").asInstanceOf[U] ``` -- If `U` is a method type `(T1, ..., Tn) => R` with at most 7 - parameters and it is not a dependent method type, we map `v.a` to +- If `U` is a method type `(T11, ..., T1n)...(TN1, ..., TNn) => R` and it is not +a dependent method type, we map `v.a(a11, ..., a1n)...(aN1, aNn)` to the equivalent of: ```scala v.a(arg1, ..., argn) ---> - (v: Selectable).applyDynamic("a", CT1, ..., CTn)(arg1, ..., argn).asInstanceOf[R] + (v: Selectable).applyDynamic("a", CT11, ..., CTn, ..., CTN1, ... CTNn) + (a11, ..., a1n, ..., aN1, ..., aNn) + .asInstanceOf[R] ``` - If `U` is neither a value nor a method type, or a dependent method - type, or has more than 7 parameters, an error is emitted. + type, an error is emitted. We make sure that `r` conforms to type `Selectable`, potentially by introducing an implicit conversion, and then call either diff --git a/library/src/scala/reflect/Selectable.scala b/library/src/scala/reflect/Selectable.scala index ee79d1c95af5..2cab4424ea19 100644 --- a/library/src/scala/reflect/Selectable.scala +++ b/library/src/scala/reflect/Selectable.scala @@ -19,7 +19,7 @@ class Selectable(val receiver: Any) extends AnyVal with scala.Selectable { val paramClasses = paramTypes.map(_.runtimeClass) val mth = rcls.getMethod(name, paramClasses: _*) ensureAccessible(mth) - mth.invoke(receiver, args.map(_.asInstanceOf[AnyRef]): _*) + mth.invoke(receiver, args.asInstanceOf[Seq[AnyRef]]: _*) } } diff --git a/tests/neg/structural.scala b/tests/neg/structural.scala index db286c255d5d..be28e4d09d17 100644 --- a/tests/neg/structural.scala +++ b/tests/neg/structural.scala @@ -20,4 +20,20 @@ object Test3 { type G = { def foo(x: Int, y: Int): Unit } def j(x: G) = x.foo(???) // error: missing argument + + class H { type S = String; type I } + class I extends H { type I = Int } + type Dep = { + def fun1(x: H, y: x.S): Int + def fun2(x: H, y: x.I): Int + def fun3(y: H): y.S + def fun4(y: H): y.I + } + def k(x: Dep) = { + val y = new I + x.fun1(y, "Hello") + x.fun2(y, 1) // error + x.fun3(y) + x.fun4(y) // error + } } diff --git a/tests/run/structural.scala b/tests/run/structural.scala new file mode 100644 index 000000000000..6c81e023c891 --- /dev/null +++ b/tests/run/structural.scala @@ -0,0 +1,106 @@ +import scala.reflect.Selectable.reflectiveSelectable + +object Test { + class C { type S = String; type I } + class D extends C { type I = Int } + + type Foo = { + def sel0: Int + def sel1: Int => Int + def fun0(x: Int): Int + + def fun1(x: Int)(y: Int): Int + def fun2(x: Int): Int => Int + def fun3(a1: Int, a2: Int, a3: Int) + (a4: Int, a5: Int, a6: Int) + (a7: Int, a8: Int, a9: Int): Int + + def fun4(implicit x: Int): Int + def fun5(x: Int)(implicit y: Int): Int + + def fun6(x: C, y: x.S): Int + def fun7(x: C, y: x.I): Int + def fun8(y: C): y.S + def fun9(y: C): y.I + } + + class FooI { + def sel0: Int = 1 + def sel1: Int => Int = x => x + def fun0(x: Int): Int = x + + def fun1(x: Int)(y: Int): Int = x + y + def fun2(x: Int): Int => Int = y => x * y + def fun3(a1: Int, a2: Int, a3: Int) + (a4: Int, a5: Int, a6: Int) + (a7: Int, a8: Int, a9: Int): Int = -1 + + def fun4(implicit x: Int): Int = x + def fun5(x: Int)(implicit y: Int): Int = x + y + + def fun6(x: C, y: x.S): Int = 1 + def fun7(x: C, y: x.I): Int = 2 + def fun8(y: C): y.S = "Hello" + def fun9(y: C): y.I = 1.asInstanceOf[y.I] + } + + def basic(x: Foo): Unit ={ + assert(x.sel0 == 1) + assert(x.sel1(2) == 2) + assert(x.fun0(3) == 3) + + val f = x.sel1 + assert(f(3) == 3) + } + + def currying(x: Foo): Unit = { + assert(x.fun1(1)(2) == 3) + assert(x.fun2(1)(2) == 2) + assert(x.fun3(1, 2, 3)(4, 5, 6)(7, 8, 9) == -1) + } + + def etaExpansion(x: Foo): Unit = { + val f0 = x.fun0(_) + assert(f0(2) == 2) + + val f1 = x.fun0 _ + assert(f1(2) == 2) + + val f2 = x.fun1(1)(_) + assert(f2(2) == 3) + + val f3 = x.fun1(1) _ + assert(f3(2) == 3) + + val f4 = x.fun1(1) + assert(f4(2) == 3) + } + + def implicits(x: Foo) = { + implicit val y = 2 + assert(x.fun4 == 2) + assert(x.fun5(1) == 3) + } + + // Limited support for dependant methods + def dependant(x: Foo) = { + val y = new D + + assert(x.fun6(y, "Hello") == 1) + // assert(x.fun7(y, 1) == 2) // error: No ClassTag available for x.I + + val s = x.fun8(y) + assert((s: String) == "Hello") + + // val i = x.fun9(y) // error: rejected (blows up in pickler if not rejected) + // assert((i: String) == "Hello") // error: Type mismatch: found: y.S(i); required: String + } + + def main(args: Array[String]): Unit = { + basic(new FooI) + currying(new FooI) + etaExpansion(new FooI) + implicits(new FooI) + dependant(new FooI) + } +}