Skip to content

Commit 57ac460

Browse files
committed
add support for precise typing with implicit conversions.
for this purpose, additional context was required in `TyperState`. a stack was used for the conversion history. Also, a more general fix for scala#12802 was applied.
1 parent 20b1831 commit 57ac460

File tree

4 files changed

+90
-7
lines changed

4 files changed

+90
-7
lines changed

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import collection.mutable
1212
import java.lang.ref.WeakReference
1313
import util.{Stats, SimpleIdentityMap}
1414
import Decorators._
15+
import ast.tpd.Tree
1516

1617
import scala.annotation.internal.sharable
1718

@@ -24,21 +25,23 @@ object TyperState {
2425
.setCommittable(true)
2526

2627
type LevelMap = SimpleIdentityMap[TypeVar, Integer]
28+
type PreciseConversionStack = List[Set[Tree]]
2729

28-
opaque type Snapshot = (Constraint, TypeVars, LevelMap)
30+
opaque type Snapshot = (Constraint, TypeVars, LevelMap, PreciseConversionStack)
2931

3032
extension (ts: TyperState)
3133
def snapshot()(using Context): Snapshot =
32-
(ts.constraint, ts.ownedVars, ts.upLevels)
34+
(ts.constraint, ts.ownedVars, ts.upLevels, ts.myPreciseConvStack)
3335

3436
def resetTo(state: Snapshot)(using Context): Unit =
35-
val (constraint, ownedVars, upLevels) = state
37+
val (constraint, ownedVars, upLevels, myPreciseConvStack) = state
3638
for tv <- ownedVars do
3739
if !ts.ownedVars.contains(tv) then // tv has been instantiated
3840
tv.resetInst(ts)
3941
ts.constraint = constraint
4042
ts.ownedVars = ownedVars
4143
ts.upLevels = upLevels
44+
ts.myPreciseConvStack = myPreciseConvStack
4245
}
4346

