Skip to content

Commit dd5954b

Browse files
authored
Fix unsoundness in APPLY and SELECT rules (#24159)
We have to specially treat the case where the result contains a ResultCap. In that case no improvement is allowed, even if the alternative capture set is empty. Based on #24155
2 parents a72423e + 04b3e29 commit dd5954b

File tree

21 files changed

+215
-142
lines changed

21 files changed

+215
-142
lines changed

compiler/src/dotty/tools/dotc/cc/Capability.scala

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,14 +1252,11 @@ object Capabilities:
12521252
def toResultInResults(sym: Symbol, fail: Message => Unit, keepAliases: Boolean = false)(tp: Type)(using Context): Type =
12531253
val m = new TypeMap with FollowAliasesMap:
12541254
def apply(t: Type): Type = t match
1255-
case AnnotatedType(parent @ defn.RefinedFunctionOf(mt), ann) if ann.symbol == defn.InferredDepFunAnnot =>
1256-
val mt1 = mapOver(mt).asInstanceOf[MethodType]
1257-
if mt1 ne mt then mt1.toFunctionType(alwaysDependent = true)
1258-
else parent
1259-
case defn.RefinedFunctionOf(mt) =>
1260-
val mt1 = apply(mt)
1261-
if mt1 ne mt then mt1.toFunctionType(alwaysDependent = true)
1262-
else t
1255+
case rt @ defn.RefinedFunctionOf(mt) =>
1256+
rt.derivedRefinedType(refinedInfo =
1257+
if rt.isInstanceOf[InferredRefinedType]
1258+
then mapOver(mt)
1259+
else apply(mt))
12631260
case t: MethodType if variance > 0 && t.marksExistentialScope =>
12641261
val t1 = mapOver(t).asInstanceOf[MethodType]
12651262
t1.derivedLambdaType(resType = toResult(t1.resType, t1, fail))

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -732,19 +732,26 @@ class CheckCaptures extends Recheck, SymTransformer:
732732
|since its capture set ${qualType.captureSet} is read-only""",
733733
tree.srcPos)
734734

735-
val selType = mapResultRoots(recheckSelection(tree, qualType, name, disambiguate), tree.symbol)
735+
val origSelType = recheckSelection(tree, qualType, name, disambiguate)
736+
val selType = mapResultRoots(origSelType, tree.symbol)
736737
val selWiden = selType.widen
737738

739+
def capturesResult = origSelType.widenSingleton match
740+
case ExprType(resType) => resType.captureSet.containsResultCapability
741+
case _ => false
742+
738743
// Don't apply the rule
739744
// - on the LHS of assignments, or
740745
// - if the qualifier or selection type is boxed, or
741-
// - the selection is either a trackable capture reference or a pure type
746+
// - the selection is either a trackable capture reference or a pure type, or
747+
// - if the selection is of a parameterless method capturing a result cap
742748
if noWiden(selType, pt)
743749
|| qualType.isBoxedCapturing
744750
|| selType.isBoxedCapturing
745751
|| selWiden.isBoxedCapturing
746752
|| selType.isTrackableRef
747753
|| selWiden.captureSet.isAlwaysEmpty
754+
|| capturesResult
748755
then
749756
selType
750757
else
@@ -819,9 +826,8 @@ class CheckCaptures extends Recheck, SymTransformer:
819826
*/
820827
protected override
821828
def recheckApplication(tree: Apply, qualType: Type, funType: MethodType, argTypes: List[Type])(using Context): Type =
822-
val appType = resultToFresh(
823-
super.recheckApplication(tree, qualType, funType, argTypes),
824-
Origin.ResultInstance(funType, tree.symbol))
829+
val resultType = super.recheckApplication(tree, qualType, funType, argTypes)
830+
val appType = resultToFresh(resultType, Origin.ResultInstance(funType, tree.symbol))
825831
val qualCaptures = qualType.captureSet
826832
val argCaptures =
827833
for (argType, formal) <- argTypes.lazyZip(funType.paramInfos) yield
@@ -830,6 +836,7 @@ class CheckCaptures extends Recheck, SymTransformer:
830836
case appType @ CapturingType(appType1, refs)
831837
if qualType.exists
832838
&& !tree.fun.symbol.isConstructor
839+
&& !resultType.captureSet.containsResultCapability
833840
&& qualCaptures.mightSubcapture(refs)
834841
&& argCaptures.forall(_.mightSubcapture(refs)) =>
835842
val callCaptures = argCaptures.foldLeft(qualCaptures)(_ ++ _)

compiler/src/dotty/tools/dotc/cc/Setup.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
193193
case AppliedType(`tycon`, args0) => args0.last ne args.last
194194
case _ => false
195195
if expand then
196-
val fn = depFun(
196+
val (fn: RefinedType) = depFun(
197197
args.init, args.last,
198198
isContextual = defn.isContextFunctionClass(tycon.classSymbol))
199199
.showing(i"add function refinement $tp ($tycon, ${args.init}, ${args.last}) --> $result", capt)
200-
AnnotatedType(fn, Annotation(defn.InferredDepFunAnnot, util.Spans.NoSpan))
200+
.runtimeChecked
201+
RefinedType.inferred(fn.parent, fn.refinedName, fn.refinedInfo)
201202
else tp
202203
case _ => tp
203204

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3268,12 +3268,14 @@ object Types extends TypeUtils {
32683268

32693269
def checkInst(using Context): this.type = this // debug hook
32703270

3271+
def newLikeThis(parent: Type, refinedName: Name, refinedInfo: Type)(using Context): Type =
3272+
RefinedType(parent, refinedName, refinedInfo)
3273+
32713274
final def derivedRefinedType
32723275
(parent: Type = this.parent, refinedName: Name = this.refinedName, refinedInfo: Type = this.refinedInfo)
32733276
(using Context): Type =
32743277
if ((parent eq this.parent) && (refinedName eq this.refinedName) && (refinedInfo eq this.refinedInfo)) this
3275-
else if isPrecise then RefinedType.precise(parent, refinedName, refinedInfo)
3276-
else RefinedType(parent, refinedName, refinedInfo)
3278+
else newLikeThis(parent, refinedName, refinedInfo)
32773279

32783280
/** Add this refinement to `parent`, provided `refinedName` is a member of `parent`. */
32793281
def wrapIfMember(parent: Type)(using Context): Type =
@@ -3308,6 +3310,17 @@ object Types extends TypeUtils {
33083310
class PreciseRefinedType(parent: Type, refinedName: Name, refinedInfo: Type)
33093311
extends RefinedType(parent, refinedName, refinedInfo):
33103312
override def isPrecise = true
3313+
override def newLikeThis(parent: Type, refinedName: Name, refinedInfo: Type)(using Context): Type =
3314+
PreciseRefinedType(parent, refinedName, refinedInfo)
3315+
3316+
/** Used for refined function types created at cc/Setup that come from original
3317+
* generic function types. Function types of this class don't get their result
3318+
* captures mapped from FreshCaps to ResultCaps with toResult.
3319+
*/
3320+
class InferredRefinedType(parent: Type, refinedName: Name, refinedInfo: Type)
3321+
extends RefinedType(parent, refinedName, refinedInfo):
3322+
override def newLikeThis(parent: Type, refinedName: Name, refinedInfo: Type)(using Context): Type =
3323+
InferredRefinedType(parent, refinedName, refinedInfo)
33113324

33123325
object RefinedType {
33133326
@tailrec def make(parent: Type, names: List[Name], infos: List[Type])(using Context): Type =
@@ -3322,6 +3335,10 @@ object Types extends TypeUtils {
33223335
def precise(parent: Type, name: Name, info: Type)(using Context): RefinedType =
33233336
assert(!ctx.erasedTypes)
33243337
unique(new PreciseRefinedType(parent, name, info)).checkInst
3338+
3339+
def inferred(parent: Type, name: Name, info: Type)(using Context): RefinedType =
3340+
assert(!ctx.erasedTypes)
3341+
unique(new InferredRefinedType(parent, name, info)).checkInst
33253342
}
33263343

33273344
/** A recursive type. Instances should be constructed via the companion object.

library/src/scala/collection/immutable/LazyListIterable.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ final class LazyListIterable[+A] private (lazyState: LazyListIterable.EmptyMarke
311311
throw new RuntimeException(
312312
"LazyListIterable evaluation depends on its own result (self-reference); see docs for more info")
313313

314-
val fun = _tail.asInstanceOf[() ->{this} LazyListIterable[A]^]
314+
val fun = _tail.asInstanceOf[() ->{this} LazyListIterable[A]^{this}]
315315
_tail = MidEvaluation
316316
val l =
317317
// `fun` returns a LazyListIterable that represents the state (head/tail) of `this`. We call `l.evaluated` to ensure
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/apply-fresh.scala:17:15 ----------------------------------
2+
17 | val _: Ref = x // error
3+
| ^
4+
| Found: (x : Ref^)
5+
| Required: Ref
6+
|
7+
| Note that capability cap is not included in capture set {}.
8+
|
9+
| where: ^ and cap refer to a fresh root capability in the type of value x
10+
|
11+
| longer explanation available when compiling with `-explain`
12+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/apply-fresh.scala:21:15 ----------------------------------
13+
21 | val _: Ref = y // error
14+
| ^
15+
| Found: (y : Ref^)
16+
| Required: Ref
17+
|
18+
| Note that capability cap is not included in capture set {}.
19+
|
20+
| where: ^ and cap refer to a fresh root capability in the type of value y
21+
|
22+
| longer explanation available when compiling with `-explain`
23+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/apply-fresh.scala:25:15 ----------------------------------
24+
25 | val _: Ref = z // error
25+
| ^
26+
| Found: (z : Ref^{bar.newRef})
27+
| Required: Ref
28+
|
29+
| Note that capability bar.newRef is not included in capture set {}.
30+
|
31+
| longer explanation available when compiling with `-explain`
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
object Test
2+
3+
class Ref
4+
5+
class Foo:
6+
def newRef: Ref^ = Ref()
7+
8+
class Bar:
9+
val newRef: Ref^ = Ref()
10+
11+
def newRef: Ref^ = Ref()
12+
13+
def test =
14+
15+
val f = () => newRef
16+
val x = f()
17+
val _: Ref = x // error
18+
19+
val foo = Foo()
20+
val y = foo.newRef
21+
val _: Ref = y // error
22+
23+
val bar = Bar()
24+
val z = bar.newRef
25+
val _: Ref = z // error
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt-depfun.scala:11:43 ----------------------------------
22
11 | val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error
33
| ^^^^^^^
4-
| Found: Str^{} ->{ac, y, z} Str^{y, z}
5-
| Required: Str^{y, z} => Str^{y, z}
4+
|Found: Str^{} => Str^{y, z}
5+
|Required: Str^{y, z} =>² Str^{y, z}
66
|
7-
| Note that capability y is not included in capture set {}.
7+
|Note that capability y is not included in capture set {}.
88
|
9-
| where: => refers to a fresh root capability in the type of value dc
9+
|where: => refers to a fresh root capability created in value dc when instantiating method apply's type (x: C^): Str^{x} =>³ Str^{x}
10+
| =>² refers to a fresh root capability in the type of value dc
1011
|
1112
| longer explanation available when compiling with `-explain`

tests/neg-custom-args/captures/caseclass/Test_2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ def test(c: C) =
2222

2323
val y4 = y3 match
2424
case Ref(xx) => xx
25-
val y4c: () ->{y3} Unit = y4
25+
val y4c: () ->{y3} Unit = y4 // error
Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,56 @@
11
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-existential-conformance.scala:8:24 --------------------
22
8 | val y: A -> Fun[B^] = x // error
33
| ^
4-
| Found: (x : A -> (x²: A) -> B^)
5-
| Required: A -> A -> B^²
4+
| Found: (x : A -> (x²: A) -> B^)
5+
| Required: A -> A -> B^²
66
|
7-
| Note that capability cap is not included in capture set {cap²}
8-
| because cap is not visible from cap² in value y.
7+
| Note that capability cap is not included in capture set {cap²}
8+
| because cap is not visible from cap² in value y.
99
|
10-
| where: ^ and cap refer to a root capability associated with the result type of (x²: A): B^
11-
| ^² and cap² refer to a fresh root capability in the type of value y
12-
| x is a value in method test
13-
| x² is a reference to a value parameter
10+
| where: ^ refers to a root capability associated with the result type of (x²: A): B^
11+
| ^² and cap² refer to a fresh root capability in the type of value y
12+
| cap is the universal root capability
13+
| x is a value in method test
14+
| x² is a reference to a value parameter
1415
|
1516
| longer explanation available when compiling with `-explain`
1617
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-existential-conformance.scala:9:29 --------------------
1718
9 | val z: A -> (x: A) -> B^ = y // error
1819
| ^
19-
| Found: A -> A -> B^{y*}
20-
| Required: A -> (x: A) -> B^
20+
| Found: A -> A -> B^{y*}
21+
| Required: A -> (x: A) -> B^
2122
|
22-
| Note that capability y* is not included in capture set {cap}.
23+
| Note that capability y* is not included in capture set {cap}.
2324
|
24-
| Note that the existential capture root in B^²
25-
| cannot subsume the capability y* since that capability is not a `Sharable` capability..
26-
|
27-
| where: ^ and cap refer to a root capability associated with the result type of (x: A): B^
28-
| ^² refers to the universal root capability
25+
| where: ^ refers to a root capability associated with the result type of (x: A): B^
26+
| cap is the universal root capability
2927
|
3028
| longer explanation available when compiling with `-explain`
3129
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-existential-conformance.scala:13:19 -------------------
3230
13 | val y: Fun[B^] = x // error
3331
| ^
34-
| Found: (x : (x²: A) -> B^)
35-
| Required: A -> B^²
32+
| Found: (x : (x²: A) -> B^)
33+
| Required: A -> B^²
3634
|
37-
| Note that capability cap is not included in capture set {cap²}
38-
| because cap is not visible from cap² in value y.
35+
| Note that capability cap is not included in capture set {cap²}
36+
| because cap is not visible from cap² in value y.
3937
|
40-
| where: ^ and cap refer to a root capability associated with the result type of (x²: A): B^
41-
| ^² and cap² refer to a fresh root capability in the type of value y
42-
| x is a value in method test2
43-
| x² is a reference to a value parameter
38+
| where: ^ refers to a root capability associated with the result type of (x²: A): B^
39+
| ^² and cap² refer to a fresh root capability in the type of value y
40+
| cap is the universal root capability
41+
| x is a value in method test2
42+
| x² is a reference to a value parameter
4443
|
4544
| longer explanation available when compiling with `-explain`
4645
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-existential-conformance.scala:14:24 -------------------
4746
14 | val z: (x: A) -> B^ = y // error
4847
| ^
49-
| Found: A -> B^{y*}
50-
| Required: (x: A) -> B^
51-
|
52-
| Note that capability y* is not included in capture set {cap}.
48+
| Found: A -> B^{y*}
49+
| Required: (x: A) -> B^
5350
|
54-
| Note that the existential capture root in B^²
55-
| cannot subsume the capability y* since that capability is not a `Sharable` capability..
51+
| Note that capability y* is not included in capture set {cap}.
5652
|
57-
| where: ^ and cap refer to a root capability associated with the result type of (x: A): B^
58-
| refers to the universal root capability
53+
| where: ^ refers to a root capability associated with the result type of (x: A): B^
54+
| cap is the universal root capability
5955
|
6056
| longer explanation available when compiling with `-explain`

0 commit comments

Comments
 (0)