diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 629794efb74a..3764dbd66d34 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -282,6 +282,11 @@ object Contexts { /** The current reporter */ def reporter: Reporter = typerState.reporter + /** Run `op` as if it was run in a fresh explore typer state, but possibly + * optimized to re-use the current typer state. + */ + final def test[T](op: Context => T): T = typerState.test(op)(this) + /** Is this a context for the members of a class definition? */ def isClassDefContext: Boolean = owner.isClass && (owner ne outer.owner) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index c4fdd272b1a4..1821735522f5 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -64,6 +64,14 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab def isGlobalCommittable: Boolean = isCommittable && (previous == null || previous.isGlobalCommittable) + private[this] var isShared = false + + /** Mark typer state as shared (typically because it is the typer state of + * the creation context of a source definition that potentially still needs + * to be completed). Members of shared typer states are never overwritten in `test`. + */ + def markShared(): Unit = isShared = true + private[this] var isCommitted = false /** A fresh typer state with the same constraint as this one. */ @@ -94,29 +102,35 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab private[this] var testReporter: StoreReporter = null - /** Test using `op`, restoring typerState to previous state afterwards */ - def test[T](op: => T): T = { - val savedConstraint = myConstraint - val savedReporter = myReporter - val savedCommittable = myIsCommittable - val savedCommitted = isCommitted - myIsCommittable = false - myReporter = { - if (testReporter == null) { - testReporter = new StoreReporter(reporter) - } else { - testReporter.reset() + /** Test using `op`. If current typerstate is shared, run `op` in a fresh exploration + * typerstate. If it is unshared, run `op` in current typerState, restoring typerState + * to previous state afterwards. + */ + def test[T](op: Context => T)(implicit ctx: Context): T = + if (isShared) + op(ctx.fresh.setExploreTyperState()) + else { + val savedConstraint = myConstraint + val savedReporter = myReporter + val savedCommittable = myIsCommittable + val savedCommitted = isCommitted + myIsCommittable = false + myReporter = { + if (testReporter == null) { + testReporter = new StoreReporter(reporter) + } else { + testReporter.reset() + } + testReporter + } + try op(ctx) + finally { + resetConstraintTo(savedConstraint) + myReporter = savedReporter + myIsCommittable = savedCommittable + isCommitted = savedCommitted } - testReporter - } - try op - finally { - resetConstraintTo(savedConstraint) - myReporter = savedReporter - myIsCommittable = savedCommittable - isCommitted = savedCommitted } - } /** Commit typer state so that its information is copied into current typer state * In addition (1) the owning state of undetermined or temporarily instantiated diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index a49e0f73bea9..3a577b7b2bca 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -993,19 +993,19 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * @param resultType The expected result type of the application */ def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = - ctx.typerState.test(new ApplicableToTrees(methRef, targs, args, resultType).success) + ctx.test(implicit ctx => new ApplicableToTrees(methRef, targs, args, resultType).success) /** Is given method reference applicable to type arguments `targs` and argument trees `args` without inferring views? * @param resultType The expected result type of the application */ def isDirectlyApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = - ctx.typerState.test(new ApplicableToTreesDirectly(methRef, targs, args, resultType).success) + ctx.test(implicit ctx => new ApplicableToTreesDirectly(methRef, targs, args, resultType).success) /** Is given method reference applicable to argument types `args`? * @param resultType The expected result type of the application */ def isApplicable(methRef: TermRef, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean = - ctx.typerState.test(new ApplicableToTypes(methRef, args, resultType).success) + ctx.test(implicit ctx => new ApplicableToTypes(methRef, args, resultType).success) /** Is given type applicable to type arguments `targs` and argument trees `args`, * possibly after inserting an `apply`? @@ -1110,7 +1110,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case tp2: MethodType => true // (3a) case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a) case tp2: PolyType => // (3b) - ctx.typerState.test(isAsSpecificValueType(tp1, constrained(tp2).resultType)) + ctx.test(implicit ctx => isAsSpecificValueType(tp1, constrained(tp2).resultType)) case _ => // (3b) isAsSpecificValueType(tp1, tp2) } @@ -1262,9 +1262,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * do they prune much, on average. */ def adaptByResult(chosen: TermRef) = pt match { - case pt: FunProto if !ctx.typerState.test(resultConforms(chosen, pt.resultType)) => + case pt: FunProto if !ctx.test(implicit ctx => resultConforms(chosen, pt.resultType)) => val conformingAlts = alts.filter(alt => - (alt ne chosen) && ctx.typerState.test(resultConforms(alt, pt.resultType))) + (alt ne chosen) && ctx.test(implicit ctx => resultConforms(alt, pt.resultType))) conformingAlts match { case Nil => chosen case alt2 :: Nil => alt2 diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index d35852a2b2e8..7806365512d1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -80,7 +80,7 @@ object Implicits { case mt: MethodType => mt.isImplicitMethod || mt.paramInfos.length != 1 || - !ctx.typerState.test(argType relaxed_<:< mt.paramInfos.head) + !ctx.test(implicit ctx => argType relaxed_<:< mt.paramInfos.head) case poly: PolyType => // We do not need to call ProtoTypes#constrained on `poly` because // `refMatches` is always called with mode TypevarsMissContext enabled. @@ -88,7 +88,7 @@ object Implicits { case mt: MethodType => mt.isImplicitMethod || mt.paramInfos.length != 1 || - !ctx.typerState.test(argType relaxed_<:< wildApprox(mt.paramInfos.head, null, Set.empty)) + !ctx.test(implicit ctx => argType relaxed_<:< wildApprox(mt.paramInfos.head, null, Set.empty)) case rtp => discardForView(wildApprox(rtp, null, Set.empty), argType) } @@ -148,7 +148,7 @@ object Implicits { else { val nestedCtx = ctx.fresh.addMode(Mode.TypevarsMissContext) refs - .filter(ref => nestedCtx.typerState.test(refMatches(ref.underlyingRef)(nestedCtx))) + .filter(ref => nestedCtx.test(implicit ctx => refMatches(ref.underlyingRef))) .map(Candidate(_, level)) } } @@ -591,7 +591,7 @@ trait Implicits { self: Typer => formal.argTypes match { case args @ (arg1 :: arg2 :: Nil) if !ctx.featureEnabled(defn.LanguageModuleClass, nme.strictEquality) && - ctx.typerState.test(validEqAnyArgs(arg1, arg2)) => + ctx.test(implicit ctx => validEqAnyArgs(arg1, arg2)) => ref(defn.Eq_eqAny).appliedToTypes(args).withPos(pos) case _ => EmptyTree @@ -787,7 +787,8 @@ trait Implicits { self: Typer => assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], em"found: $argument: ${argument.tpe}, expected: $pt") - private def nestedContext() = ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled) + private def nestedContext() = + ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled) private def implicitProto(resultType: Type, f: Type => Type) = if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType)) @@ -876,7 +877,7 @@ trait Implicits { self: Typer => */ def compareCandidate(prev: SearchSuccess, ref: TermRef, level: Int): Int = if (prev.ref eq ref) 0 - else ctx.typerState.test(compare(prev.ref, ref, prev.level, level)(nestedContext())) + else nestedContext().test(implicit ctx => compare(prev.ref, ref, prev.level, level)) /* Seems we don't need this anymore. def numericValueTieBreak(alt1: SearchSuccess, alt2: SearchSuccess) = { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index d619feb966bb..d1d61e5b2454 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -718,6 +718,11 @@ class Namer { typer: Typer => localCtx } + def missingType(sym: Symbol, modifier: String)(implicit ctx: Context) = { + ctx.error(s"${modifier}type of implicit definition needs to be given explicitly", sym.pos) + sym.resetFlag(Implicit) + } + /** The completer of a symbol defined by a member def or import (except ClassSymbols) */ class Completer(val original: Tree)(implicit ctx: Context) extends LazyType { @@ -725,6 +730,7 @@ class Namer { typer: Typer => /** The context with which this completer was created */ def creationContext = ctx + ctx.typerState.markShared() protected def typeSig(sym: Symbol): Type = original match { case original: ValDef => @@ -851,7 +857,11 @@ class Namer { typer: Typer => val targs1 = targs map (typedAheadType(_)) val ptype = typedAheadType(tpt).tpe appliedTo targs1.tpes if (ptype.typeParams.isEmpty) ptype - else fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.pos) + else { + if (denot.is(ModuleClass) && denot.sourceModule.is(Implicit)) + missingType(denot.symbol, "parent ")(creationContext) + fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.pos) + } } /* Check parent type tree `parent` for the following well-formedness conditions: @@ -1080,14 +1090,10 @@ class Namer { typer: Typer => lhsType // keep constant types that fill in for a non-constant (to be revised when inline has landed). else inherited else { - def missingType(modifier: String) = { - ctx.error(s"${modifier}type of implicit definition needs to be given explicitly", mdef.pos) - sym.resetFlag(Implicit) - } if (sym is Implicit) mdef match { - case _: DefDef => missingType("result") - case _: ValDef if sym.owner.isType => missingType("") + case _: DefDef => missingType(sym, "result ") + case _: ValDef if sym.owner.isType => missingType(sym, "") case _ => } lhsType orElse WildcardType diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 067b9c1e15df..db585f9d3ee1 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -39,10 +39,11 @@ object ProtoTypes { (tp.widenExpr relaxed_<:< pt.widenExpr) || viewExists(tp, pt) /** Test compatibility after normalization in a fresh typerstate. */ - def normalizedCompatible(tp: Type, pt: Type)(implicit ctx: Context) = ctx.typerState.test { - val normTp = normalize(tp, pt) - isCompatible(normTp, pt) || pt.isRef(defn.UnitClass) && normTp.isParameterless - } + def normalizedCompatible(tp: Type, pt: Type)(implicit ctx: Context) = + ctx.test { implicit ctx => + val normTp = normalize(tp, pt) + isCompatible(normTp, pt) || pt.isRef(defn.UnitClass) && normTp.isParameterless + } private def disregardProto(pt: Type)(implicit ctx: Context): Boolean = pt.dealias match { case _: OrType => true diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index fa2cd8f25c5b..cf77429436d1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2056,7 +2056,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val constraint = ctx.typerState.constraint def inst(tp: Type): Type = tp match { case TypeBounds(lo, hi) - if (lo eq hi) || ctx.typerState.test(hi <:< lo) => + if (lo eq hi) || ctx.test(implicit ctx => hi <:< lo) => inst(lo) case tp: TypeParamRef => constraint.typeVarOfParam(tp).orElse(tp) diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index d08789d72a92..b32d5e66314d 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -52,6 +52,7 @@ class FromTastyTests extends ParallelTesting { "t8023.scala", "tcpoly_ticket2096.scala", "t247.scala", + "i3067.scala", ) ) step1.checkCompile() // Compile all files to generate the class files with tasty diff --git a/tests/neg/i1802.scala b/tests/neg/i1802.scala index 455ee9573a43..c2e65d8dd8d7 100644 --- a/tests/neg/i1802.scala +++ b/tests/neg/i1802.scala @@ -17,5 +17,5 @@ object Exception { def mkThrowableCatcher[T](isDef: Throwable => Boolean, f: Throwable => T) = mkCatcher(isDef, f) // error: undetermined ClassTag implicit def throwableSubtypeToCatcher[Ex <: Throwable: ClassTag, T](pf: PartialFunction[Ex, T]) = // error: result type needs to be given - mkCatcher(pf.isDefinedAt _, pf.apply _) + mkCatcher(pf.isDefinedAt _, pf.apply _) // error: method needs return type } diff --git a/tests/neg/i3067.scala b/tests/neg/i3067.scala new file mode 100644 index 000000000000..995a199dc970 --- /dev/null +++ b/tests/neg/i3067.scala @@ -0,0 +1,11 @@ +class Test[T](f: List[String] => T) + +object o { + + implicit val x = 3 // error + + implicit def y = "abc" // error + + implicit object a extends Test(_ map identity) // error + implicit object b extends Test(_ map identity) // error // error: cyclic reference +} diff --git a/tests/neg/i3067b.scala b/tests/neg/i3067b.scala new file mode 100644 index 000000000000..a364aff064c8 --- /dev/null +++ b/tests/neg/i3067b.scala @@ -0,0 +1,11 @@ +import collection.generic.CanBuildFrom + +class Test[T](f: List[String] => T) + +object o { + + implicitly[CanBuildFrom[String, Char, String]] + + implicit object b extends Test(_ map identity) // error: type needs to be given // error: cyclic reference + +} diff --git a/tests/pos/i3067.scala b/tests/pos/i3067.scala new file mode 100644 index 000000000000..5c8242c6e58b --- /dev/null +++ b/tests/pos/i3067.scala @@ -0,0 +1,6 @@ +class Test[T](f: List[String] => T) + +object o { + implicit object a extends Test[List[String]](_ map identity) + implicit object b extends Test[List[String]](_ map identity) +}