4447
class TyperState() {
@@ -93,6 +96,23 @@ class TyperState() {
9396

9497
private var upLevels: LevelMap = _
9598

99+
// Stack can be empty and precise conversion occur in `val x : Foo = from`
100+
// where `from` is implicitly and precisely converted into `Foo`. We don't
101+
// care about these conversions.
102+
private var myPreciseConvStack: List[Set[Tree]] = _
103+
def hasPreciseConversion(tree: Tree): Boolean =
104+
myPreciseConvStack match
105+
case head :: _ => head.contains(tree)
106+
case _ => false
107+
def addPreciseConversion(tree: Tree): Unit =
108+
myPreciseConvStack = myPreciseConvStack match
109+
case head :: tail => (head + tree) :: tail
110+
case _ => myPreciseConvStack
111+
def pushPreciseConversionStack(): Unit =
112+
myPreciseConvStack = Set.empty[Tree] :: myPreciseConvStack
113+
def popPreciseConversionStack(): Unit =
114+
myPreciseConvStack = myPreciseConvStack.drop(1)
115+
96116
/** Initializes all fields except reporter, isCommittable, which need to be
97117
* set separately.
98118
*/
@@ -105,6 +125,7 @@ class TyperState() {
105125
this.myOwnedVars = SimpleIdentitySet.empty
106126
this.upLevels = SimpleIdentityMap.empty
107127
this.isCommitted = false
128+
this.myPreciseConvStack = Nil
108129
this
109130

110131
/** A fresh typer state with the same constraint as this one. */
@@ -115,6 +136,7 @@ class TyperState() {
115136
.setReporter(reporter)
116137
.setCommittable(committable)
117138
ts.upLevels = upLevels
139+
ts.myPreciseConvStack = myPreciseConvStack
118140
ts
119141

120142
/** The uninstantiated variables */
@@ -162,6 +184,8 @@ class TyperState() {
162184
assert(!isCommitted, s"$this is already committed")
163185
val targetState = ctx.typerState
164186

187+
targetState.myPreciseConvStack = myPreciseConvStack
188+
165189
val nothingToCommit = (constraint eq targetState.constraint) && !reporter.hasUnreportedMessages
166190
assert(!targetState.isCommitted || nothingToCommit ||
167191
// Committing into an already committed TyperState usually doesn't make

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

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,7 @@ trait Applications extends Compatibility {
891891
*/
892892
def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = {
893893

894-
def realApply(using Context): Tree = {
894+
def realApply(argsPrecises: List[Boolean] = Nil)(using Context): Tree = {
895895
val resultProto = tree.fun match
896896
case Select(New(tpt), _) if pt.isInstanceOf[ValueType] =>
897897
if tpt.isType && typedAheadType(tpt).tpe.typeSymbol.typeParams.isEmpty then
@@ -905,7 +905,7 @@ trait Applications extends Compatibility {
905905
// Do ignore other expected result types, since there might be an implicit conversion
906906
// on the result. We could drop this if we disallow unrestricted implicit conversions.
907907
val originalProto =
908-
new FunProto(tree.args, resultProto)(this, tree.applyKind)(using argCtx(tree))
908+
new FunProto(tree.args, resultProto)(this, tree.applyKind, argsPrecises = argsPrecises)(using argCtx(tree))
909909
record("typedApply")
910910
val fun1 = typedExpr(tree.fun, originalProto)
911911

@@ -1015,6 +1015,41 @@ trait Applications extends Compatibility {
10151015
}
10161016
}
10171017

1018+
/** Attempts to run `realApply`, and if implicit conversions should be
1019+
* precise, then re-run `realApply` with the precise enforcement.
1020+
*/
1021+
def realApplyWithRetry(using Context): Tree = {
1022+
ctx.typerState.pushPreciseConversionStack()
1023+
val firstAttemptCtx = ctx.fresh.setNewTyperState()
1024+
// tuple application for tuple return type gets special treatment when arguments are precise
1025+
val argsPrecises = (tree.fun, pt.dealias) match
1026+
case (untpd.TypedSplice(fun), AppliedType(_, args))
1027+
if defn.isTupleClass(fun.tpe.classSymbol.companionClass) && defn.isTupleNType(pt) =>
1028+
args.map(_.isPrecise)
1029+
case _ => Nil
1030+
val app = realApply(argsPrecises)(using firstAttemptCtx)
1031+
val retTree = app match
1032+
// If we are already in precise mode, then the arguments are already typed precisely,
1033+
// so there is no need for any additional logic.
1034+
case Apply(_, args) if !ctx.mode.is(Mode.Precise) =>
1035+
val convArgsPrecises = args.map(firstAttemptCtx.typerState.hasPreciseConversion)
1036+
if (convArgsPrecises.contains(true))
1037+
val preciseCtx = ctx.fresh.setNewTyperState()
1038+
val updatedPrecises =
1039+
if (argsPrecises.nonEmpty) convArgsPrecises.lazyZip(argsPrecises).map(_ || _)
1040+
else convArgsPrecises
1041+
val app = realApply(updatedPrecises)(using preciseCtx)
1042+
preciseCtx.typerState.commit()
1043+
app
1044+
else
1045+
firstAttemptCtx.typerState.commit()
1046+
app
1047+
case _ =>
1048+
firstAttemptCtx.typerState.commit()
1049+
app
1050+
ctx.typerState.popPreciseConversionStack()
1051+
retTree
1052+
}
10181053
/** Convert expression like
10191054
*
10201055
* e += (args)
@@ -1037,7 +1072,7 @@ trait Applications extends Compatibility {
10371072
val app1 =
10381073
if (untpd.isOpAssign(tree))
10391074
tryEither {
1040-
realApply
1075+
realApplyWithRetry
10411076
} { (failedVal, failedState) =>
10421077
tryEither {
10431078
typedOpAssign
@@ -1049,7 +1084,7 @@ trait Applications extends Compatibility {
10491084
else {
10501085
val app = tree.fun match
10511086
case _: untpd.Splice if ctx.mode.is(Mode.QuotedPattern) => typedAppliedSplice(tree, pt)
1052-
case _ => realApply
1087+
case _ => realApplyWithRetry
10531088
app match {
10541089
case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType =>
10551090
val op = fn.symbol

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,7 @@ trait Implicits:
10131013
// would cause a cyclic reference error (if the import is named) or cause a
10141014
// spurious import skip (if the import is a wildcard import). See i12802 for a test case.
10151015
var searchCtx = ctx
1016+
while searchCtx.outer.owner.isImport do searchCtx = searchCtx.outer
10161017
if ctx.owner.isImport then
10171018
while
10181019
searchCtx = searchCtx.outer

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4019,6 +4019,29 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
40194019
case SearchSuccess(found, _, _, isExtension) =>
40204020
if isExtension then found
40214021
else
4022+
object Extract:
4023+
@tailrec def unapply(tree: Tree): Option[Tree] =
4024+
tree match
4025+
case Apply(tree, _) => unapply(tree)
4026+
case Select(tree, _) => unapply(tree)
4027+
case Inlined(tree, _, _) => unapply(tree)
4028+
case ta: TypeApply => Some(ta.fun)
4029+
case _ => None
4030+
found match
4031+
case Extract(fun) =>
4032+
fun.tpe.widen match
4033+
case pt: PolyType => pt.resType match
4034+
case mt: MethodType => mt.paramInfos.headOption.foreach {
4035+
case v if v.isPrecise =>
4036+
ctx.typerState.addPreciseConversion(found)
4037+
case _ =>
4038+
}
4039+
case AppliedType(tycon, from :: _)
4040+
if tycon.derivesFrom(defn.ConversionClass) && from.isPrecise =>
4041+
ctx.typerState.addPreciseConversion(found)
4042+
case _ =>
4043+
case _ =>
4044+
case _ =>
40224045
checkImplicitConversionUseOK(found)
40234046
withoutMode(Mode.ImplicitsEnabled)(readapt(found))
40244047
case failure: SearchFailure =>

0 commit comments

Comments
 (0)