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..85f44ead5f28 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -71,6 +71,13 @@ 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 + inContext(newctx): + if !tvar.isInstantiated then + tvar.instantiate(fromBelow = false) // any direction + + // 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 bbd78b5dcc2e..d39a8485846d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4757,7 +4757,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)