Skip to content

Commit fc71ee6

Browse files
authored
Merge pull request #3770 from dotty-staging/fix-#3067
Fix #3067: Flag missing parent type of implicit object as error
2 parents bbdef5e + 87f3d9e commit fc71ee6

File tree

12 files changed

+102
-46
lines changed

12 files changed

+102
-46
lines changed

compiler/src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ object Contexts {
282282
/** The current reporter */
283283
def reporter: Reporter = typerState.reporter
284284

285+
/** Run `op` as if it was run in a fresh explore typer state, but possibly
286+
* optimized to re-use the current typer state.
287+
*/
288+
final def test[T](op: Context => T): T = typerState.test(op)(this)
289+
285290
/** Is this a context for the members of a class definition? */
286291
def isClassDefContext: Boolean =
287292
owner.isClass && (owner ne outer.owner)

compiler/src/dotty/tools/dotc/core/TyperState.scala

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab
6464
def isGlobalCommittable: Boolean =
6565
isCommittable && (previous == null || previous.isGlobalCommittable)
6666

67+
private[this] var isShared = false
68+
69+
/** Mark typer state as shared (typically because it is the typer state of
70+
* the creation context of a source definition that potentially still needs
71+
* to be completed). Members of shared typer states are never overwritten in `test`.
72+
*/
73+
def markShared(): Unit = isShared = true
74+
6775
private[this] var isCommitted = false
6876

6977
/** A fresh typer state with the same constraint as this one. */
@@ -94,29 +102,35 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab
94102

95103
private[this] var testReporter: StoreReporter = null
96104

97-
/** Test using `op`, restoring typerState to previous state afterwards */
98-
def test[T](op: => T): T = {
99-
val savedConstraint = myConstraint
100-
val savedReporter = myReporter
101-
val savedCommittable = myIsCommittable
102-
val savedCommitted = isCommitted
103-
myIsCommittable = false
104-
myReporter = {
105-
if (testReporter == null) {
106-
testReporter = new StoreReporter(reporter)
107-
} else {
108-
testReporter.reset()
105+
/** Test using `op`. If current typerstate is shared, run `op` in a fresh exploration
106+
* typerstate. If it is unshared, run `op` in current typerState, restoring typerState
107+
* to previous state afterwards.
108+
*/
109+
def test[T](op: Context => T)(implicit ctx: Context): T =
110+
if (isShared)
111+
op(ctx.fresh.setExploreTyperState())
112+
else {
113+
val savedConstraint = myConstraint
114+
val savedReporter = myReporter
115+
val savedCommittable = myIsCommittable
116+
val savedCommitted = isCommitted
117+
myIsCommittable = false
118+
myReporter = {
119+
if (testReporter == null) {
120+
testReporter = new StoreReporter(reporter)
121+
} else {
122+
testReporter.reset()
123+
}
124+
testReporter
125+
}
126+
try op(ctx)
127+
finally {
128+
resetConstraintTo(savedConstraint)
129+
myReporter = savedReporter
130+
myIsCommittable = savedCommittable
131+
isCommitted = savedCommitted
109132
}
110-
testReporter
111-
}
112-
try op
113-
finally {
114-
resetConstraintTo(savedConstraint)
115-
myReporter = savedReporter
116-
myIsCommittable = savedCommittable
117-
isCommitted = savedCommitted
118133
}
119-
}
120134

121135
/** Commit typer state so that its information is copied into current typer state
122136
* In addition (1) the owning state of undetermined or temporarily instantiated

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,19 +1002,19 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
10021002
* @param resultType The expected result type of the application
10031003
*/
10041004
def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean =
1005-
ctx.typerState.test(new ApplicableToTrees(methRef, targs, args, resultType).success)
1005+
ctx.test(implicit ctx => new ApplicableToTrees(methRef, targs, args, resultType).success)
10061006

10071007
/** Is given method reference applicable to type arguments `targs` and argument trees `args` without inferring views?
10081008
* @param resultType The expected result type of the application
10091009
*/
10101010
def isDirectlyApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean =
1011-
ctx.typerState.test(new ApplicableToTreesDirectly(methRef, targs, args, resultType).success)
1011+
ctx.test(implicit ctx => new ApplicableToTreesDirectly(methRef, targs, args, resultType).success)
10121012

10131013
/** Is given method reference applicable to argument types `args`?
10141014
* @param resultType The expected result type of the application
10151015
*/
10161016
def isApplicable(methRef: TermRef, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean =
1017-
ctx.typerState.test(new ApplicableToTypes(methRef, args, resultType).success)
1017+
ctx.test(implicit ctx => new ApplicableToTypes(methRef, args, resultType).success)
10181018

10191019
/** Is given type applicable to type arguments `targs` and argument trees `args`,
10201020
* possibly after inserting an `apply`?
@@ -1119,7 +1119,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
11191119
case tp2: MethodType => true // (3a)
11201120
case tp2: PolyType if tp2.resultType.isInstanceOf[MethodType] => true // (3a)
11211121
case tp2: PolyType => // (3b)
1122-
ctx.typerState.test(isAsSpecificValueType(tp1, constrained(tp2).resultType))
1122+
ctx.test(implicit ctx => isAsSpecificValueType(tp1, constrained(tp2).resultType))
11231123
case _ => // (3b)
11241124
isAsSpecificValueType(tp1, tp2)
11251125
}
@@ -1271,9 +1271,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
12711271
* do they prune much, on average.
12721272
*/
12731273
def adaptByResult(chosen: TermRef) = pt match {
1274-
case pt: FunProto if !ctx.typerState.test(resultConforms(chosen, pt.resultType)) =>
1274+
case pt: FunProto if !ctx.test(implicit ctx => resultConforms(chosen, pt.resultType)) =>
12751275
val conformingAlts = alts.filter(alt =>
1276-
(alt ne chosen) && ctx.typerState.test(resultConforms(alt, pt.resultType)))
1276+
(alt ne chosen) && ctx.test(implicit ctx => resultConforms(alt, pt.resultType)))
12771277
conformingAlts match {
12781278
case Nil => chosen
12791279
case alt2 :: Nil => alt2

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ object Implicits {
8080
case mt: MethodType =>
8181
mt.isImplicitMethod ||
8282
mt.paramInfos.length != 1 ||
83-
!ctx.typerState.test(argType relaxed_<:< mt.paramInfos.head)
83+
!ctx.test(implicit ctx => argType relaxed_<:< mt.paramInfos.head)
8484
case poly: PolyType =>
8585
// We do not need to call ProtoTypes#constrained on `poly` because
8686
// `refMatches` is always called with mode TypevarsMissContext enabled.
8787
poly.resultType match {
8888
case mt: MethodType =>
8989
mt.isImplicitMethod ||
9090
mt.paramInfos.length != 1 ||
91-
!ctx.typerState.test(argType relaxed_<:< wildApprox(mt.paramInfos.head, null, Set.empty))
91+
!ctx.test(implicit ctx => argType relaxed_<:< wildApprox(mt.paramInfos.head, null, Set.empty))
9292
case rtp =>
9393
discardForView(wildApprox(rtp, null, Set.empty), argType)
9494
}
@@ -148,7 +148,7 @@ object Implicits {
148148
else {
149149
val nestedCtx = ctx.fresh.addMode(Mode.TypevarsMissContext)
150150
refs
151-
.filter(ref => nestedCtx.typerState.test(refMatches(ref.underlyingRef)(nestedCtx)))
151+
.filter(ref => nestedCtx.test(implicit ctx => refMatches(ref.underlyingRef)))
152152
.map(Candidate(_, level))
153153
}
154154
}
@@ -591,7 +591,7 @@ trait Implicits { self: Typer =>
591591
formal.argTypes match {
592592
case args @ (arg1 :: arg2 :: Nil)
593593
if !ctx.featureEnabled(defn.LanguageModuleClass, nme.strictEquality) &&
594-
ctx.typerState.test(validEqAnyArgs(arg1, arg2)) =>
594+
ctx.test(implicit ctx => validEqAnyArgs(arg1, arg2)) =>
595595
ref(defn.Eq_eqAny).appliedToTypes(args).withPos(pos)
596596
case _ =>
597597
EmptyTree
@@ -789,7 +789,8 @@ trait Implicits { self: Typer =>
789789
assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType],
790790
em"found: $argument: ${argument.tpe}, expected: $pt")
791791

792-
private def nestedContext() = ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled)
792+
private def nestedContext() =
793+
ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled)
793794

794795
private def implicitProto(resultType: Type, f: Type => Type) =
795796
if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType))
@@ -878,7 +879,7 @@ trait Implicits { self: Typer =>
878879
*/
879880
def compareCandidate(prev: SearchSuccess, ref: TermRef, level: Int): Int =
880881
if (prev.ref eq ref) 0
881-
else ctx.typerState.test(compare(prev.ref, ref, prev.level, level)(nestedContext()))
882+
else nestedContext().test(implicit ctx => compare(prev.ref, ref, prev.level, level))
882883

883884
/* Seems we don't need this anymore.
884885
def numericValueTieBreak(alt1: SearchSuccess, alt2: SearchSuccess) = {

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -717,13 +717,19 @@ class Namer { typer: Typer =>
717717
localCtx
718718
}
719719

720+
def missingType(sym: Symbol, modifier: String)(implicit ctx: Context) = {
721+
ctx.error(s"${modifier}type of implicit definition needs to be given explicitly", sym.pos)
722+
sym.resetFlag(Implicit)
723+
}
724+
720725
/** The completer of a symbol defined by a member def or import (except ClassSymbols) */
721726
class Completer(val original: Tree)(implicit ctx: Context) extends LazyType {
722727

723728
protected def localContext(owner: Symbol) = ctx.fresh.setOwner(owner).setTree(original)
724729

725730
/** The context with which this completer was created */
726731
def creationContext = ctx
732+
ctx.typerState.markShared()
727733

728734
protected def typeSig(sym: Symbol): Type = original match {
729735
case original: ValDef =>
@@ -850,7 +856,11 @@ class Namer { typer: Typer =>
850856
val targs1 = targs map (typedAheadType(_))
851857
val ptype = typedAheadType(tpt).tpe appliedTo targs1.tpes
852858
if (ptype.typeParams.isEmpty) ptype
853-
else fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.pos)
859+
else {
860+
if (denot.is(ModuleClass) && denot.sourceModule.is(Implicit))
861+
missingType(denot.symbol, "parent ")(creationContext)
862+
fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.pos)
863+
}
854864
}
855865

856866
/* Check parent type tree `parent` for the following well-formedness conditions:
@@ -1079,14 +1089,10 @@ class Namer { typer: Typer =>
10791089
lhsType // keep constant types that fill in for a non-constant (to be revised when inline has landed).
10801090
else inherited
10811091
else {
1082-
def missingType(modifier: String) = {
1083-
ctx.error(s"${modifier}type of implicit definition needs to be given explicitly", mdef.pos)
1084-
sym.resetFlag(Implicit)
1085-
}
10861092
if (sym is Implicit)
10871093
mdef match {
1088-
case _: DefDef => missingType("result")
1089-
case _: ValDef if sym.owner.isType => missingType("")
1094+
case _: DefDef => missingType(sym, "result ")
1095+
case _: ValDef if sym.owner.isType => missingType(sym, "")
10901096
case _ =>
10911097
}
10921098
lhsType orElse WildcardType

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ object ProtoTypes {
3939
(tp.widenExpr relaxed_<:< pt.widenExpr) || viewExists(tp, pt)
4040

4141
/** Test compatibility after normalization in a fresh typerstate. */
42-
def normalizedCompatible(tp: Type, pt: Type)(implicit ctx: Context) = ctx.typerState.test {
43-
val normTp = normalize(tp, pt)
44-
isCompatible(normTp, pt) || pt.isRef(defn.UnitClass) && normTp.isParameterless
45-
}
42+
def normalizedCompatible(tp: Type, pt: Type)(implicit ctx: Context) =
43+
ctx.test { implicit ctx =>
44+
val normTp = normalize(tp, pt)
45+
isCompatible(normTp, pt) || pt.isRef(defn.UnitClass) && normTp.isParameterless
46+
}
4647

4748
private def disregardProto(pt: Type)(implicit ctx: Context): Boolean = pt.dealias match {
4849
case _: OrType => true

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2080,7 +2080,7 @@ class Typer extends Namer
20802080
val constraint = ctx.typerState.constraint
20812081
def inst(tp: Type): Type = tp match {
20822082
case TypeBounds(lo, hi)
2083-
if (lo eq hi) || ctx.typerState.test(hi <:< lo) =>
2083+
if (lo eq hi) || ctx.test(implicit ctx => hi <:< lo) =>
20842084
inst(lo)
20852085
case tp: TypeParamRef =>
20862086
constraint.typeVarOfParam(tp).orElse(tp)

compiler/test/dotty/tools/dotc/FromTastyTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class FromTastyTests extends ParallelTesting {
4545
"t8023.scala",
4646
"tcpoly_ticket2096.scala",
4747
"t247.scala",
48+
"i3067.scala",
4849
)
4950
)
5051
step1.checkCompile() // Compile all files to generate the class files with tasty

tests/neg/i1802.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ object Exception {
1717
def mkThrowableCatcher[T](isDef: Throwable => Boolean, f: Throwable => T) = mkCatcher(isDef, f) // error: undetermined ClassTag
1818

1919
implicit def throwableSubtypeToCatcher[Ex <: Throwable: ClassTag, T](pf: PartialFunction[Ex, T]) = // error: result type needs to be given
20-
mkCatcher(pf.isDefinedAt _, pf.apply _)
20+
mkCatcher(pf.isDefinedAt _, pf.apply _) // error: method needs return type
2121
}

tests/neg/i3067.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class Test[T](f: List[String] => T)
2+
3+
object o {
4+
5+
implicit val x = 3 // error
6+
7+
implicit def y = "abc" // error
8+
9+
implicit object a extends Test(_ map identity) // error
10+
implicit object b extends Test(_ map identity) // error // error: cyclic reference
11+
}

tests/neg/i3067b.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import collection.generic.CanBuildFrom
2+
3+
class Test[T](f: List[String] => T)
4+
5+
object o {
6+
7+
implicitly[CanBuildFrom[String, Char, String]]
8+
9+
implicit object b extends Test(_ map identity) // error: type needs to be given // error: cyclic reference
10+
11+
}

tests/pos/i3067.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class Test[T](f: List[String] => T)
2+
3+
object o {
4+
implicit object a extends Test[List[String]](_ map identity)
5+
implicit object b extends Test[List[String]](_ map identity)
6+
}

0 commit comments

Comments
 (0)