From 66b62800e2a746029661266b9e069137ede88162 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 12 Nov 2017 10:51:44 +0100 Subject: [PATCH 01/13] Add syntax for dependent function types --- .../dotty/tools/dotc/parsing/Parsers.scala | 30 +++++++++++++++++-- docs/docs/internals/syntax.md | 2 ++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 70c0b410c752..88e90ba915d0 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -735,6 +735,7 @@ object Parsers { * | InfixType * FunArgTypes ::= InfixType * | `(' [ FunArgType {`,' FunArgType } ] `)' + * | '(' TypedFunParam {',' TypedFunParam } ')' */ def typ(): Tree = { val start = in.offset @@ -745,6 +746,16 @@ object Parsers { val t = typ() if (isImplicit) new ImplicitFunction(params, t) else Function(params, t) } + def funArgTypesRest(first: Tree, following: () => Tree) = { + val buf = new ListBuffer[Tree] += first + while (in.token == COMMA) { + in.nextToken() + buf += following() + } + buf.toList + } + var isValParamList = false + val t = if (in.token == LPAREN) { in.nextToken() @@ -754,10 +765,19 @@ object Parsers { } else { openParens.change(LPAREN, 1) - val ts = commaSeparated(funArgType) + val paramStart = in.offset + val ts = funArgType() match { + case Ident(name) if name != tpnme.WILDCARD && in.token == COLON => + isValParamList = true + funArgTypesRest( + typedFunParam(paramStart, name.toTermName), + () => typedFunParam(in.offset, ident())) + case t => + funArgTypesRest(t, funArgType) + } openParens.change(LPAREN, -1) accept(RPAREN) - if (isImplicit || in.token == ARROW) functionRest(ts) + if (isImplicit || isValParamList || in.token == ARROW) functionRest(ts) else { for (t <- ts) if (t.isInstanceOf[ByNameTypeTree]) @@ -790,6 +810,12 @@ object Parsers { } } + /** TypedFunParam ::= id ':' Type */ + def typedFunParam(start: Offset, name: TermName): Tree = atPos(start) { + accept(COLON) + makeParameter(name, typ(), Modifiers(Param)) + } + /** InfixType ::= RefinedType {id [nl] refinedType} */ def infixType(): Tree = infixTypeRest(refinedType()) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 65f5e451349e..4f4552cbf3e9 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -122,6 +122,8 @@ Type ::= [‘implicit’] FunArgTypes ‘=>’ Type | InfixType FunArgTypes ::= InfixType | ‘(’ [ FunArgType {‘,’ FunArgType } ] ‘)’ + | '(' TypedFunParam {',' TypedFunParam } ')' +TypedFunParam ::= id ':' Type InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2) RefinedType ::= WithType {[nl] Refinement} RefinedTypeTree(t, ds) WithType ::= AnnotType {‘with’ AnnotType} (deprecated) From e31aa08cb5c14017dd7e24303a2942285add930b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 12 Nov 2017 12:35:53 +0100 Subject: [PATCH 02/13] Handle dependent function types in Typer --- .../src/dotty/tools/dotc/core/Symbols.scala | 3 + .../src/dotty/tools/dotc/core/Types.scala | 14 + .../tools/dotc/core/tasty/TreeUnpickler.scala | 3 +- .../src/dotty/tools/dotc/typer/Typer.scala | 306 ++++++++++-------- tests/neg/depfuns.scala | 5 + tests/pos/depfuntype.scala | 14 + 6 files changed, 205 insertions(+), 140 deletions(-) create mode 100644 tests/neg/depfuns.scala create mode 100644 tests/pos/depfuntype.scala diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 84ae7b4ee333..fd30a9b89212 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -130,6 +130,9 @@ trait Symbols { this: Context => newClassSymbol(owner, name, flags, completer, privateWithin, coord, assocFile) } + def newRefinedClassSymbol = newCompleteClassSymbol( + ctx.owner, tpnme.REFINE_CLASS, NonMember, parents = Nil) + /** Create a module symbol with associated module class * from its non-info fields and a function producing the info * of the module class (this info may be lazy). diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c53968543bcb..67ff4d371bd7 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2723,6 +2723,20 @@ object Types { def isParamDependent(implicit ctx: Context): Boolean = paramDependencyStatus == TrueDeps def newParamRef(n: Int) = new TermParamRef(this, n) {} + + /** The least supertype of `resultType` that does not contain parameter dependencies */ + def nonDependentResultApprox(implicit ctx: Context): Type = + if (isDependent) { + val dropDependencies = new ApproximatingTypeMap { + def apply(tp: Type) = tp match { + case tp @ TermParamRef(thisLambdaType, _) => + range(tp.bottomType, atVariance(1)(apply(tp.underlying))) + case _ => mapOver(tp) + } + } + dropDependencies(resultType) + } + else resultType } abstract case class MethodType(paramNames: List[TermName])( diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 78a0fe11f16b..dbd6be64e882 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -999,8 +999,7 @@ class TreeUnpickler(reader: TastyReader, nameAtRef: NameRef => TermName, posUnpi val argPats = until(end)(readTerm()) UnApply(fn, implicitArgs, argPats, patType) case REFINEDtpt => - val refineCls = ctx.newCompleteClassSymbol( - ctx.owner, tpnme.REFINE_CLASS, NonMember, parents = Nil) + val refineCls = ctx.newRefinedClassSymbol typeAtAddr(start) = refineCls.typeRef val parent = readTpt() val refinements = readStats(refineCls, end)(localContext(refineCls)) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 983dfcc48e19..3287515c7dd5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -697,160 +697,190 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } def typedFunction(tree: untpd.Function, pt: Type)(implicit ctx: Context) = track("typedFunction") { + if (ctx.mode is Mode.Type) typedFunctionType(tree, pt) + else typedFunctionValue(tree, pt) + } + + def typedFunctionType(tree: untpd.Function, pt: Type)(implicit ctx: Context) = { val untpd.Function(args, body) = tree - if (ctx.mode is Mode.Type) { - val isImplicit = tree match { - case _: untpd.ImplicitFunction => - if (args.length == 0) { - ctx.error(ImplicitFunctionTypeNeedsNonEmptyParameterList(), tree.pos) - false - } - else true - case _ => false - } - val funCls = defn.FunctionClass(args.length, isImplicit) - typed(cpy.AppliedTypeTree(tree)( - untpd.TypeTree(funCls.typeRef), args :+ body), pt) + val isImplicit = tree match { + case _: untpd.ImplicitFunction => + if (args.length == 0) { + ctx.error(ImplicitFunctionTypeNeedsNonEmptyParameterList(), tree.pos) + false + } + else true + case _ => false } - else { - val params = args.asInstanceOf[List[untpd.ValDef]] - - pt match { - case pt: TypeVar if untpd.isFunctionWithUnknownParamType(tree) => - // try to instantiate `pt` if this is possible. If it does not - // work the error will be reported later in `inferredParam`, - // when we try to infer the parameter type. - isFullyDefined(pt, ForceDegree.noBottom) - case _ => - } + val funCls = defn.FunctionClass(args.length, isImplicit) + + def typedDependent(params: List[ValDef])(implicit ctx: Context) = { + completeParams(params) + val params1 = params.map(typedExpr(_).asInstanceOf[ValDef]) + val resultTpt = typed(body) + val companion = if (isImplicit) ImplicitMethodType else MethodType + val mt = companion.fromSymbols(params1.map(_.symbol), resultTpt.tpe) + if (mt.isParamDependent) + ctx.error(i"$mt is an illegal function type because it has inter-parameter dependencies") + val resTpt = TypeTree(mt.nonDependentResultApprox).withPos(body.pos) + val typeArgs = params1.map(_.tpt) :+ resTpt + val tycon = TypeTree(funCls.typeRef) + val core = assignType(cpy.AppliedTypeTree(tree)(tycon, typeArgs), tycon, typeArgs) + val appMeth = ctx.newSymbol(ctx.owner, nme.apply, Synthetic | Deferred, mt) + val appDef = assignType( + untpd.DefDef(appMeth.name, Nil, List(params1), resultTpt, EmptyTree), + appMeth) + RefinedTypeTree(core, List(appDef), ctx.owner.asClass) + } + + args match { + case ValDef(_, _, _) :: _ => + typedDependent(args.asInstanceOf[List[ValDef]])( + ctx.fresh.setOwner(ctx.newRefinedClassSymbol).setNewScope) + case _ => + typed(cpy.AppliedTypeTree(tree)(untpd.TypeTree(funCls.typeRef), args :+ body), pt) + } + } - val (protoFormals, protoResult) = decomposeProtoFunction(pt, params.length) + def typedFunctionValue(tree: untpd.Function, pt: Type)(implicit ctx: Context) = { + val untpd.Function(args, body) = tree + val params = args.asInstanceOf[List[untpd.ValDef]] - def refersTo(arg: untpd.Tree, param: untpd.ValDef): Boolean = arg match { - case Ident(name) => name == param.name - case _ => false - } + pt match { + case pt: TypeVar if untpd.isFunctionWithUnknownParamType(tree) => + // try to instantiate `pt` if this is possible. If it does not + // work the error will be reported later in `inferredParam`, + // when we try to infer the parameter type. + isFullyDefined(pt, ForceDegree.noBottom) + case _ => + } - /** The function body to be returned in the closure. Can become a TypedSplice - * of a typed expression if this is necessary to infer a parameter type. - */ - var fnBody = tree.body + val (protoFormals, protoResult) = decomposeProtoFunction(pt, params.length) - /** A map from parameter names to unique positions where the parameter - * appears in the argument list of an application. - */ - var paramIndex = Map[Name, Int]() + def refersTo(arg: untpd.Tree, param: untpd.ValDef): Boolean = arg match { + case Ident(name) => name == param.name + case _ => false + } - /** If parameter `param` appears exactly once as an argument in `args`, - * the singleton list consisting of its position in `args`, otherwise `Nil`. - */ - def paramIndices(param: untpd.ValDef, args: List[untpd.Tree]): List[Int] = { - def loop(args: List[untpd.Tree], start: Int): List[Int] = args match { - case arg :: args1 => - val others = loop(args1, start + 1) - if (refersTo(arg, param)) start :: others else others - case _ => Nil - } - val allIndices = loop(args, 0) - if (allIndices.length == 1) allIndices else Nil + /** The function body to be returned in the closure. Can become a TypedSplice + * of a typed expression if this is necessary to infer a parameter type. + */ + var fnBody = tree.body + + /** A map from parameter names to unique positions where the parameter + * appears in the argument list of an application. + */ + var paramIndex = Map[Name, Int]() + + /** If parameter `param` appears exactly once as an argument in `args`, + * the singleton list consisting of its position in `args`, otherwise `Nil`. + */ + def paramIndices(param: untpd.ValDef, args: List[untpd.Tree]): List[Int] = { + def loop(args: List[untpd.Tree], start: Int): List[Int] = args match { + case arg :: args1 => + val others = loop(args1, start + 1) + if (refersTo(arg, param)) start :: others else others + case _ => Nil } + val allIndices = loop(args, 0) + if (allIndices.length == 1) allIndices else Nil + } - /** If function is of the form - * (x1, ..., xN) => f(... x1, ..., XN, ...) - * where each `xi` occurs exactly once in the argument list of `f` (in - * any order), the type of `f`, otherwise NoType. - * Updates `fnBody` and `paramIndex` as a side effect. - * @post: If result exists, `paramIndex` is defined for the name of - * every parameter in `params`. - */ - def calleeType: Type = fnBody match { - case Apply(expr, args) => - paramIndex = { - for (param <- params; idx <- paramIndices(param, args)) - yield param.name -> idx - }.toMap - if (paramIndex.size == params.length) - expr match { - case untpd.TypedSplice(expr1) => - expr1.tpe - case _ => - val protoArgs = args map (_ withType WildcardType) - val callProto = FunProto(protoArgs, WildcardType, this) - val expr1 = typedExpr(expr, callProto) - fnBody = cpy.Apply(fnBody)(untpd.TypedSplice(expr1), args) - expr1.tpe - } - else NoType + /** If function is of the form + * (x1, ..., xN) => f(... x1, ..., XN, ...) + * where each `xi` occurs exactly once in the argument list of `f` (in + * any order), the type of `f`, otherwise NoType. + * Updates `fnBody` and `paramIndex` as a side effect. + * @post: If result exists, `paramIndex` is defined for the name of + * every parameter in `params`. + */ + def calleeType: Type = fnBody match { + case Apply(expr, args) => + paramIndex = { + for (param <- params; idx <- paramIndices(param, args)) + yield param.name -> idx + }.toMap + if (paramIndex.size == params.length) + expr match { + case untpd.TypedSplice(expr1) => + expr1.tpe + case _ => + val protoArgs = args map (_ withType WildcardType) + val callProto = FunProto(protoArgs, WildcardType, this) + val expr1 = typedExpr(expr, callProto) + fnBody = cpy.Apply(fnBody)(untpd.TypedSplice(expr1), args) + expr1.tpe + } + else NoType + case _ => + NoType + } + + /** Two attempts: First, if expected type is fully defined pick this one. + * Second, if function is of the form + * (x1, ..., xN) => f(... x1, ..., XN, ...) + * where each `xi` occurs exactly once in the argument list of `f` (in + * any order), and f has a method type MT, pick the corresponding parameter + * type in MT, if this one is fully defined. + * If both attempts fail, issue a "missing parameter type" error. + */ + def inferredParamType(param: untpd.ValDef, formal: Type): Type = { + if (isFullyDefined(formal, ForceDegree.noBottom)) return formal + calleeType.widen match { + case mtpe: MethodType => + val pos = paramIndex(param.name) + if (pos < mtpe.paramInfos.length) { + val ptype = mtpe.paramInfos(pos) + if (isFullyDefined(ptype, ForceDegree.noBottom) && !ptype.isRepeatedParam) + return ptype + } case _ => - NoType } + errorType(AnonymousFunctionMissingParamType(param, args, tree, pt), param.pos) + } - /** Two attempts: First, if expected type is fully defined pick this one. - * Second, if function is of the form - * (x1, ..., xN) => f(... x1, ..., XN, ...) - * where each `xi` occurs exactly once in the argument list of `f` (in - * any order), and f has a method type MT, pick the corresponding parameter - * type in MT, if this one is fully defined. - * If both attempts fail, issue a "missing parameter type" error. - */ - def inferredParamType(param: untpd.ValDef, formal: Type): Type = { - if (isFullyDefined(formal, ForceDegree.noBottom)) return formal - calleeType.widen match { - case mtpe: MethodType => - val pos = paramIndex(param.name) - if (pos < mtpe.paramInfos.length) { - val ptype = mtpe.paramInfos(pos) - if (isFullyDefined(ptype, ForceDegree.noBottom) && !ptype.isRepeatedParam) - return ptype - } - case _ => - } - errorType(AnonymousFunctionMissingParamType(param, args, tree, pt), param.pos) - } + def protoFormal(i: Int): Type = + if (protoFormals.length == params.length) protoFormals(i) + else errorType(WrongNumberOfParameters(protoFormals.length), tree.pos) - def protoFormal(i: Int): Type = - if (protoFormals.length == params.length) protoFormals(i) - else errorType(WrongNumberOfParameters(protoFormals.length), tree.pos) - - /** Is `formal` a product type which is elementwise compatible with `params`? */ - def ptIsCorrectProduct(formal: Type) = { - isFullyDefined(formal, ForceDegree.noBottom) && - defn.isProductSubType(formal) && - Applications.productSelectorTypes(formal).corresponds(params) { - (argType, param) => - param.tpt.isEmpty || argType <:< typedAheadType(param.tpt).tpe - } + /** Is `formal` a product type which is elementwise compatible with `params`? */ + def ptIsCorrectProduct(formal: Type) = { + isFullyDefined(formal, ForceDegree.noBottom) && + defn.isProductSubType(formal) && + Applications.productSelectorTypes(formal).corresponds(params) { + (argType, param) => + param.tpt.isEmpty || argType <:< typedAheadType(param.tpt).tpe } + } - val desugared = - if (protoFormals.length == 1 && params.length != 1 && ptIsCorrectProduct(protoFormals.head)) { - desugar.makeTupledFunction(params, fnBody) - } - else { - val inferredParams: List[untpd.ValDef] = - for ((param, i) <- params.zipWithIndex) yield - if (!param.tpt.isEmpty) param - else cpy.ValDef(param)( - tpt = untpd.TypeTree( - inferredParamType(param, protoFormal(i)).underlyingIfRepeated(isJava = false))) - - // Define result type of closure as the expected type, thereby pushing - // down any implicit searches. We do this even if the expected type is not fully - // defined, which is a bit of a hack. But it's needed to make the following work - // (see typers.scala and printers/PlainPrinter.scala for examples). - // - // def double(x: Char): String = s"$x$x" - // "abc" flatMap double - // - val resultTpt = protoResult match { - case WildcardType(_) => untpd.TypeTree() - case _ => untpd.TypeTree(protoResult) - } - val inlineable = pt.hasAnnotation(defn.InlineParamAnnot) - desugar.makeClosure(inferredParams, fnBody, resultTpt, inlineable) + val desugared = + if (protoFormals.length == 1 && params.length != 1 && ptIsCorrectProduct(protoFormals.head)) { + desugar.makeTupledFunction(params, fnBody) + } + else { + val inferredParams: List[untpd.ValDef] = + for ((param, i) <- params.zipWithIndex) yield + if (!param.tpt.isEmpty) param + else cpy.ValDef(param)( + tpt = untpd.TypeTree( + inferredParamType(param, protoFormal(i)).underlyingIfRepeated(isJava = false))) + + // Define result type of closure as the expected type, thereby pushing + // down any implicit searches. We do this even if the expected type is not fully + // defined, which is a bit of a hack. But it's needed to make the following work + // (see typers.scala and printers/PlainPrinter.scala for examples). + // + // def double(x: Char): String = s"$x$x" + // "abc" flatMap double + // + val resultTpt = protoResult match { + case WildcardType(_) => untpd.TypeTree() + case _ => untpd.TypeTree(protoResult) } - typed(desugared, pt) - } + val inlineable = pt.hasAnnotation(defn.InlineParamAnnot) + desugar.makeClosure(inferredParams, fnBody, resultTpt, inlineable) + } + typed(desugared, pt) } def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context): Tree = track("typedClosure") { diff --git a/tests/neg/depfuns.scala b/tests/neg/depfuns.scala new file mode 100644 index 000000000000..56f217e2f36b --- /dev/null +++ b/tests/neg/depfuns.scala @@ -0,0 +1,5 @@ +object Test { + + type T = (x: Int) // error: `=>' expected + +} diff --git a/tests/pos/depfuntype.scala b/tests/pos/depfuntype.scala new file mode 100644 index 000000000000..93f36c193611 --- /dev/null +++ b/tests/pos/depfuntype.scala @@ -0,0 +1,14 @@ +object Test { + + trait C { type M; val m: M } + + type DF = (x: C) => x.M + val depfun: DF = ??? // (x: C) => x.m + val c = new C { type M = Int; val m = 0 } + val y = depfun(c) + val y1: Int = y + + val d: C = c + val z = depfun(d) + val z1: d.M = z +} \ No newline at end of file From e15ebaa2afb1344a5420a558b87a032d2477edd3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 12 Nov 2017 13:44:40 +0100 Subject: [PATCH 03/13] Drop AllowDependendFunctions mode bit Tests indicate it's not needed anymore. Also: fix neg test error position. --- compiler/src/dotty/tools/dotc/core/Mode.scala | 6 ------ compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++-- .../src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 4 ++-- tests/neg/depfuns.scala | 4 ++-- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 5affc2cb8fd4..c97e20a88d49 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -48,12 +48,6 @@ object Mode { /** Allow GADTFlexType labelled types to have their bounds adjusted */ val GADTflexible = newMode(8, "GADTflexible") - /** Allow dependent functions. This is currently necessary for unpickling, because - * some dependent functions are passed through from the front end(s?), even though they - * are technically speaking illegal. - */ - val AllowDependentFunctions = newMode(9, "AllowDependentFunctions") - /** We are currently printing something: avoid to produce more logs about * the printing */ diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 67ff4d371bd7..ab3946a19e52 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1312,12 +1312,12 @@ object Types { // ----- misc ----------------------------------------------------------- /** Turn type into a function type. - * @pre this is a non-dependent method type. + * @pre this is a method type without parameter dependencies. * @param dropLast The number of trailing parameters that should be dropped * when forming the function type. */ def toFunctionType(dropLast: Int = 0)(implicit ctx: Context): Type = this match { - case mt: MethodType if !mt.isDependent || ctx.mode.is(Mode.AllowDependentFunctions) => + case mt: MethodType if !mt.isDependent => val formals1 = if (dropLast == 0) mt.paramInfos else mt.paramInfos dropRight dropLast defn.FunctionOf( formals1 mapConserve (_.underlyingIfRepeated(mt.isJavaMethod)), mt.resultType, mt.isImplicitMethod && !ctx.erasedTypes) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index dbd6be64e882..09677d9490ec 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -74,7 +74,7 @@ class TreeUnpickler(reader: TastyReader, nameAtRef: NameRef => TermName, posUnpi /** The unpickled trees */ def unpickle()(implicit ctx: Context): List[Tree] = { assert(roots != null, "unpickle without previous enterTopLevel") - new TreeReader(reader).readTopLevel()(ctx.addMode(Mode.AllowDependentFunctions)) + new TreeReader(reader).readTopLevel() } class Completer(owner: Symbol, reader: TastyReader) extends LazyType { @@ -1095,7 +1095,7 @@ class TreeUnpickler(reader: TastyReader, nameAtRef: NameRef => TermName, posUnpi class LazyReader[T <: AnyRef](reader: TreeReader, op: TreeReader => Context => T) extends Trees.Lazy[T] { def complete(implicit ctx: Context): T = { pickling.println(i"starting to read at ${reader.reader.currentAddr}") - op(reader)(ctx.addMode(Mode.AllowDependentFunctions).withPhaseNoLater(ctx.picklerPhase)) + op(reader)(ctx.withPhaseNoLater(ctx.picklerPhase)) } } diff --git a/tests/neg/depfuns.scala b/tests/neg/depfuns.scala index 56f217e2f36b..ac96915a78b5 100644 --- a/tests/neg/depfuns.scala +++ b/tests/neg/depfuns.scala @@ -1,5 +1,5 @@ object Test { - type T = (x: Int) // error: `=>' expected + type T = (x: Int) -} +} // error: `=>' expected From 33869c300d9d5427c52ac72322491c2220023544 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 12 Nov 2017 18:25:50 +0100 Subject: [PATCH 04/13] Fix typing of dependent closures --- .../dotty/tools/dotc/core/Definitions.scala | 8 +++++- .../src/dotty/tools/dotc/core/Types.scala | 17 +++++++++--- .../src/dotty/tools/dotc/typer/Dynamic.scala | 4 +-- .../src/dotty/tools/dotc/typer/Namer.scala | 8 ++---- .../src/dotty/tools/dotc/typer/Typer.scala | 26 +++++++++---------- tests/pos/depfuntype.scala | 12 ++++++--- 6 files changed, 44 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 3ecf650f6c89..d6293b11d76f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -702,7 +702,8 @@ class Definitions { val tsym = ft.typeSymbol if (isFunctionClass(tsym)) { val targs = ft.dealias.argInfos - Some(targs.init, targs.last, tsym.name.isImplicitFunction) + if (targs.isEmpty) None + else Some(targs.init, targs.last, tsym.name.isImplicitFunction) } else None } @@ -921,6 +922,11 @@ class Definitions { arity >= 0 && isFunctionClass(sym) && tp.isRef(FunctionType(arity, sym.name.isImplicitFunction).typeSymbol) } + def isDependentFunctionType(tp: Type)(implicit ctx: Context) = tp.stripTypeVar match { + case RefinedType(parent, nme.apply, _) => isFunctionType(parent) + case _ => false + } + // Specialized type parameters defined for scala.Function{0,1,2}. private lazy val Function1SpecializedParams: collection.Set[Type] = Set(IntType, LongType, FloatType, DoubleType) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ab3946a19e52..3a6c3ae2fed0 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1014,6 +1014,12 @@ object Types { case _ => this } + /** If this is a dependent function type, drop the `apply` refinement */ + final def dropDependentRefinement(implicit ctx: Context): Type = stripTypeVar match { + case RefinedType(parent, nme.apply, _) => parent + case _ => this + } + /** The type constructor of an applied type, otherwise the type itself */ final def typeConstructor(implicit ctx: Context): Type = this match { case AppliedType(tycon, _) => tycon @@ -1317,10 +1323,13 @@ object Types { * when forming the function type. */ def toFunctionType(dropLast: Int = 0)(implicit ctx: Context): Type = this match { - case mt: MethodType if !mt.isDependent => + case mt: MethodType if !mt.isParamDependent => val formals1 = if (dropLast == 0) mt.paramInfos else mt.paramInfos dropRight dropLast - defn.FunctionOf( - formals1 mapConserve (_.underlyingIfRepeated(mt.isJavaMethod)), mt.resultType, mt.isImplicitMethod && !ctx.erasedTypes) + val funType = defn.FunctionOf( + formals1 mapConserve (_.underlyingIfRepeated(mt.isJavaMethod)), + mt.nonDependentResultApprox, mt.isImplicitMethod && !ctx.erasedTypes) + if (mt.isDependent) RefinedType(funType, nme.apply, mt) + else funType } /** The signature of this type. This is by default NotAMethod, @@ -3745,7 +3754,7 @@ object Types { // println(s"absMems: ${absMems map (_.show) mkString ", "}") if (absMems.size == 1) absMems.head.info match { - case mt: MethodType if !mt.isDependent => Some(absMems.head) + case mt: MethodType if !mt.isParamDependent => Some(absMems.head) case _ => None } else if (tp isRef defn.PartialFunctionClass) diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index ee5e9aab7195..f108c15b2d8d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -144,8 +144,8 @@ trait Dynamic { self: Typer with Applications => tree.tpe.widen match { case tpe: MethodType => - if (tpe.isDependent) - fail(i"has a dependent method type") + 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""") diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 4d8910347fc1..f22b6b4f5a35 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1150,12 +1150,8 @@ class Namer { typer: Typer => vparamss foreach completeParams def typeParams = tparams map symbolOfTree val paramSymss = ctx.normalizeIfConstructor(vparamss.nestedMap(symbolOfTree), isConstructor) - def wrapMethType(restpe: Type): Type = { - val restpe1 = // try to make anonymous functions non-dependent, so that they can be used in closures - if (name == nme.ANON_FUN) avoid(restpe, paramSymss.flatten) - else restpe - ctx.methodType(tparams map symbolOfTree, paramSymss, restpe1, isJava = ddef.mods is JavaDefined) - } + def wrapMethType(restpe: Type): Type = + ctx.methodType(tparams map symbolOfTree, paramSymss, restpe, isJava = ddef.mods is JavaDefined) if (isConstructor) { // set result type tree to unit, but take the current class as result type of the symbol typedAheadType(ddef.tpt, defn.UnitType) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3287515c7dd5..c49011487556 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -688,10 +688,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit // if expected parameter type(s) are wildcards, approximate from below. // if expected result type is a wildcard, approximate from above. // this can type the greatest set of admissible closures. - (pt.dealias.argTypesLo.init, pt.dealias.argTypesHi.last) + val funType = pt.dealias + (funType.argTypesLo.init, funType.argTypesHi.last) case SAMType(meth) => - val MethodTpe(_, formals, restpe) = meth.info - (formals, restpe) + val mt @ MethodTpe(_, formals, restpe) = meth.info + (formals, if (mt.isDependent) WildcardType else restpe) case _ => (List.tabulate(defaultArity)(alwaysWildcardType), WildcardType) } @@ -891,13 +892,17 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit meth1.tpe.widen match { case mt: MethodType => pt match { - case SAMType(meth) if !defn.isFunctionType(pt) && mt <:< meth.info => + case SAMType(meth) + if !defn.isFunctionType(pt.dealias.dropDependentRefinement) && mt <:< meth.info => if (!isFullyDefined(pt, ForceDegree.all)) ctx.error(ex"result type of closure is an underspecified SAM type $pt", tree.pos) TypeTree(pt) case _ => - if (!mt.isDependent) EmptyTree - else throw new java.lang.Error(i"internal error: cannot turn dependent method type $mt into closure, position = ${tree.pos}, raw type = ${mt.toString}") // !!! DEBUG. Eventually, convert to an error? + if (!mt.isParamDependent) EmptyTree + else throw new java.lang.Error( + i"""internal error: cannot turn method type $mt into closure + |because it has internal parameter dependencies, + |position = ${tree.pos}, raw type = ${mt.toString}""") // !!! DEBUG. Eventually, convert to an error? } case tp => throw new java.lang.Error(i"internal error: closing over non-method $tp, pos = ${tree.pos}") @@ -1331,7 +1336,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val vparamss1 = vparamss nestedMapconserve (typed(_).asInstanceOf[ValDef]) vparamss1.foreach(checkNoForwardDependencies) if (sym is Implicit) checkImplicitParamsNotSingletons(vparamss1) - var tpt1 = checkSimpleKinded(typedType(tpt)) + val tpt1 = checkSimpleKinded(typedType(tpt)) var rhsCtx = ctx if (sym.isConstructor && !sym.isPrimaryConstructor && tparams1.nonEmpty) { @@ -1347,13 +1352,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit // Overwrite inline body to make sure it is not evaluated twice if (sym.isInlineMethod) Inliner.registerInlineInfo(sym, _ => rhs1) - if (sym.isAnonymousFunction) { - // If we define an anonymous function, make sure the return type does not - // refer to parameters. This is necessary because closure types are - // function types so no dependencies on parameters are allowed. - tpt1 = tpt1.withType(avoid(tpt1.tpe, vparamss1.flatMap(_.map(_.symbol)))) - } - assignType(cpy.DefDef(ddef)(name, tparams1, vparamss1, tpt1, rhs1), sym) //todo: make sure dependent method types do not depend on implicits or by-name params } diff --git a/tests/pos/depfuntype.scala b/tests/pos/depfuntype.scala index 93f36c193611..4c5ec0798792 100644 --- a/tests/pos/depfuntype.scala +++ b/tests/pos/depfuntype.scala @@ -3,12 +3,16 @@ object Test { trait C { type M; val m: M } type DF = (x: C) => x.M - val depfun: DF = ??? // (x: C) => x.m + val depfun1: DF = (x: C) => x.m val c = new C { type M = Int; val m = 0 } - val y = depfun(c) + val y = depfun1(c) val y1: Int = y + def depmeth(x: C) = x.m + val depfun2 = depmeth + val depfun3: DF = depfun2 + val d: C = c - val z = depfun(d) + val z = depfun1(d) val z1: d.M = z -} \ No newline at end of file +} From 382e4cefe09972bf4e40e99f795dab823d91b65a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 12 Nov 2017 18:42:05 +0100 Subject: [PATCH 05/13] Dependent function types may not be implicit For the moment we want to rule this out. There are quite a lot of bits we have to think through before we can allow this. --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3a6c3ae2fed0..6ed23ae5cbe7 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1328,7 +1328,7 @@ object Types { val funType = defn.FunctionOf( formals1 mapConserve (_.underlyingIfRepeated(mt.isJavaMethod)), mt.nonDependentResultApprox, mt.isImplicitMethod && !ctx.erasedTypes) - if (mt.isDependent) RefinedType(funType, nme.apply, mt) + if (mt.isDependent && !mt.isImplicitMethod) RefinedType(funType, nme.apply, mt) else funType } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c49011487556..ae83a8b640aa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -719,10 +719,12 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit completeParams(params) val params1 = params.map(typedExpr(_).asInstanceOf[ValDef]) val resultTpt = typed(body) - val companion = if (isImplicit) ImplicitMethodType else MethodType - val mt = companion.fromSymbols(params1.map(_.symbol), resultTpt.tpe) + val mt = MethodType.fromSymbols(params1.map(_.symbol), resultTpt.tpe) if (mt.isParamDependent) - ctx.error(i"$mt is an illegal function type because it has inter-parameter dependencies") + ctx.error(i"$mt is an illegal function type because it has inter-parameter dependencies", tree.pos) + if (isImplicit) + ctx.error(i"dependent function type $mt may not be implicit", tree.pos) + val resTpt = TypeTree(mt.nonDependentResultApprox).withPos(body.pos) val typeArgs = params1.map(_.tpt) :+ resTpt val tycon = TypeTree(funCls.typeRef) From e013788b9e1895d84f8fef878a769db3d559a5f1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 12 Nov 2017 23:19:49 +0100 Subject: [PATCH 06/13] Fix problem with partly uninstantiated dependent functions If the result type of a dependent function contains a type variable whose bounds refer to the function's parameters, that type variable has to be instantiated before the method type is formed. Otherwise, if the type variable is instantiated later the method result type will refer to the original parameter symbols instead of the method types ParamRefs. --- .../dotty/tools/dotc/typer/Inferencing.scala | 21 +++++++++++++++++++ .../src/dotty/tools/dotc/typer/Namer.scala | 18 +++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index ae4bee6b8fd0..11b58e53e8f9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -49,6 +49,27 @@ object Inferencing { def instantiateSelected(tp: Type, tvars: List[Type])(implicit ctx: Context): Unit = new IsFullyDefinedAccumulator(new ForceDegree.Value(tvars.contains, minimizeAll = true)).process(tp) + /** Instantiate any type variables in `tp` whose bounds contain a reference to + * one of the parameters in `tparams` or `vparamss`. + */ + def instantiateDependent(tp: Type, tparams: List[Symbol], vparamss: List[List[Symbol]])(implicit ctx: Context): Unit = { + val dependentVars = new TypeAccumulator[Set[TypeVar]] { + lazy val params = (vparamss :\ tparams)( _ ::: _) + def apply(tvars: Set[TypeVar], tp: Type) = tp match { + case tp: TypeVar + if !tp.isInstantiated && + ctx.typeComparer.bounds(tp.origin) + .namedPartsWith(ref => params.contains(ref.symbol)) + .nonEmpty => + tvars + tp + case _ => + foldOver(tvars, tp) + } + } + val depVars = dependentVars(Set(), tp) + if (depVars.nonEmpty) instantiateSelected(tp, depVars.toList) + } + /** The accumulator which forces type variables using the policy encoded in `force` * and returns whether the type is fully defined. The direction in which * a type variable is instantiated is determined as follows: diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index f22b6b4f5a35..5b1f0b97d653 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -96,12 +96,12 @@ trait NamerContextOps { this: Context => else given /** if isConstructor, make sure it has one non-implicit parameter list */ - def normalizeIfConstructor(paramSymss: List[List[Symbol]], isConstructor: Boolean) = + def normalizeIfConstructor(termParamss: List[List[Symbol]], isConstructor: Boolean) = if (isConstructor && - (paramSymss.isEmpty || paramSymss.head.nonEmpty && (paramSymss.head.head is Implicit))) - Nil :: paramSymss + (termParamss.isEmpty || termParamss.head.nonEmpty && (termParamss.head.head is Implicit))) + Nil :: termParamss else - paramSymss + termParamss /** The method type corresponding to given parameters and result type */ def methodType(typeParams: List[Symbol], valueParamss: List[List[Symbol]], resultType: Type, isJava: Boolean = false)(implicit ctx: Context): Type = { @@ -1149,15 +1149,17 @@ class Namer { typer: Typer => vparamss foreach completeParams def typeParams = tparams map symbolOfTree - val paramSymss = ctx.normalizeIfConstructor(vparamss.nestedMap(symbolOfTree), isConstructor) - def wrapMethType(restpe: Type): Type = - ctx.methodType(tparams map symbolOfTree, paramSymss, restpe, isJava = ddef.mods is JavaDefined) + val termParamss = ctx.normalizeIfConstructor(vparamss.nestedMap(symbolOfTree), isConstructor) + def wrapMethType(restpe: Type): Type = { + instantiateDependent(restpe, typeParams, termParamss) + ctx.methodType(tparams map symbolOfTree, termParamss, restpe, isJava = ddef.mods is JavaDefined) + } if (isConstructor) { // set result type tree to unit, but take the current class as result type of the symbol typedAheadType(ddef.tpt, defn.UnitType) wrapMethType(ctx.effectiveResultType(sym, typeParams, NoType)) } - else valOrDefDefSig(ddef, sym, typeParams, paramSymss, wrapMethType) + else valOrDefDefSig(ddef, sym, typeParams, termParamss, wrapMethType) } def typeDefSig(tdef: TypeDef, sym: Symbol, tparamSyms: List[TypeSymbol])(implicit ctx: Context): Type = { From 5cb78bbb5bd7bf6f773b4f7554e79b5beffd6e02 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 12 Nov 2017 23:21:40 +0100 Subject: [PATCH 07/13] Refine isFunctionType `isFunctionType` nor returns true for dependent as well as non-dependent function types. `isNonDepFunctionType` returns true only for the latter. --- .../src/dotty/tools/dotc/core/Definitions.scala | 13 +++++++------ compiler/src/dotty/tools/dotc/core/Types.scala | 17 ++++++++++++----- compiler/src/dotty/tools/dotc/typer/Typer.scala | 4 ++-- tests/pos/depfuntype.scala | 2 +- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index d6293b11d76f..026f3ceae6cf 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -915,17 +915,18 @@ class Definitions { def isProductSubType(tp: Type)(implicit ctx: Context) = tp.derivesFrom(ProductType.symbol) - /** Is `tp` (an alias) of either a scala.FunctionN or a scala.ImplicitFunctionN? */ - def isFunctionType(tp: Type)(implicit ctx: Context) = { + /** Is `tp` (an alias) of either a scala.FunctionN or a scala.ImplicitFunctionN + * instance? + */ + def isNonDepFunctionType(tp: Type)(implicit ctx: Context) = { val arity = functionArity(tp) val sym = tp.dealias.typeSymbol arity >= 0 && isFunctionClass(sym) && tp.isRef(FunctionType(arity, sym.name.isImplicitFunction).typeSymbol) } - def isDependentFunctionType(tp: Type)(implicit ctx: Context) = tp.stripTypeVar match { - case RefinedType(parent, nme.apply, _) => isFunctionType(parent) - case _ => false - } + /** Is `tp` a representation of a (possibly depenent) function type or an alias of such? */ + def isFunctionType(tp: Type)(implicit ctx: Context) = + isNonDepFunctionType(tp.dropDependentRefinement) // Specialized type parameters defined for scala.Function{0,1,2}. private lazy val Function1SpecializedParams: collection.Set[Type] = diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 6ed23ae5cbe7..9fffc8ab1fea 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1328,7 +1328,10 @@ object Types { val funType = defn.FunctionOf( formals1 mapConserve (_.underlyingIfRepeated(mt.isJavaMethod)), mt.nonDependentResultApprox, mt.isImplicitMethod && !ctx.erasedTypes) - if (mt.isDependent && !mt.isImplicitMethod) RefinedType(funType, nme.apply, mt) + if (mt.isDependent) { + assert(!mt.isImplicitMethod) + RefinedType(funType, nme.apply, mt) + } else funType } @@ -2590,7 +2593,7 @@ object Types { def integrate(tparams: List[ParamInfo], tp: Type)(implicit ctx: Context): Type = tparams match { case LambdaParam(lam, _) :: _ => tp.subst(lam, this) - case tparams: List[Symbol @unchecked] => tp.subst(tparams, paramRefs) + case params: List[Symbol @unchecked] => tp.subst(params, paramRefs) } final def derivedLambdaType(paramNames: List[ThisName] = this.paramNames, @@ -2697,7 +2700,7 @@ object Types { * def f(x: C)(y: x.S) // dependencyStatus = TrueDeps * def f(x: C)(y: x.T) // dependencyStatus = FalseDeps, i.e. * // dependency can be eliminated by dealiasing. - */ + */ private def dependencyStatus(implicit ctx: Context): DependencyStatus = { if (myDependencyStatus != Unknown) myDependencyStatus else { @@ -3220,8 +3223,10 @@ object Types { case _ => false } + protected def kindString: String + override def toString = - try s"ParamRef($paramName)" + try s"${kindString}ParamRef($paramName)" catch { case ex: IndexOutOfBoundsException => s"ParamRef()" } @@ -3230,8 +3235,9 @@ object Types { /** Only created in `binder.paramRefs`. Use `binder.paramRefs(paramNum)` to * refer to `TermParamRef(binder, paramNum)`. */ - abstract case class TermParamRef(binder: TermLambda, paramNum: Int) extends ParamRef { + abstract case class TermParamRef(binder: TermLambda, paramNum: Int) extends ParamRef with SingletonType { type BT = TermLambda + def kindString = "Term" def copyBoundType(bt: BT) = bt.paramRefs(paramNum) } @@ -3240,6 +3246,7 @@ object Types { */ abstract case class TypeParamRef(binder: TypeLambda, paramNum: Int) extends ParamRef { type BT = TypeLambda + def kindString = "Type" def copyBoundType(bt: BT) = bt.paramRefs(paramNum) /** Looking only at the structure of `bound`, is one of the following true? diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ae83a8b640aa..72666017be4b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -684,7 +684,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } private def decomposeProtoFunction(pt: Type, defaultArity: Int)(implicit ctx: Context): (List[Type], Type) = pt match { - case _ if defn.isFunctionType(pt) => + case _ if defn.isNonDepFunctionType(pt) => // if expected parameter type(s) are wildcards, approximate from below. // if expected result type is a wildcard, approximate from above. // this can type the greatest set of admissible closures. @@ -895,7 +895,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case mt: MethodType => pt match { case SAMType(meth) - if !defn.isFunctionType(pt.dealias.dropDependentRefinement) && mt <:< meth.info => + if !defn.isFunctionType(pt) && mt <:< meth.info => if (!isFullyDefined(pt, ForceDegree.all)) ctx.error(ex"result type of closure is an underspecified SAM type $pt", tree.pos) TypeTree(pt) diff --git a/tests/pos/depfuntype.scala b/tests/pos/depfuntype.scala index 4c5ec0798792..e77ec042b928 100644 --- a/tests/pos/depfuntype.scala +++ b/tests/pos/depfuntype.scala @@ -13,6 +13,6 @@ object Test { val depfun3: DF = depfun2 val d: C = c - val z = depfun1(d) + val z = depfun3(d) val z1: d.M = z } From 7e2e042488c3673749219254bd91a16b93c4ed3e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 13 Nov 2017 00:32:01 +0100 Subject: [PATCH 08/13] Fix printing of TermParamRefs The change that TermParamRefs are Singletons caused a change how they are printed, which this commit reverts. We had before def f(x: T): x.type def f(x: T): x.type # M Once TermParamRefs were singletons we got: def f(x: T): T (x) def f(x: T): x.M With this commit we now get: def f(x: T): x.type def f(x: T): x.M --- .../src/dotty/tools/dotc/printing/PlainPrinter.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 1689ac392031..e31d87d9b295 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -146,10 +146,14 @@ class PlainPrinter(_ctx: Context) extends Printer { toTextRef(tp) ~ ".type" case tp: TermRef if tp.denot.isOverloaded => "" - case tp: SingletonType => - toTextLocal(tp.underlying) ~ "(" ~ toTextRef(tp) ~ ")" case tp: TypeRef => toTextPrefix(tp.prefix) ~ selectionString(tp) + case tp: TermParamRef => + ParamRefNameString(tp) ~ ".type" + case tp: TypeParamRef => + ParamRefNameString(tp) ~ lambdaHash(tp.binder) + case tp: SingletonType => + toTextLocal(tp.underlying) ~ "(" ~ toTextRef(tp) ~ ")" case AppliedType(tycon, args) => (toTextLocal(tycon) ~ "[" ~ Text(args map argText, ", ") ~ "]").close case tp: RefinedType => @@ -196,10 +200,6 @@ class PlainPrinter(_ctx: Context) extends Printer { "]" ~ lambdaHash(tp) ~ (" => " provided !tp.resultType.isInstanceOf[MethodType]) ~ toTextGlobal(tp.resultType) } - case tp: TypeParamRef => - ParamRefNameString(tp) ~ lambdaHash(tp.binder) - case tp: TermParamRef => - ParamRefNameString(tp) ~ ".type" case AnnotatedType(tpe, annot) => toTextLocal(tpe) ~ " " ~ toText(annot) case tp: TypeVar => From 9b6d0c08f77379f8c4be793747a28f3f6e1f89b1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Nov 2017 15:24:24 +0100 Subject: [PATCH 09/13] Implement implicit dependent function types --- compiler/src/dotty/tools/dotc/core/Types.scala | 5 +---- compiler/src/dotty/tools/dotc/typer/Typer.scala | 8 +++----- library/src/dotty/DottyPredef.scala | 1 + tests/pos/depfuntype.scala | 17 +++++++++++++++++ 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9fffc8ab1fea..133afe0aa845 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1328,10 +1328,7 @@ object Types { val funType = defn.FunctionOf( formals1 mapConserve (_.underlyingIfRepeated(mt.isJavaMethod)), mt.nonDependentResultApprox, mt.isImplicitMethod && !ctx.erasedTypes) - if (mt.isDependent) { - assert(!mt.isImplicitMethod) - RefinedType(funType, nme.apply, mt) - } + if (mt.isDependent) RefinedType(funType, nme.apply, mt) else funType } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 72666017be4b..cbba5558dda6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -719,12 +719,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit completeParams(params) val params1 = params.map(typedExpr(_).asInstanceOf[ValDef]) val resultTpt = typed(body) - val mt = MethodType.fromSymbols(params1.map(_.symbol), resultTpt.tpe) + val companion = if (isImplicit) ImplicitMethodType else MethodType + val mt = companion.fromSymbols(params1.map(_.symbol), resultTpt.tpe) if (mt.isParamDependent) ctx.error(i"$mt is an illegal function type because it has inter-parameter dependencies", tree.pos) - if (isImplicit) - ctx.error(i"dependent function type $mt may not be implicit", tree.pos) - val resTpt = TypeTree(mt.nonDependentResultApprox).withPos(body.pos) val typeArgs = params1.map(_.tpt) :+ resTpt val tycon = TypeTree(funCls.typeRef) @@ -1723,7 +1721,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } protected def makeImplicitFunction(tree: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = { - val defn.FunctionOf(formals, resType, true) = pt.dealias + val defn.FunctionOf(formals, _, true) = pt.dealias.dropDependentRefinement val paramTypes = formals.map(fullyDefinedType(_, "implicit function parameter", tree.pos)) val ifun = desugar.makeImplicitFunction(paramTypes, tree) typr.println(i"make implicit function $tree / $pt ---> $ifun") diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 09facf4b31f8..aebf577e5fc0 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -38,4 +38,5 @@ object DottyPredef { final def assertFail(): Unit = throw new java.lang.AssertionError("assertion failed") final def assertFail(message: => Any): Unit = throw new java.lang.AssertionError("assertion failed: " + message) + @inline final def implicitly[T](implicit ev: T): T = ev } diff --git a/tests/pos/depfuntype.scala b/tests/pos/depfuntype.scala index e77ec042b928..308b7aecc58d 100644 --- a/tests/pos/depfuntype.scala +++ b/tests/pos/depfuntype.scala @@ -15,4 +15,21 @@ object Test { val d: C = c val z = depfun3(d) val z1: d.M = z + + // Reproduced here because the one from DottyPredef is lacking a Tasty tree and + // therefore can't be inlined when testing non-bootstrapped. + // But inlining `implicitly` is vital to make the definition of `ifun` below work. + inline final def implicitly[T](implicit ev: T): T = ev + + type IDF = implicit (x: C) => x.M + + implicit val ic: C = ??? + + val ifun: IDF = implicitly[C].m + + val u = ifun(c) + val u1: Int = u + + val v = ifun(d) + val v1: d.M = v } From 9106bf14be3ebeb754159f8ab6bd7a44f2633f20 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Nov 2017 18:09:35 +0100 Subject: [PATCH 10/13] Add case for Inlined to TypedTreeCopier The missing case was discovered after inlining `implicitly`. It produced a type mismatch in Ycheck after lambda lift. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index df03eda317cc..334e2a467476 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -560,6 +560,15 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } + override def Inlined(tree: Tree)(call: Tree, bindings: List[MemberDef], expansion: Tree)(implicit ctx: Context): Inlined = { + val tree1 = untpd.cpy.Inlined(tree)(call, bindings, expansion) + tree match { + case tree: Inlined if sameTypes(bindings, tree.bindings) && (expansion.tpe eq tree.expansion.tpe) => + tree1.withTypeUnchecked(tree.tpe) + case _ => ta.assignType(tree1, bindings, expansion) + } + } + override def SeqLiteral(tree: Tree)(elems: List[Tree], elemtpt: Tree)(implicit ctx: Context): SeqLiteral = { val tree1 = untpd.cpy.SeqLiteral(tree)(elems, elemtpt) tree match { From 1e753c3dad59cd363f20e1842f55404146ad2a5d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Nov 2017 11:53:48 +0100 Subject: [PATCH 11/13] Print dependent function types correctly in RefinedPrinter --- .../dotty/tools/dotc/printing/PlainPrinter.scala | 14 ++++++++------ .../dotty/tools/dotc/printing/RefinedPrinter.scala | 8 ++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index e31d87d9b295..39938d721443 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -184,20 +184,17 @@ class PlainPrinter(_ctx: Context) extends Printer { case NoPrefix => "" case tp: MethodType => - def paramText(name: TermName, tp: Type) = toText(name) ~ ": " ~ toText(tp) changePrec(GlobalPrec) { - (if (tp.isImplicitMethod) "(implicit " else "(") ~ - Text((tp.paramNames, tp.paramInfos).zipped map paramText, ", ") ~ + (if (tp.isImplicitMethod) "(implicit " else "(") ~ paramsText(tp) ~ (if (tp.resultType.isInstanceOf[MethodType]) ")" else "): ") ~ toText(tp.resultType) } case tp: ExprType => changePrec(GlobalPrec) { "=> " ~ toText(tp.resultType) } case tp: TypeLambda => - def paramText(name: Name, bounds: TypeBounds): Text = name.unexpandedName.toString ~ toText(bounds) changePrec(GlobalPrec) { - "[" ~ Text((tp.paramNames, tp.paramInfos).zipped.map(paramText), ", ") ~ - "]" ~ lambdaHash(tp) ~ (" => " provided !tp.resultType.isInstanceOf[MethodType]) ~ + "[" ~ paramsText(tp) ~ "]" ~ lambdaHash(tp) ~ + (" => " provided !tp.resultType.isInstanceOf[MethodType]) ~ toTextGlobal(tp.resultType) } case AnnotatedType(tpe, annot) => @@ -221,6 +218,11 @@ class PlainPrinter(_ctx: Context) extends Printer { } }.close + protected def paramsText(tp: LambdaType): Text = { + def paramText(name: Name, tp: Type) = toText(name) ~ toTextRHS(tp) + Text((tp.paramNames, tp.paramInfos).zipped.map(paramText), ", ") + } + protected def ParamRefNameString(name: Name): String = name.toString protected def ParamRefNameString(param: ParamRef): String = diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 4a83041e35ae..8d0b3cf4e783 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -116,6 +116,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { override def toText(tp: Type): Text = controlled { def toTextTuple(args: List[Type]): Text = "(" ~ Text(args.map(argText), ", ") ~ ")" + def toTextFunction(args: List[Type], isImplicit: Boolean): Text = changePrec(GlobalPrec) { val argStr: Text = @@ -126,6 +127,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { ("implicit " provided isImplicit) ~ argStr ~ " => " ~ argText(args.last) } + def toTextDependentFunction(appType: MethodType): Text = { + ("implicit " provided appType.isImplicitMethod) ~ + "(" ~ paramsText(appType) ~ ") => " ~ toText(appType.resultType) + } + def isInfixType(tp: Type): Boolean = tp match { case AppliedType(tycon, args) => args.length == 2 && @@ -158,6 +164,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (isInfixType(tp)) return toTextInfixType(tycon, args) case EtaExpansion(tycon) => return toText(tycon) + case tp: RefinedType if defn.isFunctionType(tp) => + return toTextDependentFunction(tp.refinedInfo.asInstanceOf[MethodType]) case tp: TypeRef => if (tp.symbol.isAnonymousClass && !ctx.settings.uniqid.value) return toText(tp.info) From b6642e6099eb4cdfb1dfe4edc721e93d8429b82c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Nov 2017 15:41:52 +0100 Subject: [PATCH 12/13] Address reviewers comments --- compiler/src/dotty/tools/dotc/core/Types.scala | 6 +++--- compiler/src/dotty/tools/dotc/typer/Inferencing.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 133afe0aa845..faa349c911e5 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1014,10 +1014,10 @@ object Types { case _ => this } - /** If this is a dependent function type, drop the `apply` refinement */ - final def dropDependentRefinement(implicit ctx: Context): Type = stripTypeVar match { + /** Dealias, and if result is a dependent function type, drop the `apply` refinement. */ + final def dropDependentRefinement(implicit ctx: Context): Type = dealias match { case RefinedType(parent, nme.apply, _) => parent - case _ => this + case tp => tp } /** The type constructor of an applied type, otherwise the type itself */ diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 11b58e53e8f9..85932d11295a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -54,7 +54,7 @@ object Inferencing { */ def instantiateDependent(tp: Type, tparams: List[Symbol], vparamss: List[List[Symbol]])(implicit ctx: Context): Unit = { val dependentVars = new TypeAccumulator[Set[TypeVar]] { - lazy val params = (vparamss :\ tparams)( _ ::: _) + lazy val params = (tparams :: vparamss).flatten def apply(tvars: Set[TypeVar], tp: Type) = tp match { case tp: TypeVar if !tp.isInstantiated && diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index cbba5558dda6..d42752632668 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -715,7 +715,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } val funCls = defn.FunctionClass(args.length, isImplicit) - def typedDependent(params: List[ValDef])(implicit ctx: Context) = { + /** Typechecks dependent function type with given parameters `params` */ + def typedDependent(params: List[ValDef])(implicit ctx: Context): Tree = { completeParams(params) val params1 = params.map(typedExpr(_).asInstanceOf[ValDef]) val resultTpt = typed(body) @@ -744,8 +745,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } def typedFunctionValue(tree: untpd.Function, pt: Type)(implicit ctx: Context) = { - val untpd.Function(args, body) = tree - val params = args.asInstanceOf[List[untpd.ValDef]] + val untpd.Function(params: List[untpd.ValDef], body) = tree pt match { case pt: TypeVar if untpd.isFunctionWithUnknownParamType(tree) => @@ -837,7 +837,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } case _ => } - errorType(AnonymousFunctionMissingParamType(param, args, tree, pt), param.pos) + errorType(AnonymousFunctionMissingParamType(param, params, tree, pt), param.pos) } def protoFormal(i: Int): Type = @@ -1721,7 +1721,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } protected def makeImplicitFunction(tree: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = { - val defn.FunctionOf(formals, _, true) = pt.dealias.dropDependentRefinement + val defn.FunctionOf(formals, _, true) = pt.dropDependentRefinement val paramTypes = formals.map(fullyDefinedType(_, "implicit function parameter", tree.pos)) val ifun = desugar.makeImplicitFunction(paramTypes, tree) typr.println(i"make implicit function $tree / $pt ---> $ifun") From 28a29eabf7a64901706bae075402f2b023ec0e65 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 15 Nov 2017 18:30:46 +0100 Subject: [PATCH 13/13] Improve type inference for dependent function types Given a dependently typed function value like this one: def f: (x: C) => D => x.T => E we did not propagate information about the subsequent types `D` and `x.T` to the result type of the closure with parameter `(x: C)`. Doing so is a bit tricky because of the dependency. But it's necessary to infer the types of subsequent parameters. Test case: eff-dependent.scala --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 1 + .../src/dotty/tools/dotc/typer/Namer.scala | 2 + .../src/dotty/tools/dotc/typer/Typer.scala | 61 +++++++++++-------- tests/run/eff-dependent.scala | 38 ++++++++++++ 4 files changed, 75 insertions(+), 27 deletions(-) create mode 100644 tests/run/eff-dependent.scala diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index aae73844958c..e4b34d59c189 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -86,6 +86,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class GenAlias(pat: Tree, expr: Tree) extends Tree case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree]) extends TypTree case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree) extends DefTree + case class DependentTypeTree(tp: List[Symbol] => Type) extends Tree @sharable object EmptyTypeIdent extends Ident(tpnme.EMPTY) with WithoutTypeOrPos[Untyped] { override def isEmpty = true diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 5b1f0b97d653..23f1871c83e1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1096,6 +1096,8 @@ class Namer { typer: Typer => WildcardType case TypeTree() => inferredType + case DependentTypeTree(tpFun) => + tpFun(paramss.head) case TypedSplice(tpt: TypeTree) if !isFullyDefined(tpt.tpe, ForceDegree.none) => val rhsType = typedAheadExpr(mdef.rhs, tpt.tpe).tpe mdef match { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d42752632668..8b3ff0022771 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -683,18 +683,37 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit assignType(cpy.If(tree)(cond1, thenp2, elsep2), thenp2, elsep2) } - private def decomposeProtoFunction(pt: Type, defaultArity: Int)(implicit ctx: Context): (List[Type], Type) = pt match { - case _ if defn.isNonDepFunctionType(pt) => - // if expected parameter type(s) are wildcards, approximate from below. - // if expected result type is a wildcard, approximate from above. - // this can type the greatest set of admissible closures. - val funType = pt.dealias - (funType.argTypesLo.init, funType.argTypesHi.last) - case SAMType(meth) => - val mt @ MethodTpe(_, formals, restpe) = meth.info - (formals, if (mt.isDependent) WildcardType else restpe) - case _ => - (List.tabulate(defaultArity)(alwaysWildcardType), WildcardType) + /** Decompose function prototype into a list of parameter prototypes and a result prototype + * tree, using WildcardTypes where a type is not known. + * For the result type we do this even if the expected type is not fully + * defined, which is a bit of a hack. But it's needed to make the following work + * (see typers.scala and printers/PlainPrinter.scala for examples). + * + * def double(x: Char): String = s"$x$x" + * "abc" flatMap double + */ + private def decomposeProtoFunction(pt: Type, defaultArity: Int)(implicit ctx: Context): (List[Type], untpd.Tree) = { + def typeTree(tp: Type) = tp match { + case _: WildcardType => untpd.TypeTree() + case _ => untpd.TypeTree(tp) + } + pt match { + case _ if defn.isNonDepFunctionType(pt) => + // if expected parameter type(s) are wildcards, approximate from below. + // if expected result type is a wildcard, approximate from above. + // this can type the greatest set of admissible closures. + val funType = pt.dealias + (funType.argTypesLo.init, typeTree(funType.argTypesHi.last)) + case SAMType(meth) => + val mt @ MethodTpe(_, formals, restpe) = meth.info + (formals, + if (mt.isDependent) + untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef))) + else + typeTree(restpe)) + case _ => + (List.tabulate(defaultArity)(alwaysWildcardType), untpd.TypeTree()) + } } def typedFunction(tree: untpd.Function, pt: Type)(implicit ctx: Context) = track("typedFunction") { @@ -756,7 +775,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case _ => } - val (protoFormals, protoResult) = decomposeProtoFunction(pt, params.length) + val (protoFormals, resultTpt) = decomposeProtoFunction(pt, params.length) def refersTo(arg: untpd.Tree, param: untpd.ValDef): Boolean = arg match { case Ident(name) => name == param.name @@ -865,19 +884,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit else cpy.ValDef(param)( tpt = untpd.TypeTree( inferredParamType(param, protoFormal(i)).underlyingIfRepeated(isJava = false))) - - // Define result type of closure as the expected type, thereby pushing - // down any implicit searches. We do this even if the expected type is not fully - // defined, which is a bit of a hack. But it's needed to make the following work - // (see typers.scala and printers/PlainPrinter.scala for examples). - // - // def double(x: Char): String = s"$x$x" - // "abc" flatMap double - // - val resultTpt = protoResult match { - case WildcardType(_) => untpd.TypeTree() - case _ => untpd.TypeTree(protoResult) - } val inlineable = pt.hasAnnotation(defn.InlineParamAnnot) desugar.makeClosure(inferredParams, fnBody, resultTpt, inlineable) } @@ -1700,7 +1706,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case tree: untpd.PackageDef => typedPackageDef(tree) case tree: untpd.Annotated => typedAnnotated(tree, pt) case tree: untpd.TypedSplice => typedTypedSplice(tree) - case tree: untpd.UnApply => typedUnApply(tree, pt) + case tree: untpd.UnApply => typedUnApply(tree, pt) + case tree: untpd.DependentTypeTree => typed(untpd.TypeTree().withPos(tree.pos), pt) case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt) case untpd.EmptyTree => tpd.EmptyTree case _ => typedUnadapted(desugar(tree), pt) diff --git a/tests/run/eff-dependent.scala b/tests/run/eff-dependent.scala new file mode 100644 index 000000000000..0e86494b1165 --- /dev/null +++ b/tests/run/eff-dependent.scala @@ -0,0 +1,38 @@ +object Test extends App { + + trait Effect + + // Type X => Y + abstract class Fun[-X, +Y] { + type Eff <: Effect + def apply(x: X): implicit Eff => Y + } + + class CanThrow extends Effect + class CanIO extends Effect + + val i2s = new Fun[Int, String] { type Eff = CanThrow; def apply(x: Int) = x.toString } + val s2i = new Fun[String, Int] { type Eff = CanIO; def apply(x: String) = x.length } + + implicit val ct: CanThrow = new CanThrow + implicit val ci: CanIO = new CanIO + + // def map(f: A => B)(xs: List[A]): List[B] + def map[A, B](f: Fun[A, B])(xs: List[A]): implicit f.Eff => List[B] = + xs.map(f.apply) + + // def mapFn[A, B]: (A => B) -> List[A] -> List[B] + def mapFn[A, B]: (f: Fun[A, B]) => List[A] => implicit f.Eff => List[B] = + f => xs => map(f)(xs) + + // def compose(f: A => B)(g: B => C)(x: A): C + def compose[A, B, C](f: Fun[A, B])(g: Fun[B, C])(x: A): implicit f.Eff => implicit g.Eff => C = g(f(x)) + + // def composeFn: (A => B) -> (B => C) -> A -> C + def composeFn[A, B, C]: (f: Fun[A, B]) => (g: Fun[B, C]) => A => implicit f.Eff => implicit g.Eff => C = + f => g => x => compose(f)(g)(x) + + assert(mapFn(i2s)(List(1, 2, 3)).mkString == "123") + assert(composeFn(i2s)(s2i)(22) == 2) + +}