diff options
author | Martin Odersky <odersky@gmail.com> | 2017-01-05 16:02:09 +0700 |
---|---|---|
committer | Martin Odersky <odersky@gmail.com> | 2017-01-05 18:00:06 +0700 |
commit | aa6ebe938639f07dd6f5612e645f1449f37a86eb (patch) | |
tree | 24d257c1f12d0f5cb9b76102043046e3a904d98a /compiler/src | |
parent | 42eb864dc752254fc3b8b0428570fe94aa1dafc7 (diff) | |
download | dotty-aa6ebe938639f07dd6f5612e645f1449f37a86eb.tar.gz dotty-aa6ebe938639f07dd6f5612e645f1449f37a86eb.tar.bz2 dotty-aa6ebe938639f07dd6f5612e645f1449f37a86eb.zip |
Implement structural type member access
New scheme for implementing structural type member access.
Diffstat (limited to 'compiler/src')
5 files changed, 93 insertions, 6 deletions
diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index da83d0644..429b7235b 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -629,6 +629,28 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case nil => Nil } + + /** Is this a 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) = tree match { + case tree: Select => + def hasRefinement(qualtpe: Type): Boolean = qualtpe.dealias match { + case RefinedType(parent, rname, rinfo) => + rname == tree.name && tree.tpe.widen <:< rinfo || hasRefinement(parent) + case tp: TypeProxy => + hasRefinement(tp.underlying) + case tp: OrType => + hasRefinement(tp.tp1) || hasRefinement(tp.tp2) + case tp: AndType => + hasRefinement(tp.tp1) && hasRefinement(tp.tp2) + case _ => + false + } + !tree.symbol.exists && tree.isTerm && hasRefinement(tree.qualifier.tpe) + case _ => + false + } } object TreeInfo { diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 45e37eb8b..c900f0b64 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -26,6 +26,9 @@ object Definitions { * else without affecting the set of programs that can be compiled. */ val MaxImplementedFunctionArity = 22 + + /** The maximal arity of a function thta can be accessed as member of a structrual type */ + val MaxStructuralMethodArity = 7 } /** A class defining symbols and types of standard definitions @@ -505,6 +508,8 @@ class Definitions { lazy val LanguageModuleRef = ctx.requiredModule("scala.language") def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl") + lazy val ProjectorType: TypeRef = ctx.requiredClassRef("scala.Projector") + def ProjectorClass(implicit ctx: Context) = ProjectorType.symbol.asClass lazy val ClassTagType = ctx.requiredClassRef("scala.reflect.ClassTag") def ClassTagClass(implicit ctx: Context) = ClassTagType.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 716959648..f53571bdc 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -414,6 +414,7 @@ object StdNames { val genericArrayOps: N = "genericArrayOps" val get: N = "get" val getClass_ : N = "getClass" + val getMethod : N = "getMethod" val getOrElse: N = "getOrElse" val hasNext: N = "hasNext" val hashCode_ : N = "hashCode" diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index 4039c8b81..7cfd6327b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -7,10 +7,13 @@ 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 +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 { @@ -18,7 +21,10 @@ object Dynamic { name == nme.applyDynamic || name == nme.selectDynamic || name == nme.updateDynamic || name == nme.applyDynamicNamed } -/** Translates selection that does not typecheck according to the scala.Dynamic rules: +/** 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), ...) @@ -26,6 +32,10 @@ object Dynamic { * foo.bar ~~> foo.selectDynamic(bar) * * The first matching rule of is applied. + * + * 2. Translates member seclections on structural types by means of an implicit + * Projector instance. @See handleStructural. + * */ trait Dynamic { self: Typer with Applications => import Dynamic._ @@ -100,4 +110,54 @@ trait Dynamic { self: Typer with Applications => 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: + * + * implicitly[Projection[T]].get(x, "a").asInstanceOf[U] + * + * If `U` is a method type (T1,...,Tn)R, map `x.a` to the equivalent of: + * + * implicitly[Projection[T]].getMethod(x, "a")(CT1, ..., CTn).asInstanceOf[(T1,...,Tn) => R] + * + * where CT1,...,CTn are the classtags representing the erasure of T1,...,Tn. + * + * The small print: (1) T is forced to be fully defined. (2) 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 issueError(msgFn: String => String): Unit = ctx.error(msgFn("reflective call"), tree.pos) + def implicitArg(tpe: Type) = inferImplicitArg(tpe, issueError, tree.pos.endPos) + val projector = implicitArg(defn.ProjectorType.appliedTo(qual.tpe.widen)) + + def structuralCall(getterName: TermName, formals: List[Tree]) = { + val scall = untpd.Apply( + untpd.TypedSplice(projector.select(getterName)), + (qual :: 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") + fullyDefinedType(tree.tpe.widen, "structural access", tree.pos) 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") + val ctags = tpe.paramTypes.map(pt => + implicitArg(defn.ClassTagType.appliedTo(pt :: Nil))) + structuralCall(nme.getMethod, ctags).asInstance(tpe.toFunctionType()) + case tpe: ValueType => + structuralCall(nme.get, Nil).asInstance(tpe) + case tpe: PolyType => + fail("is polymorphic") + case tpe => + fail(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 6dd3f45fc..4c238518e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1039,9 +1039,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit for (refinement <- refinements1) { // TODO: get clarity whether we want to enforce these conditions typr.println(s"adding refinement $refinement") checkRefinementNonCyclic(refinement, refineCls, seen) - val rsym = refinement.symbol - if (rsym.is(Method) && rsym.allOverriddenSymbols.isEmpty) - ctx.error(i"refinement $rsym without matching type in parent $tpt1", refinement.pos) } assignType(cpy.RefinedTypeTree(tree)(tpt1, refinements1), tpt1, refinements1, refineCls) } @@ -1808,6 +1805,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case Apply(_, _) => " more" case _ => "" } + println(i"tree = $tree, pt = $pt") errorTree(tree, em"$methodStr does not take$more parameters") } } @@ -2045,7 +2043,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit adaptInterpolated(tree.appliedToTypeTrees(typeArgs), pt, original)) } case wtp => - pt match { + if (isStructuralTermSelect(tree)) adapt(handleStructural(tree), pt) + else pt match { case pt: FunProto => adaptToArgs(wtp, pt) case pt: PolyProto => |