From b3d392ddb7ffd526881b98d874ab0616956af3fc Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 2 Nov 2018 19:31:57 +0100 Subject: [PATCH] Fix #5372: Use `applyDynamic` in structural calls This commit changes how `Selectable` works for calling methods: It used to be called `selectMethodDynamic and to receive the name of the method to call along with the class tags of the formal parameters. This commit renames it to `applyDynamic`, and changes it so that it also takes the actual arguments that are passed in the function call. Fixes #5372 --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 11 +++ .../dotty/tools/dotc/core/Definitions.scala | 3 - .../src/dotty/tools/dotc/core/StdNames.scala | 1 - .../dotty/tools/dotc/typer/Applications.scala | 5 +- .../src/dotty/tools/dotc/typer/Dynamic.scala | 70 +++++++++++-------- .../src/dotty/tools/dotc/typer/Typer.scala | 5 +- .../changed/structural-types-spec.md | 27 ++++--- .../reference/changed/structural-types.md | 10 ++- library/src/scala/Selectable.scala | 4 +- library/src/scala/reflect/Selectable.scala | 50 +------------ tests/neg/structural.scala | 6 +- 11 files changed, 87 insertions(+), 105 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 0b1bf71a18db..5daee6fc9b46 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -737,6 +737,17 @@ 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? */ diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 05f599297e26..d492b8ab6ed1 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -24,9 +24,6 @@ object Definitions { * else without affecting the set of programs that can be compiled. */ val MaxImplementedFunctionArity: Int = 22 - - /** The maximal arity of a function that can be accessed as member of a structural type */ - val MaxStructuralMethodArity: Int = 7 } /** A class defining symbols and types of standard definitions diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index d1cbf31b00e6..98e5ed22a024 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -501,7 +501,6 @@ object StdNames { val scala_ : N = "scala" val scalaShadowing : N = "scalaShadowing" val selectDynamic: N = "selectDynamic" - val selectDynamicMethod: N = "selectDynamicMethod" val selectOverloadedMethod: N = "selectOverloadedMethod" val selectTerm: N = "selectTerm" val selectType: N = "selectType" diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index e44b6b907dff..3231d20849d4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -490,7 +490,10 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } def tryDefault(n: Int, args1: List[Arg]): Unit = { - val getter = findDefaultGetter(n + numArgs(normalizedFun)) + val getter = + // `methRef.symbol` doesn't exist for structural calls + if (methRef.symbol.exists) findDefaultGetter(n + numArgs(normalizedFun)) + else EmptyTree if (getter.isEmpty) missingArg(n) else { val substParam = addTyped( diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 53093bc6292e..889232902567 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -35,7 +35,7 @@ object Dynamic { * 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. + * or `applyDynamic` on a `Selectable` instance. @See handleStructural. * */ trait Dynamic { self: Typer with Applications => @@ -113,53 +113,65 @@ trait Dynamic { self: Typer with Applications => } /** 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: + * 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] + * (x: Selectable).selectDynamic("a").asInstanceOf[U] * - * If `U` is a method type (T1,...,Tn)R, map `x.a` to the equivalent of: + * Given `x.a(arg1, ..., argn)`, where `x.a` is of (widened) type (T1, ..., Tn)R, + * map `x.a(arg1, ..., argn)` to the equivalent of: * - * (x: Selectable).selectDynamicMethod("a", CT1, ..., CTn).asInstanceOf[(T1,...,Tn) => R] + * (x:selectable).applyDynamic("a", CT1, ..., CTn)(arg1, ..., argn).asInstanceOf[R] * - * where CT1,...,CTn are the class tags representing the erasure of T1,...,Tn. + * 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). + * type. */ def handleStructural(tree: Tree)(implicit ctx: Context): Tree = { - val Select(qual, name) = tree - def structuralCall(selectorName: TermName, formals: List[Tree]) = { + def structuralCall(qual: Tree, name: Name, selectorName: TermName, ctags: List[Tree], args: Option[List[Tree]]) = { val selectable = adapt(qual, defn.SelectableType) - val scall = untpd.Apply( - untpd.TypedSplice(selectable.select(selectorName)).withPos(tree.pos), - (Literal(Constant(name.toString)) :: formals).map(untpd.TypedSplice(_))) + + // ($qual: Selectable).$selectorName("$name", ..$ctags) + val base = + untpd.Apply( + untpd.TypedSplice(selectable.select(selectorName)).withPos(tree.pos), + (Literal(Constant(name.toString)) :: ctags).map(untpd.TypedSplice(_))) + + val scall = args match { + case None => base + case Some(args) => untpd.Apply(base, args) + } + typed(scall) } - def fail(reason: String) = + def fail(name: Name, reason: String) = errorTree(tree, em"Structural access not allowed on method $name because it $reason") - tree.tpe.widen match { - case tpe: MethodType => - if (tpe.isParamDependent) - fail(i"has a method type with inter-parameter dependencies") - else if (tpe.paramNames.length > Definitions.MaxStructuralMethodArity) - fail(i"""takes too many parameters. - |Structural types only support methods taking up to ${Definitions.MaxStructuralMethodArity} arguments""") + val tpe = tree.tpe.widen + tree match { + case Apply(fun @ Select(qual, name), args) => + val funTpe = fun.tpe.widen.asInstanceOf[MethodType] + if (funTpe.isParamDependent) + fail(name, i"has a method type with inter-parameter dependencies") else { - val ctags = tpe.paramInfos.map(pt => + val ctags = funTpe.paramInfos.map(pt => implicitArgTree(defn.ClassTagType.appliedTo(pt :: Nil), tree.pos.endPos)) - structuralCall(nme.selectDynamicMethod, ctags).asInstance(tpe.toFunctionType()) + structuralCall(qual, name, nme.applyDynamic, ctags, Some(args)).asInstance(tpe.resultType) } - case tpe: ValueType => - structuralCall(nme.selectDynamic, Nil).asInstance(tpe) - case tpe: PolyType => - fail("is polymorphic") - case tpe => - fail(i"has an unsupported type: $tpe") + 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] => + fail(name, "is polymorphic") + case Select(_, name) => + fail(name, i"has an unsupported type: ${tree.tpe.widen}") } } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 92f5a47f7d76..9c3901128aa2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2638,7 +2638,10 @@ class Typer extends Namer convertNewGenericArray(readapt(tree.appliedToTypeTrees(typeArgs))) } case wtp => - if (isStructuralTermSelect(tree)) readaptSimplified(handleStructural(tree)) + if (isStructuralTermApply(tree)) + readaptSimplified(handleStructural(tree)) + else if (isStructuralTermSelect(tree) && tree.tpe.widen.isValueType) + readaptSimplified(handleStructural(tree)) else pt match { case pt: FunProto => adaptToArgs(wtp, pt) diff --git a/docs/docs/reference/changed/structural-types-spec.md b/docs/docs/reference/changed/structural-types-spec.md index 5802c012450c..8739b7f23ca0 100644 --- a/docs/docs/reference/changed/structural-types-spec.md +++ b/docs/docs/reference/changed/structural-types-spec.md @@ -20,8 +20,8 @@ The standard library defines a trait `Selectable` in the package ```scala trait Selectable extends Any { def selectDynamic(name: String): Any - def selectDynamicMethod(name: String, paramClasses: ClassTag[_]*): Any = - new UnsupportedOperationException("selectDynamicMethod") + def applyDynamic(name: String, paramClasses: ClassTag[_]*)(args: Any*): Any = + new UnsupportedOperationException("applyDynamic") } ``` @@ -31,10 +31,10 @@ implementations can be envisioned for platforms where Java reflection is not available. `selectDynamic` takes a field name and returns the value associated -with that name in the `Selectable`. Similarly, `selectDynamicMethod` +with that name in the `Selectable`. Similarly, `applyDynamic` takes a method name, `ClassTag`s representing its parameters types and -will return the function that matches this -name and parameter types. +the arguments to pass to the function. It will return the result of +calling this function with the given arguments. Given a value `v` of type `C { Rs }`, where `C` is a class reference and `Rs` are refinement declarations, and given `v.a` of type `U`, we @@ -51,9 +51,9 @@ consider three distinct cases: parameters and it is not a dependent method type, we map `v.a` to the equivalent of: ```scala - v.a + v.a(arg1, ..., argn) ---> - (v: Selectable).selectDynamic("a", CT1, ..., CTn).asInstanceOf[(T1, ..., Tn) => R] + (v: Selectable).applyDynamic("a", CT1, ..., CTn)(arg1, ..., argn).asInstanceOf[R] ``` - If `U` is neither a value nor a method type, or a dependent method @@ -61,16 +61,15 @@ consider three distinct cases: We make sure that `r` conforms to type `Selectable`, potentially by introducing an implicit conversion, and then call either -`selectDynamic` or `selectMethodDynamic`, passing the name of the -member to access and the class tags of the formal parameters, in the -case of a method call. These parameters could be used to disambiguate -one of several overload variants in the future, but overloads are not -supported in structural types at the moment. +`selectDynamic` or `applyDynamic`, passing the name of the +member to access, along with the class tags of the formal parameters +and the arguments in the case of a method call. These parameters +could be used to disambiguate one of several overload variants in the +future, but overloads are not supported in structural types at the +moment. ## Limitations of structural types -- Methods with more than 7 formal parameters cannot be called via - structural call. - Dependent methods cannot be called via structural call. - Overloaded methods cannot be called via structural call. - Refinements do not handle polymorphic methods. diff --git a/docs/docs/reference/changed/structural-types.md b/docs/docs/reference/changed/structural-types.md index 52b171aa75d8..29b69059c3a3 100644 --- a/docs/docs/reference/changed/structural-types.md +++ b/docs/docs/reference/changed/structural-types.md @@ -69,11 +69,9 @@ differences. how to define reflective access operations. By contrast `Selectable` is a trait which declares the access operations. -- One access operation, `selectDynamic` is shared between both - approaches, but the other access operations are - different. `Selectable` defines a `selectDynamicMethod`, which - takes class tags indicating the method's formal parameter types as - additional argument. `Dynamic` comes with `applyDynamic` and - `updateDynamic` methods, which take actual argument values. +- Two access operations, `selectDynamic` and `applyDynamic` are shared + between both approches. In `Selectable`, `applyDynamic` also takes + `ClassTag` indicating the method's formal parameter types. `Dynamic` + comes with `updateDynamic`. [More details](structural-types-spec.html) diff --git a/library/src/scala/Selectable.scala b/library/src/scala/Selectable.scala index c5c714ca9c47..2dd9449c3fad 100644 --- a/library/src/scala/Selectable.scala +++ b/library/src/scala/Selectable.scala @@ -3,6 +3,6 @@ import scala.reflect.ClassTag trait Selectable extends Any { def selectDynamic(name: String): Any - def selectDynamicMethod(name: String, paramClasses: ClassTag[_]*): Any = - new UnsupportedOperationException("selectDynamicMethod") + def applyDynamic(name: String, paramClasses: ClassTag[_]*)(args: Any*): Any = + new UnsupportedOperationException("applyDynamic") } diff --git a/library/src/scala/reflect/Selectable.scala b/library/src/scala/reflect/Selectable.scala index 951cecc9b439..ee79d1c95af5 100644 --- a/library/src/scala/reflect/Selectable.scala +++ b/library/src/scala/reflect/Selectable.scala @@ -10,60 +10,16 @@ class Selectable(val receiver: Any) extends AnyVal with scala.Selectable { } catch { case ex: NoSuchFieldException => - selectDynamicMethod(name).asInstanceOf[() => Any]() + applyDynamic(name)() } } - override def selectDynamicMethod(name: String, paramTypes: ClassTag[_]*): Any = { + override def applyDynamic(name: String, paramTypes: ClassTag[_]*)(args: Any*): Any = { val rcls = receiver.getClass val paramClasses = paramTypes.map(_.runtimeClass) val mth = rcls.getMethod(name, paramClasses: _*) ensureAccessible(mth) - paramTypes.length match { - case 0 => () => - mth.invoke(receiver) - case 1 => (x0: Any) => - mth.invoke(receiver, x0.asInstanceOf[Object]) - case 2 => (x0: Any, x1: Any) => - mth.invoke(receiver, - x0.asInstanceOf[Object], - x1.asInstanceOf[Object]) - case 3 => (x0: Any, x1: Any, x2: Any) => - mth.invoke(receiver, - x0.asInstanceOf[Object], - x1.asInstanceOf[Object], - x2.asInstanceOf[Object]) - case 4 => (x0: Any, x1: Any, x2: Any, x3: Any) => - mth.invoke(receiver, - x0.asInstanceOf[Object], - x1.asInstanceOf[Object], - x2.asInstanceOf[Object], - x3.asInstanceOf[Object]) - case 5 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any) => - mth.invoke(receiver, - x0.asInstanceOf[Object], - x1.asInstanceOf[Object], - x2.asInstanceOf[Object], - x3.asInstanceOf[Object], - x4.asInstanceOf[Object]) - case 6 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any, x5: Any) => - mth.invoke(receiver, - x0.asInstanceOf[Object], - x1.asInstanceOf[Object], - x2.asInstanceOf[Object], - x3.asInstanceOf[Object], - x4.asInstanceOf[Object], - x5.asInstanceOf[Object]) - case 7 => (x0: Any, x1: Any, x2: Any, x3: Any, x4: Any, x5: Any, x6: Any) => - mth.invoke(receiver, - x0.asInstanceOf[Object], - x1.asInstanceOf[Object], - x2.asInstanceOf[Object], - x3.asInstanceOf[Object], - x4.asInstanceOf[Object], - x5.asInstanceOf[Object], - x6.asInstanceOf[Object]) - } + mth.invoke(receiver, args.map(_.asInstanceOf[AnyRef]): _*) } } diff --git a/tests/neg/structural.scala b/tests/neg/structural.scala index bf9fe67b8111..db286c255d5d 100644 --- a/tests/neg/structural.scala +++ b/tests/neg/structural.scala @@ -15,5 +15,9 @@ object Test3 { trait Entry { type Key; val key: Key } type D = { def foo(e: Entry, k: e.Key): Unit } - def i(x: D) = x.foo(???) // error: foo has dependent params + val e = new Entry { type Key = Int; val key = 0 } + def i(x: D) = x.foo(e, 1) // error: foo has dependent params + + type G = { def foo(x: Int, y: Int): Unit } + def j(x: G) = x.foo(???) // error: missing argument }