Skip to content

Commit f5611e3

Browse files
committed
Fixes and well-formedness check for capture-dependent types
1 parent 1750139 commit f5611e3

File tree

5 files changed

+50
-10
lines changed

5 files changed

+50
-10
lines changed

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3661,7 +3661,8 @@ object Types {
36613661
/** Does result type contain references to parameters of this method type,
36623662
* which cannot be eliminated by de-aliasing?
36633663
*/
3664-
def isResultDependent(using Context): Boolean = dependencyStatus == TrueDeps
3664+
def isResultDependent(using Context): Boolean =
3665+
dependencyStatus == TrueDeps || dependencyStatus == CaptureDeps
36653666

36663667
/** Does one of the parameter types contain references to earlier parameters
36673668
* of this method type which cannot be eliminated by de-aliasing?
@@ -3671,13 +3672,13 @@ object Types {
36713672
/** Is there either a true or false type dependency, or does the result
36723673
* type capture a parameter?
36733674
*/
3674-
def isCaptureDependent(using Context) = dependencyStatus >= CaptureDeps
3675+
def isCaptureDependent(using Context) = dependencyStatus == CaptureDeps
36753676

36763677
def newParamRef(n: Int): TermParamRef = new TermParamRefImpl(this, n)
36773678

36783679
/** The least supertype of `resultType` that does not contain parameter dependencies */
36793680
def nonDependentResultApprox(using Context): Type =
3680-
if (isResultDependent) {
3681+
if isResultDependent then
36813682
val dropDependencies = new ApproximatingTypeMap {
36823683
def apply(tp: Type) = tp match {
36833684
case tp @ TermParamRef(thisLambdaType, _) =>
@@ -3686,7 +3687,6 @@ object Types {
36863687
}
36873688
}
36883689
dropDependencies(resultType)
3689-
}
36903690
else resultType
36913691
}
36923692

@@ -4054,8 +4054,8 @@ object Types {
40544054
type DependencyStatus = Byte
40554055
final val Unknown: DependencyStatus = 0 // not yet computed
40564056
final val NoDeps: DependencyStatus = 1 // no dependent parameters found
4057-
final val CaptureDeps: DependencyStatus = 2
4058-
final val FalseDeps: DependencyStatus = 3 // all dependent parameters are prefixes of non-depended alias types
4057+
final val FalseDeps: DependencyStatus = 2 // all dependent parameters are prefixes of non-depended alias types
4058+
final val CaptureDeps: DependencyStatus = 3
40594059
final val TrueDeps: DependencyStatus = 4 // some truly dependent parameters exist
40604060
final val StatusMask: DependencyStatus = 7 // the bits indicating actual dependency status
40614061
final val Provisional: DependencyStatus = 8 // set if dependency status can still change due to type variable instantiations

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import Trees._
1818
import scala.util.control.NonFatal
1919
import typer.ErrorReporting._
2020
import util.Spans.Span
21-
import util.SimpleIdentitySet
21+
import util.{SimpleIdentitySet, SrcPos}
2222
import util.Chars.*
2323
import Nullables._
2424
import transform.*
@@ -96,6 +96,24 @@ class CheckCaptures extends RefineTypes:
9696

9797
inline val disallowGlobal = true
9898

99+
def checkWellFormed(whole: Type, pos: SrcPos)(using Context): Unit =
100+
def checkRelativeVariance(mt: MethodType) = new TypeTraverser:
101+
def traverse(tp: Type): Unit = tp match
102+
case CapturingType(parent, ref @ TermParamRef(`mt`, _)) =>
103+
if variance <= 0 then
104+
val direction = if variance < 0 then "contra" else "in"
105+
report.error(em"captured reference $ref appears ${direction}variantly in type $whole", pos)
106+
traverse(parent)
107+
case _ =>
108+
traverseChildren(tp)
109+
val checkVariance = new TypeTraverser:
110+
def traverse(tp: Type): Unit = tp match
111+
case mt: MethodType if mt.isResultDependent =>
112+
checkRelativeVariance(mt).traverse(mt)
113+
case _ =>
114+
traverseChildren(tp)
115+
checkVariance.traverse(whole)
116+
99117
object PostRefinerCheck extends TreeTraverser:
100118
def traverse(tree: Tree)(using Context) =
101119
tree match
@@ -116,6 +134,10 @@ class CheckCaptures extends RefineTypes:
116134
|The inferred arguments are: [$args%, %]"""
117135
case _ => s"type argument$notAllowed"
118136
report.error(msg, arg.srcPos)
137+
case tree: TypeTree =>
138+
// it's inferred, no need to check
139+
case tree: TypTree =>
140+
checkWellFormed(tree.tpe, tree.srcPos)
119141
case _ =>
120142
traverseChildren(tree)
121143

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,10 @@ trait TypeAssigner {
311311
val ownType = fn.tpe.widen match {
312312
case fntpe: MethodType =>
313313
if (sameLength(fntpe.paramInfos, args) || ctx.phase.prev.relaxedTyping)
314-
if fntpe.isResultDependent then
315-
safeSubstParams(fntpe.resultType, fntpe.paramRefs, args.tpes)
316-
else if fntpe.isCaptureDependent then
314+
if fntpe.isCaptureDependent then
317315
fntpe.resultType.substParams(fntpe, args.tpes)
316+
else if fntpe.isResultDependent then
317+
safeSubstParams(fntpe.resultType, fntpe.paramRefs, args.tpes)
318318
else
319319
fntpe.resultType
320320
else
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class C
2+
type Cap = C retains *
3+
type Top = Any retains *
4+
5+
type T = (x: Cap) => List[String retains x.type] => Unit // error
6+
val x: (x: Cap) => Array[String retains x.type] = ??? // error
7+
val y = x
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class C
2+
type Cap = C retains *
3+
type Top = Any retains *
4+
5+
type T = (x: Cap) => String retains x.type
6+
7+
def f(y: Cap): String retains * =
8+
val a: T = (x: Cap) => ""
9+
val b = a(y)
10+
val c: String retains y.type = b
11+
c

0 commit comments

Comments
 (0)