From 5e59f87531db5328cadc7976bdfb515196bdf0b4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 7 Feb 2020 10:21:09 +0100 Subject: [PATCH 1/4] Fix a problem in IsFullyDefinedAccumulator A maximized type variable could be instantiated to a type that contains more type variables. These have to be instantiated in turn. --- compiler/src/dotty/tools/dotc/typer/Inferencing.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 0a215887cc06..e579641c3593 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -150,9 +150,14 @@ object Inferencing { if !tvar.isInstantiated then instantiate(tvar, fromBelow = false) case nil => - val res = apply(true, tp) - if res then maximize(toMaximize) - res + apply(true, tp) + && ( + toMaximize.isEmpty + || { maximize(toMaximize) + toMaximize = Nil // Do another round since the maximixing instances + process(tp) // might have type uninstantiated variables themselves. + } + ) } /** For all type parameters occurring in `tp`: From 4c84ee886ab4ea7e60e4dbf284474a4e1799ae07 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 7 Feb 2020 11:29:36 +0100 Subject: [PATCH 2/4] Don't lock in failed callee type typings When we reach out to get the callee type, we might get an error, e.g. if the callee is overloaded. Example: ``` ch => sb.append(ch) ``` Here we might want to call calleeType to find the argument type of `sb.append` in order to use this type as the type of `ch`. But this fails since `append` is overloaded on StringBuffer. Prevously this did not matter since we called calleeType only as a last effort, so if it failed the whole typing failed. But with the changes forseen for fiing #8111 we need to be able to compute calleeType and retract it if it does not work. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index cb770d5619f2..4fd9a6ad6859 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1013,11 +1013,16 @@ class Typer extends Namer case untpd.TypedSplice(expr1) => expr1.tpe case _ => + given nestedCtx as Context = ctx.fresh.setNewTyperState() val protoArgs = args map (_ withType WildcardType) val callProto = FunProto(protoArgs, WildcardType)(this, app.isGivenApply) val expr1 = typedExpr(expr, callProto) - fnBody = cpy.Apply(fnBody)(untpd.TypedSplice(expr1), args) - expr1.tpe + if nestedCtx.reporter.hasErrors then NoType + else + given Context = ctx + nestedCtx.typerState.commit() + fnBody = cpy.Apply(fnBody)(untpd.TypedSplice(expr1), args) + expr1.tpe } else NoType case _ => From 914787c5e0664c49be550784079cf7cdcc4e1317 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 7 Feb 2020 10:22:05 +0100 Subject: [PATCH 3/4] Fix #8111: Improve inferredParam An inferred parameter type I has two passible sources: - the type S known from the context - the type T known from the callee `f` if the lambda is of a form like `x => f(x)` If `T` exists, we know that `S <: I <: T`. In this commit we make use of this information to be more intelligent how the type I is inferred. --- .../src/dotty/tools/dotc/typer/Typer.scala | 51 +++++++++++-------- tests/pos/i8111.scala | 10 ++++ 2 files changed, 40 insertions(+), 21 deletions(-) create mode 100644 tests/pos/i8111.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4fd9a6ad6859..064f53d0a3b4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1009,7 +1009,7 @@ class Typer extends Namer yield param.name -> idx }.toMap if (paramIndex.size == params.length) - expr match { + expr match case untpd.TypedSplice(expr1) => expr1.tpe case _ => @@ -1023,7 +1023,6 @@ class Typer extends Namer nestedCtx.typerState.commit() fnBody = cpy.Apply(fnBody)(untpd.TypedSplice(expr1), args) expr1.tpe - } else NoType case _ => NoType @@ -1041,28 +1040,38 @@ class Typer extends Namer val (protoFormals, resultTpt) = decomposeProtoFunction(pt, params.length) - /** 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 { + /** The inferred parameter type for a parameter in a lambda that does + * not have an explicit type given. + * An inferred parameter type I has two possible sources: + * - the type S known from the context + * - the "target type" T known from the callee `f` if the lambda is of a form like `x => f(x)` + * If `T` exists, we know that `S <: I <: T`. + * + * The inference makes three attempts: + * + * 1. If the expected type `S` is already fully defined pick this one. + * 2. Compute the target type `T` and make it known that `S <: T`. + * If the expected type `S` can be fully defined under ForceDegree.noBottom, + * pick this one (this might use the fact that S <: T for an upper approximation). + * 3. Otherwise, if the target type `T` can be fully defined under ForceDegree.noBottom, + * pick this one. + * + * If all attempts fail, issue a "missing parameter type" error. + */ + def inferredParamType(param: untpd.ValDef, formal: Type): Type = + if isFullyDefined(formal, ForceDegree.none) then return formal + val target = calleeType.widen match case mtpe: MethodType => val pos = paramIndex(param.name) - if (pos < mtpe.paramInfos.length) { + if pos < mtpe.paramInfos.length then val ptype = mtpe.paramInfos(pos) - if (isFullyDefined(ptype, ForceDegree.noBottom) && !ptype.isRepeatedParam) - return ptype - } - case _ => - } - errorType(AnonymousFunctionMissingParamType(param, params, tree, formal), param.sourcePos) - } + if ptype.isRepeatedParam then NoType else ptype + else NoType + case _ => NoType + if target.exists then formal <:< target + if isFullyDefined(formal, ForceDegree.noBottom) then formal + else if target.exists && isFullyDefined(target, ForceDegree.noBottom) then target + else errorType(AnonymousFunctionMissingParamType(param, params, tree, formal), param.sourcePos) def protoFormal(i: Int): Type = if (protoFormals.length == params.length) protoFormals(i) diff --git a/tests/pos/i8111.scala b/tests/pos/i8111.scala new file mode 100644 index 000000000000..cd7cc1d638a9 --- /dev/null +++ b/tests/pos/i8111.scala @@ -0,0 +1,10 @@ +object Example extends App { + + def assertLazy[A, B](f: (A) => B): Boolean = ??? + + def fromEither[E, F](eea: Either[E, F]): Unit = ??? + + lazy val result = assertLazy(fromEither) + + println("It compiles!") +} \ No newline at end of file From 9f10d77a23aa4b796d9b2831fb16b77a4969ede5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 7 Feb 2020 19:14:37 +0100 Subject: [PATCH 4/4] Refine handling of bottom types in inferredParam We need to split `notBottom` into two states - flipBottom the old noBottom: when encountering bottom, maximimze instead - failBottom when encountering bottom, return false That way, we can have an inferredParamType which is closer to the old one. Try first to instantiate the formal type known from the context, but fail on bottom. If that fails, compute target type and proceed as before. --- .../src/dotty/tools/dotc/core/TypeOps.scala | 3 ++- .../dotty/tools/dotc/typer/Applications.scala | 2 +- .../dotty/tools/dotc/typer/Inferencing.scala | 20 ++++++++++------ .../src/dotty/tools/dotc/typer/Inliner.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 23 ++++++++++--------- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 65a42cd42561..5e2a815c972b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -19,6 +19,7 @@ import typer.Applications._ import typer.ProtoTypes._ import typer.ForceDegree import typer.Inferencing.isFullyDefined +import typer.IfBottom import scala.annotation.internal.sharable @@ -644,7 +645,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. tvar => !(ctx.typerState.constraint.entry(tvar.origin) `eq` tvar.origin.underlying) || (tvar `eq` removeThisType.prefixTVar), - allowBottom = false + IfBottom.flip ) // If parent contains a reference to an abstract type, then we should diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index a91dda90b85d..7d899ec22469 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1865,7 +1865,7 @@ trait Applications extends Compatibility { if (isPartial) defn.PartialFunctionOf(commonParamTypes.head, WildcardType) else defn.FunctionOf(commonParamTypes, WildcardType) overload.println(i"pretype arg $arg with expected type $commonFormal") - if (commonParamTypes.forall(isFullyDefined(_, ForceDegree.noBottom))) + if (commonParamTypes.forall(isFullyDefined(_, ForceDegree.flipBottom))) pt.typedArg(arg, commonFormal)(ctx.addMode(Mode.ImplicitsEnabled)) } case None => diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index e579641c3593..b640ca48de61 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -55,7 +55,7 @@ object Inferencing { def instantiateSelected(tp: Type, tvars: List[Type])(implicit ctx: Context): Unit = if (tvars.nonEmpty) IsFullyDefinedAccumulator( - ForceDegree.Value(tvars.contains, allowBottom = false), minimizeSelected = true + ForceDegree.Value(tvars.contains, IfBottom.flip), minimizeSelected = true ).process(tp) /** Instantiate any type variables in `tp` whose bounds contain a reference to @@ -98,7 +98,7 @@ object Inferencing { * If (1) and (2) do not apply, and minimizeSelected is not set: * 6: T is maximized if it appears only contravariantly in the given type, - * or if forceDegree is `noBottom` and T has no lower bound different from Nothing. + * or if forceDegree is `flipBottom` and T has no lower bound different from Nothing. * 7. Otherwise, T is minimized. * * The instantiation for (6) and (7) is done in two phases: @@ -132,8 +132,10 @@ object Inferencing { if tvar.hasLowerBound then instantiate(tvar, fromBelow = true) else if tvar.hasUpperBound then instantiate(tvar, fromBelow = false) else () // hold off instantiating unbounded unconstrained variables - else if variance >= 0 && (force.allowBottom || tvar.hasLowerBound) then + else if variance >= 0 && (force.ifBottom == IfBottom.ok || tvar.hasLowerBound) then instantiate(tvar, fromBelow = true) + else if variance >= 0 && force.ifBottom == IfBottom.fail then + return false else toMaximize = tvar :: toMaximize foldOver(x, tvar) @@ -514,9 +516,13 @@ trait Inferencing { this: Typer => /** An enumeration controlling the degree of forcing in "is-dully-defined" checks. */ @sharable object ForceDegree { - class Value(val appliesTo: TypeVar => Boolean, val allowBottom: Boolean) - val none: Value = new Value(_ => false, allowBottom = true) - val all: Value = new Value(_ => true, allowBottom = true) - val noBottom: Value = new Value(_ => true, allowBottom = false) + class Value(val appliesTo: TypeVar => Boolean, val ifBottom: IfBottom) + val none: Value = new Value(_ => false, IfBottom.ok) + val all: Value = new Value(_ => true, IfBottom.ok) + val failBottom: Value = new Value(_ => true, IfBottom.fail) + val flipBottom: Value = new Value(_ => true, IfBottom.flip) } +enum IfBottom: + case ok, fail, flip + diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index ff468264da3b..aebff8891d39 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -276,7 +276,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { // Make sure all type arguments to the call are fully determined, // but continue if that's not achievable (or else i7459.scala would crash). for arg <- callTypeArgs do - isFullyDefined(arg.tpe, ForceDegree.noBottom) + isFullyDefined(arg.tpe, ForceDegree.flipBottom) /** A map from parameter names of the inlineable method to references of the actual arguments. * For a type argument this is the full argument type. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 064f53d0a3b4..dbf6d70835c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -612,7 +612,7 @@ class Typer extends Namer var templ1 = templ def isEligible(tp: Type) = tp.exists && !tp.typeSymbol.is(Final) && !tp.isRef(defn.AnyClass) if (templ1.parents.isEmpty && - isFullyDefined(pt, ForceDegree.noBottom) && + isFullyDefined(pt, ForceDegree.flipBottom) && isSkolemFree(pt) && isEligible(pt.underlyingClassRef(refinementOK = false))) templ1 = cpy.Template(templ)(parents = untpd.TypeTree(pt) :: Nil) @@ -1034,7 +1034,7 @@ class Typer extends Namer // 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) + isFullyDefined(pt, ForceDegree.flipBottom) case _ => } @@ -1049,17 +1049,18 @@ class Typer extends Namer * * The inference makes three attempts: * - * 1. If the expected type `S` is already fully defined pick this one. + * 1. If the expected type `S` is already fully defined under ForceDegree.failBottom + * pick this one. * 2. Compute the target type `T` and make it known that `S <: T`. - * If the expected type `S` can be fully defined under ForceDegree.noBottom, + * If the expected type `S` can be fully defined under ForceDegree.flipBottom, * pick this one (this might use the fact that S <: T for an upper approximation). - * 3. Otherwise, if the target type `T` can be fully defined under ForceDegree.noBottom, + * 3. Otherwise, if the target type `T` can be fully defined under ForceDegree.flipBottom, * pick this one. * * If all attempts fail, issue a "missing parameter type" error. */ def inferredParamType(param: untpd.ValDef, formal: Type): Type = - if isFullyDefined(formal, ForceDegree.none) then return formal + if isFullyDefined(formal, ForceDegree.failBottom) then return formal val target = calleeType.widen match case mtpe: MethodType => val pos = paramIndex(param.name) @@ -1069,8 +1070,8 @@ class Typer extends Namer else NoType case _ => NoType if target.exists then formal <:< target - if isFullyDefined(formal, ForceDegree.noBottom) then formal - else if target.exists && isFullyDefined(target, ForceDegree.noBottom) then target + if isFullyDefined(formal, ForceDegree.flipBottom) then formal + else if target.exists && isFullyDefined(target, ForceDegree.flipBottom) then target else errorType(AnonymousFunctionMissingParamType(param, params, tree, formal), param.sourcePos) def protoFormal(i: Int): Type = @@ -1079,7 +1080,7 @@ class Typer extends Namer /** Is `formal` a product type which is elementwise compatible with `params`? */ def ptIsCorrectProduct(formal: Type) = - isFullyDefined(formal, ForceDegree.noBottom) && + isFullyDefined(formal, ForceDegree.flipBottom) && (defn.isProductSubType(formal) || formal.derivesFrom(defn.PairClass)) && productSelectorTypes(formal, tree.sourcePos).corresponds(params) { (argType, param) => @@ -1393,7 +1394,7 @@ class Typer extends Namer } case _ => tree.withType( - if (isFullyDefined(pt, ForceDegree.noBottom)) pt + if (isFullyDefined(pt, ForceDegree.flipBottom)) pt else if (ctx.reporter.errorsReported) UnspecifiedErrorType else errorType(i"cannot infer type; expected type $pt is not fully defined", tree.sourcePos)) } @@ -3068,7 +3069,7 @@ class Typer extends Namer pt match { case SAMType(sam) if wtp <:< sam.toFunctionType() => - // was ... && isFullyDefined(pt, ForceDegree.noBottom) + // was ... && isFullyDefined(pt, ForceDegree.flipBottom) // but this prevents case blocks from implementing polymorphic partial functions, // since we do not know the result parameter a priori. Have to wait until the // body is typechecked.