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 { 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/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 3ecf650f6c89..026f3ceae6cf 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 } @@ -914,13 +915,19 @@ 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) } + /** 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] = Set(IntType, LongType, FloatType, DoubleType) 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/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..faa349c911e5 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 } + /** 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 tp => tp + } + /** The type constructor of an applied type, otherwise the type itself */ final def typeConstructor(implicit ctx: Context): Type = this match { case AppliedType(tycon, _) => tycon @@ -1312,15 +1318,18 @@ 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.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, @@ -2581,7 +2590,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, @@ -2688,7 +2697,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 { @@ -2723,6 +2732,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])( @@ -3197,8 +3220,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()" } @@ -3207,8 +3232,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) } @@ -3217,6 +3243,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? @@ -3731,7 +3758,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/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 78a0fe11f16b..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 { @@ -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)) @@ -1096,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/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/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 1689ac392031..39938d721443 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 => @@ -180,26 +184,19 @@ 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 tp: TypeParamRef => - ParamRefNameString(tp) ~ lambdaHash(tp.binder) - case tp: TermParamRef => - ParamRefNameString(tp) ~ ".type" case AnnotatedType(tpe, annot) => toTextLocal(tpe) ~ " " ~ toText(annot) case tp: TypeVar => @@ -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) 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/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index ae4bee6b8fd0..85932d11295a 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 = (tparams :: vparamss).flatten + 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 4d8910347fc1..23f1871c83e1 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 = { @@ -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 { @@ -1149,19 +1151,17 @@ class Namer { typer: Typer => vparamss foreach completeParams def typeParams = tparams map symbolOfTree - val paramSymss = ctx.normalizeIfConstructor(vparamss.nestedMap(symbolOfTree), isConstructor) + val termParamss = 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) + 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 = { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 983dfcc48e19..8b3ff0022771 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -683,174 +683,211 @@ 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.isFunctionType(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. - (pt.dealias.argTypesLo.init, pt.dealias.argTypesHi.last) - case SAMType(meth) => - val MethodTpe(_, formals, restpe) = meth.info - (formals, 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") { + 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) + + /** 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) + 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) + 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(params: List[untpd.ValDef], body) = tree - 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, resultTpt) = 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, params, 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) - } - typed(desugared, pt) } + + 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))) + 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") { @@ -861,13 +898,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) && 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}") @@ -1301,7 +1342,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) { @@ -1317,13 +1358,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 } @@ -1672,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) @@ -1693,7 +1728,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.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/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) 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/neg/depfuns.scala b/tests/neg/depfuns.scala new file mode 100644 index 000000000000..ac96915a78b5 --- /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..308b7aecc58d --- /dev/null +++ b/tests/pos/depfuntype.scala @@ -0,0 +1,35 @@ +object Test { + + trait C { type M; val m: M } + + type DF = (x: C) => x.M + val depfun1: DF = (x: C) => x.m + val c = new C { type M = Int; val m = 0 } + 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 = 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 +} 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) + +}