Skip to content

Commit e697ca0

Browse files
committed
Fix scala#6247: Heal selection errors by fully defining qualifier type
This solves a situation where we cannot find a member `m` of a qualifier of type `T` where `T` is a type variable with lower bound `L`, where `m` is a member of `L`. This has come up occasionally before, but never until now in minimized form. The solution is to instantiate the type variable using `fullyDefinedType` and try again.
1 parent aa81657 commit e697ca0

File tree

3 files changed

+54
-31
lines changed

3 files changed

+54
-31
lines changed

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

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import ast.Trees._
1313
import NameOps._
1414
import collection.mutable
1515
import reporting.diagnostic.messages._
16+
import util.Stats.record
17+
import Inferencing._
1618
import Checking.{checkNoPrivateLeaks, checkNoWildcard}
1719

1820
trait TypeAssigner {
@@ -163,6 +165,20 @@ trait TypeAssigner {
163165

164166
def arrayToRepeated(tree: Tree)(implicit ctx: Context): Tree = toRepeated(tree, defn.ArrayClass)
165167

168+
def tryEither[T](op: Context => T)(fallBack: (T, TyperState) => T)(implicit ctx: Context): T = {
169+
val nestedCtx = ctx.fresh.setNewTyperState()
170+
val result = op(nestedCtx)
171+
if (nestedCtx.reporter.hasErrors && !nestedCtx.reporter.hasStickyErrors) {
172+
record("tryEither.fallBack")
173+
fallBack(result, nestedCtx.typerState)
174+
}
175+
else {
176+
record("tryEither.commit")
177+
nestedCtx.typerState.commit()
178+
result
179+
}
180+
}
181+
166182
/** A denotation exists really if it exists and does not point to a stale symbol. */
167183
final def reallyExists(denot: Denotation)(implicit ctx: Context): Boolean = try
168184
denot match {
@@ -228,6 +244,7 @@ trait TypeAssigner {
228244

229245
/** The type of the selection `tree`, where `qual1` is the typed qualifier part. */
230246
def selectionType(tree: untpd.RefTree, qual1: Tree)(implicit ctx: Context): Type = {
247+
231248
var qualType = qual1.tpe.widenIfUnstable
232249
if (!qualType.hasSimpleKind && tree.name != nme.CONSTRUCTOR)
233250
// constructors are selected on typeconstructor, type arguments are passed afterwards
@@ -236,29 +253,39 @@ trait TypeAssigner {
236253
qualType = errorType(em"$qualType is illegal as a selection prefix", qual1.sourcePos)
237254
val name = tree.name
238255
val mbr = qualType.member(name)
256+
257+
def fail = if (name == nme.CONSTRUCTOR)
258+
errorType(ex"$qualType does not have a constructor", tree.sourcePos)
259+
else {
260+
val kind = if (name.isTypeName) "type" else "value"
261+
val addendum =
262+
if (qualType.derivesFrom(defn.DynamicClass))
263+
"\npossible cause: maybe a wrong Dynamic method signature?"
264+
else qual1.getAttachment(Typer.HiddenSearchFailure) match {
265+
case Some(failure) if !failure.reason.isInstanceOf[Implicits.NoMatchingImplicits] =>
266+
i""".
267+
|An extension method was tried, but could not be fully constructed:
268+
|
269+
| ${failure.tree.show.replace("\n", "\n ")}"""
270+
case _ => ""
271+
}
272+
errorType(NotAMember(qualType, name, kind, addendum), tree.sourcePos)
273+
}
274+
239275
if (reallyExists(mbr))
240276
qualType.select(name, mbr)
241277
else if (qualType.derivesFrom(defn.DynamicClass) && name.isTermName && !Dynamic.isDynamicMethod(name))
242278
TryDynamicCallType
243279
else if (qualType.isErroneous || name.toTermName == nme.ERROR)
244280
UnspecifiedErrorType
245-
else if (name == nme.CONSTRUCTOR)
246-
errorType(ex"$qualType does not have a constructor", tree.sourcePos)
247-
else {
248-
val kind = if (name.isTypeName) "type" else "value"
249-
val addendum =
250-
if (qualType.derivesFrom(defn.DynamicClass))
251-
"\npossible cause: maybe a wrong Dynamic method signature?"
252-
else qual1.getAttachment(Typer.HiddenSearchFailure) match {
253-
case Some(failure) if !failure.reason.isInstanceOf[Implicits.NoMatchingImplicits] =>
254-
i""".
255-
|An extension method was tried, but could not be fully constructed:
256-
|
257-
| ${failure.tree.show.replace("\n", "\n ")}"""
258-
case _ => ""
259-
}
260-
errorType(NotAMember(qualType, name, kind, addendum), tree.sourcePos)
261-
}
281+
else if (!isFullyDefined(qualType, force = ForceDegree.none))
282+
tryEither { implicit ctx =>
283+
fullyDefinedType(qualType, "selection prefix", qual1.span)
284+
selectionType(tree, qual1)
285+
} { (_, _) =>
286+
fail
287+
}
288+
else fail
262289
}
263290

264291
/** The type of the selection in `tree`, where `qual1` is the typed qualifier part.

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2250,20 +2250,6 @@ class Typer extends Namer
22502250
def typedPattern(tree: untpd.Tree, selType: Type = WildcardType)(implicit ctx: Context): Tree =
22512251
typed(tree, selType)(ctx addMode Mode.Pattern)
22522252

2253-
def tryEither[T](op: Context => T)(fallBack: (T, TyperState) => T)(implicit ctx: Context): T = {
2254-
val nestedCtx = ctx.fresh.setNewTyperState()
2255-
val result = op(nestedCtx)
2256-
if (nestedCtx.reporter.hasErrors && !nestedCtx.reporter.hasStickyErrors) {
2257-
record("tryEither.fallBack")
2258-
fallBack(result, nestedCtx.typerState)
2259-
}
2260-
else {
2261-
record("tryEither.commit")
2262-
nestedCtx.typerState.commit()
2263-
result
2264-
}
2265-
}
2266-
22672253
/** Try `op1`, if there are errors, try `op2`, if `op2` also causes errors, fall back
22682254
* to errors and result of `op1`.
22692255
*/

tests/pos/i6247.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
implicit class NN[T](x:T|Null) {
2+
def nn: T = x.asInstanceOf[T]
3+
}
4+
5+
class Foo {
6+
val x1: String|Null = null
7+
x1.nn.length
8+
val x2: String = x1.nn
9+
x1.nn.length
10+
}

0 commit comments

Comments
 (0)