Skip to content

Commit 8eb125d

Browse files
committed
Do postchecking inside out.
Previously we sometimes got a "non=private definition with nonempty capturing type needs explicit type ascription error" that hid a more important error such as a leaking universal capture set.
1 parent e614f23 commit 8eb125d

File tree

1 file changed

+59
-53
lines changed

1 file changed

+59
-53
lines changed

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

Lines changed: 59 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -986,61 +986,67 @@ class CheckCaptures extends Recheck, SymTransformer:
986986
* - Heal ill-formed capture sets of type parameters. See `healTypeParam`.
987987
*/
988988
def postCheck(unit: tpd.Tree)(using Context): Unit =
989-
unit.foreachSubTree {
990-
case _: InferredTypeTree =>
991-
case tree: TypeTree if !tree.span.isZeroExtent =>
992-
tree.knownType.foreachPart { tp =>
993-
checkWellformedPost(tp, tree.srcPos)
994-
tp match
995-
case AnnotatedType(_, annot) if annot.symbol == defn.RetainsAnnot =>
996-
warnIfRedundantCaptureSet(annot.tree)
997-
case _ =>
998-
}
999-
case t: ValOrDefDef
1000-
if t.tpt.isInstanceOf[InferredTypeTree] && !Synthetics.isExcluded(t.symbol) =>
1001-
val sym = t.symbol
1002-
val isLocal =
1003-
sym.owner.ownersIterator.exists(_.isTerm)
1004-
|| sym.accessBoundary(defn.RootClass).isContainedIn(sym.topLevelClass)
1005-
def canUseInferred = // If canUseInferred is false, all capturing types in the type of `sym` need to be given explicitly
1006-
sym.is(Private) // private symbols can always have inferred types
1007-
|| sym.name.is(DefaultGetterName) // default getters are exempted since otherwise it would be
1008-
// too annoying. This is a hole since a defualt getter's result type
1009-
// might leak into a type variable.
1010-
|| // non-local symbols cannot have inferred types since external capture types are not inferred
1011-
isLocal // local symbols still need explicit types if
1012-
&& !sym.owner.is(Trait) // they are defined in a trait, since we do OverridingPairs checking before capture inference
1013-
def isNotPureThis(ref: CaptureRef) = ref match {
1014-
case ref: ThisType => !ref.cls.isPureClass
1015-
case _ => true
1016-
}
1017-
if !canUseInferred then
1018-
val inferred = t.tpt.knownType
1019-
def checkPure(tp: Type) = tp match
1020-
case CapturingType(_, refs)
1021-
if !refs.elems.filter(isNotPureThis).isEmpty =>
1022-
val resultStr = if t.isInstanceOf[DefDef] then " result" else ""
1023-
report.error(
1024-
em"""Non-local $sym cannot have an inferred$resultStr type
1025-
|$inferred
1026-
|with non-empty capture set $refs.
1027-
|The type needs to be declared explicitly.""".withoutDisambiguation(),
1028-
t.srcPos)
989+
val checker = new TreeTraverser:
990+
def traverse(tree: Tree)(using Context): Unit =
991+
traverseChildren(tree)
992+
check(tree)
993+
def check(tree: Tree) = tree match
994+
case _: InferredTypeTree =>
995+
case tree: TypeTree if !tree.span.isZeroExtent =>
996+
tree.knownType.foreachPart { tp =>
997+
checkWellformedPost(tp, tree.srcPos)
998+
tp match
999+
case AnnotatedType(_, annot) if annot.symbol == defn.RetainsAnnot =>
1000+
warnIfRedundantCaptureSet(annot.tree)
1001+
case _ =>
1002+
}
1003+
case t: ValOrDefDef
1004+
if t.tpt.isInstanceOf[InferredTypeTree] && !Synthetics.isExcluded(t.symbol) =>
1005+
val sym = t.symbol
1006+
val isLocal =
1007+
sym.owner.ownersIterator.exists(_.isTerm)
1008+
|| sym.accessBoundary(defn.RootClass).isContainedIn(sym.topLevelClass)
1009+
def canUseInferred = // If canUseInferred is false, all capturing types in the type of `sym` need to be given explicitly
1010+
sym.is(Private) // private symbols can always have inferred types
1011+
|| sym.name.is(DefaultGetterName) // default getters are exempted since otherwise it would be
1012+
// too annoying. This is a hole since a defualt getter's result type
1013+
// might leak into a type variable.
1014+
|| // non-local symbols cannot have inferred types since external capture types are not inferred
1015+
isLocal // local symbols still need explicit types if
1016+
&& !sym.owner.is(Trait) // they are defined in a trait, since we do OverridingPairs checking before capture inference
1017+
def isNotPureThis(ref: CaptureRef) = ref match {
1018+
case ref: ThisType => !ref.cls.isPureClass
1019+
case _ => true
1020+
}
1021+
if !canUseInferred then
1022+
val inferred = t.tpt.knownType
1023+
def checkPure(tp: Type) = tp match
1024+
case CapturingType(_, refs)
1025+
if !refs.elems.filter(isNotPureThis).isEmpty =>
1026+
val resultStr = if t.isInstanceOf[DefDef] then " result" else ""
1027+
report.error(
1028+
em"""Non-local $sym cannot have an inferred$resultStr type
1029+
|$inferred
1030+
|with non-empty capture set $refs.
1031+
|The type needs to be declared explicitly.""".withoutDisambiguation(),
1032+
t.srcPos)
1033+
case _ =>
1034+
inferred.foreachPart(checkPure, StopAt.Static)
1035+
case t @ TypeApply(fun, args) =>
1036+
fun.knownType.widen match
1037+
case tl: PolyType =>
1038+
val normArgs = args.lazyZip(tl.paramInfos).map { (arg, bounds) =>
1039+
arg.withType(arg.knownType.forceBoxStatus(
1040+
bounds.hi.isBoxedCapturing | bounds.lo.isBoxedCapturing))
1041+
}
1042+
checkBounds(normArgs, tl)
10291043
case _ =>
1030-
inferred.foreachPart(checkPure, StopAt.Static)
1031-
case t @ TypeApply(fun, args) =>
1032-
fun.knownType.widen match
1033-
case tl: PolyType =>
1034-
val normArgs = args.lazyZip(tl.paramInfos).map { (arg, bounds) =>
1035-
arg.withType(arg.knownType.forceBoxStatus(
1036-
bounds.hi.isBoxedCapturing | bounds.lo.isBoxedCapturing))
1037-
}
1038-
checkBounds(normArgs, tl)
1039-
case _ =>
10401044

1041-
args.foreach(healTypeParam(_))
1042-
case _ =>
1043-
}
1045+
args.foreach(healTypeParam(_))
1046+
case _ =>
1047+
end check
1048+
end checker
1049+
checker.traverse(unit)
10441050
if !ctx.reporter.errorsReported then
10451051
// We dont report errors here if previous errors were reported, because other
10461052
// errors often result in bad applied types, but flagging these bad types gives

0 commit comments

Comments
 (0)