Skip to content

Fix #3067: Flag missing parent type of implicit object as error #3770

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
56 changes: 35 additions & 21 deletions compiler/src/dotty/tools/dotc/core/TyperState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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`?
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down
13 changes: 7 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ 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.
poly.resultType match {
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)
}
Expand Down Expand Up @@ -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))
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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) = {
Expand Down
20 changes: 13 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -718,13 +718,19 @@ 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 {

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

/** 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 =>
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/FromTastyTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i1802.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
11 changes: 11 additions & 0 deletions tests/neg/i3067.scala
Original file line number Diff line number Diff line change
@@ -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
}
11 changes: 11 additions & 0 deletions tests/neg/i3067b.scala
Original file line number Diff line number Diff line change
@@ -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

}
6 changes: 6 additions & 0 deletions tests/pos/i3067.scala
Original file line number Diff line number Diff line change
@@ -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)
}