From 0a04b68eeda258e7fb3cec8551c19d49c4292679 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 27 Aug 2024 22:35:19 +0100 Subject: [PATCH 1/2] Remove tvars introduced while testing normalizedCompatible --- .../dotty/tools/dotc/core/TyperState.scala | 5 ++-- .../src/dotty/tools/dotc/core/Types.scala | 3 ++- .../tools/dotc/printing/Formatting.scala | 2 +- .../tools/dotc/printing/PlainPrinter.scala | 2 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 8 +++++++ .../dotty/tools/dotc/typer/TypeAssigner.scala | 17 ++++++------- .../src/dotty/tools/dotc/typer/Typer.scala | 24 ++++++++++++++++++- tests/pos/interleaving-overload.cleanup.scala | 9 +++++++ tests/pos/zipped.min.scala | 15 ++++++++++++ 9 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 tests/pos/interleaving-overload.cleanup.scala create mode 100644 tests/pos/zipped.min.scala diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 160d7749de61..d4345916ba77 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -139,14 +139,15 @@ class TyperState() { def uncommittedAncestor: TyperState = if (isCommitted && previous != null) previous.uncheckedNN.uncommittedAncestor else this - /** Commit typer state so that its information is copied into current typer state + /** Commit `this` typer state by copying information into the current typer state, + * where "current" means contextual, so meaning `ctx.typerState`. * In addition (1) the owning state of undetermined or temporarily instantiated * type variables changes from this typer state to the current one. (2) Variables * that were temporarily instantiated in the current typer state are permanently * instantiated instead. * * A note on merging: An interesting test case is isApplicableSafe.scala. It turns out that this - * requires a context merge using the new `&' operator. Sequence of actions: + * requires a context merge using the new `&` operator. Sequence of actions: * 1) Typecheck argument in typerstate 1. * 2) Cache argument. * 3) Evolve same typer state (to typecheck other arguments, say) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d8a4453325f2..ca9d73df03aa 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4124,6 +4124,7 @@ object Types extends TypeUtils { protected def prefixString: String = companion.prefixString } + // Actually.. not cached. MethodOrPoly are `UncachedGroundType`s. final class CachedMethodType(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type, val companion: MethodTypeCompanion) extends MethodType(paramNames)(paramInfosExp, resultTypeExp) @@ -4884,7 +4885,7 @@ object Types extends TypeUtils { def origin: TypeParamRef = currentOrigin /** Set origin to new parameter. Called if we merge two conflicting constraints. - * See OrderingConstraint#merge, OrderingConstraint#rename + * See OrderingConstraint#merge */ def setOrigin(p: TypeParamRef) = currentOrigin = p diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 5870731dadfa..ccd7b4e4e282 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -184,7 +184,7 @@ object Formatting { * The idea is to do this for known cases that are useful and then fall back * on regular syntax highlighting for the cases which are unhandled. * - * Please not that if used in combination with `disambiguateTypes` the + * Please note that if used in combination with `disambiguateTypes` the * correct `Context` for printing should also be passed when calling the * method. * diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index b5ed3bdb4fa7..cac82eb0c4bd 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -253,7 +253,7 @@ class PlainPrinter(_ctx: Context) extends Printer { toTextCapturing(parent, refsText, "") ~ Str("R").provided(printDebug) else toText(parent) case tp: PreviousErrorType if ctx.settings.XprintTypes.value => - "" // do not print previously reported error message because they may try to print this error type again recuresevely + "" // do not print previously reported error message because they may try to print this error type again recursively case tp: ErrorType => s"" case tp: WildcardType => diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 53e0b456ed9a..9a94c50deb7f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -71,6 +71,14 @@ object ProtoTypes { |constraint was: ${ctx.typerState.constraint} |constraint now: ${newctx.typerState.constraint}""") if result && (ctx.typerState.constraint ne newctx.typerState.constraint) then + // Remove all type lambdas and tvars introduced by testCompat + for tvar <- newctx.typerState.ownedVars do + val tl = tvar.origin.binder + newctx.typerState.ownedVars -= tvar + if newctx.typerState.constraint.contains(tl) then + newctx.typerState.constraint = newctx.typerState.constraint.remove(tl)(using newctx) + + // commit any remaining changes in typer state newctx.typerState.commit() result case _ => testCompat diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index fd16f0de5f3a..f74ab65b6e4a 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -360,20 +360,21 @@ trait TypeAssigner { resultType1) } } + else if !args.hasSameLengthAs(paramNames) then + wrongNumberOfTypeArgs(fn.tpe, pt.typeParams, args, tree.srcPos) else { // Make sure arguments don't contain the type `pt` itself. - // make a copy of the argument if that's the case. + // Make a copy of `pt` if that's the case. // This is done to compensate for the fact that normally every // reference to a polytype would have to be a fresh copy of that type, // but we want to avoid that because it would increase compilation cost. // See pos/i6682a.scala for a test case where the defensive copying matters. - val ensureFresh = new TypeMap with CaptureSet.IdempotentCaptRefMap: - def apply(tp: Type) = mapOver( - if tp eq pt then pt.newLikeThis(pt.paramNames, pt.paramInfos, pt.resType) - else tp) - val argTypes = args.tpes.mapConserve(ensureFresh) - if (argTypes.hasSameLengthAs(paramNames)) pt.instantiate(argTypes) - else wrongNumberOfTypeArgs(fn.tpe, pt.typeParams, args, tree.srcPos) + val needsFresh = new ExistsAccumulator(_ eq pt, StopAt.None, forceLazy = false) + val argTypes = args.tpes + val pt1 = if argTypes.exists(needsFresh(false, _)) then + pt.newLikeThis(pt.paramNames, pt.paramInfos, pt.resType) + else pt + pt1.instantiate(argTypes) } } case err: ErrorType => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 817e7baf1c8c..e8b5813c83bd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4754,7 +4754,29 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer var typeArgs = tree match case Select(qual, nme.CONSTRUCTOR) => qual.tpe.widenDealias.argTypesLo.map(TypeTree(_)) case _ => Nil - if typeArgs.isEmpty then typeArgs = constrained(poly, tree)._2.map(_.wrapInTypeTree(tree)) + if typeArgs.isEmpty then + val poly1 = tree match + case Select(qual, nme.apply) => qual.tpe.widen match + case defn.PolyFunctionOf(_) => + // Given a poly function, like the one in i6682a: + // val v = [T] => (y:T) => (x:y.type) => 3 + // It's possible to apply `v(v)` which extends to: + // v.apply[?T](v) + // Requiring the circular constraint `v <: ?T`, + // (because type parameter T occurs in v's type). + // So we create a fresh copy of the outer + // poly method type, so we now extend to: + // v.apply[?T'](v) + // Where `?T'` is a type var for a T' type parameter, + // leading to the non-circular `v <: ?T'` constraint. + // + // This also happens in `assignType(tree: untpd.TypeApply, ..)` + // to avoid any type arguments, containing the type lambda, + // being applied to the very same type lambda. + poly.newLikeThis(poly.paramNames, poly.paramInfos, poly.resType) + case _ => poly + case _ => poly + typeArgs = constrained(poly1, tree)._2.map(_.wrapInTypeTree(tree)) convertNewGenericArray(readapt(tree.appliedToTypeTrees(typeArgs))) case wtp => val isStructuralCall = wtp.isValueType && isStructuralTermSelectOrApply(tree) diff --git a/tests/pos/interleaving-overload.cleanup.scala b/tests/pos/interleaving-overload.cleanup.scala new file mode 100644 index 000000000000..6eb2f16de15d --- /dev/null +++ b/tests/pos/interleaving-overload.cleanup.scala @@ -0,0 +1,9 @@ +// A minimisation of interleaving-overload +// Used while developing the tvar/tl clearnup in normalizedCompatible +class B[U] +class Test(): + def fn[T]: [U] => Int => B[U] = [U] => (x: Int) => new B[U]() + def test(): Unit = + fn(1) + fn(2) + () diff --git a/tests/pos/zipped.min.scala b/tests/pos/zipped.min.scala new file mode 100644 index 000000000000..5d15b3fae240 --- /dev/null +++ b/tests/pos/zipped.min.scala @@ -0,0 +1,15 @@ +// Justifies the need for TypeApply in tryInsertImplicitOnQualifier +// after failing ys.map[?B, C] using Zipped2's map +// we want to try ys.map[?B] using Coll's map, after toColl +final class Coll[+A]: + def map[B](f: A => B): Coll[B] = new Coll[B] + def lazyZip[B](that: Coll[B]): Zipped2[A, B] = new Zipped2[A, B](this, that) +final class Zipped2[+X, +Y](xs: Coll[X], ys: Coll[Y]): + def map[B, C](f: (X, Y) => B): Coll[C] = new Coll[C] +object Zipped2: + import scala.language.implicitConversions + implicit def toColl[X, Y](zipped2: Zipped2[X, Y]): Coll[(X, Y)] = new Coll[(X, Y)] +class Test: + def test(xs: Coll[Int]): Unit = + val ys = xs.lazyZip(xs) + ys.map((x: (Int, Int)) => x._1 + x._2) From 2c0d6dfd5dbb1f962713fddd06d080df4c8ab8ce Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 25 Oct 2024 12:32:45 +0100 Subject: [PATCH 2/2] Remove tvars in normalizedCompatible via instantiate Use a higher-level method. --- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 9a94c50deb7f..85f44ead5f28 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -73,10 +73,9 @@ object ProtoTypes { if result && (ctx.typerState.constraint ne newctx.typerState.constraint) then // Remove all type lambdas and tvars introduced by testCompat for tvar <- newctx.typerState.ownedVars do - val tl = tvar.origin.binder - newctx.typerState.ownedVars -= tvar - if newctx.typerState.constraint.contains(tl) then - newctx.typerState.constraint = newctx.typerState.constraint.remove(tl)(using newctx) + inContext(newctx): + if !tvar.isInstantiated then + tvar.instantiate(fromBelow = false) // any direction // commit any remaining changes in typer state newctx.typerState.commit()