From 02aae433d918495723ae8c102cf2b94f0133631d Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 13 Sep 2024 10:59:17 +0200 Subject: [PATCH 1/3] Treat more closure parameter types as inferred This is necessary for types that contain possibly illegal @retains annotations since those annotations are only removed before pickling for InferredTypes. Fixes #21347 --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 11 +++++++++-- tests/pos-custom-args/captures/i21347.scala | 11 +++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 tests/pos-custom-args/captures/i21347.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ce5743f69d0c..901e27a2f1a1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1903,9 +1903,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if knownFormal then formal0 else errorType(AnonymousFunctionMissingParamType(param, tree, inferredType = formal, expectedType = pt), param.srcPos) ) + val untpdTpt = formal match + case _: WildcardType => + // In this case we have a situation like f(_), where we expand in the end to + // (x: T) => f(x) and `T` is taken from `f`'s declared parameters. In this case + // we treat the type as declared instead of inferred. InferredType is used for + // types that are inferred from the context. + untpd.TypeTree() + case _ => InferredTypeTree() val paramTpt = untpd.TypedSplice( - (if knownFormal then InferredTypeTree() else untpd.TypeTree()) - .withType(paramType.translateFromRepeated(toArray = false)) + untpdTpt.withType(paramType.translateFromRepeated(toArray = false)) .withSpan(param.span.endPos) ) val param0 = cpy.ValDef(param)(tpt = paramTpt) diff --git a/tests/pos-custom-args/captures/i21347.scala b/tests/pos-custom-args/captures/i21347.scala new file mode 100644 index 000000000000..e74c15bff8c1 --- /dev/null +++ b/tests/pos-custom-args/captures/i21347.scala @@ -0,0 +1,11 @@ +//> using scala 3.6.0-RC1-bin-SNAPSHOT + +import language.experimental.captureChecking + +class Box[Cap^] {} + +def run[Cap^](f: Box[Cap]^{Cap^} => Unit): Box[Cap]^{Cap^} = ??? + +def main() = + val b = run(_ => ()) + // val b = run[caps.CapSet](_ => ()) // this compiles \ No newline at end of file From edd40bc50d318a188f8c1767c636526fedbb990e Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 13 Sep 2024 11:05:21 +0200 Subject: [PATCH 2/3] Avoid using ExplainingTypeComparer in regular code The operations of an ExplainingTypeComparer are expensive. So we should only run it when producing an error message. --- .../src/dotty/tools/dotc/core/TypeOps.scala | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 0d8801b646ee..bfda613d0586 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -691,20 +691,18 @@ object TypeOps: val hiBound = instantiate(bounds.hi, skolemizedArgTypes) val loBound = instantiate(bounds.lo, skolemizedArgTypes) - def check(tp1: Type, tp2: Type, which: String, bound: Type)(using Context) = { - val isSub = TypeComparer.explaining { cmp => - val isSub = cmp.isSubType(tp1, tp2) - if !isSub then - if !ctx.typerState.constraint.domainLambdas.isEmpty then - typr.println(i"${ctx.typerState.constraint}") - if !ctx.gadt.symbols.isEmpty then - typr.println(i"${ctx.gadt}") - typr.println(cmp.lastTrace(i"checkOverlapsBounds($lo, $hi, $arg, $bounds)($which)")) - //trace.dumpStack() - isSub - }//(using ctx.fresh.setSetting(ctx.settings.verbose, true)) // uncomment to enable moreInfo in ExplainingTypeComparer recur - if !isSub then violations += ((arg, which, bound)) - } + def check(tp1: Type, tp2: Type, which: String, bound: Type)(using Context) = + val isSub = TypeComparer.isSubType(tp1, tp2) + if !isSub then + // inContext(ctx.fresh.setSetting(ctx.settings.verbose, true)): // uncomment to enable moreInfo in ExplainingTypeComparer + TypeComparer.explaining: cmp => + if !ctx.typerState.constraint.domainLambdas.isEmpty then + typr.println(i"${ctx.typerState.constraint}") + if !ctx.gadt.symbols.isEmpty then + typr.println(i"${ctx.gadt}") + typr.println(cmp.lastTrace(i"checkOverlapsBounds($lo, $hi, $arg, $bounds)($which)")) + violations += ((arg, which, bound)) + check(lo, hiBound, "upper", hiBound)(using checkCtx) check(loBound, hi, "lower", loBound)(using checkCtx) } From 45728afddd3d1f5ffca185a302959f3f39dddc08 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 13 Sep 2024 13:12:01 +0200 Subject: [PATCH 3/3] Embed accountsFor info in regular explain traces --- compiler/src/dotty/tools/dotc/cc/CaptureSet.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index c57ad639783c..44d5e2cf4b88 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -158,9 +158,13 @@ sealed abstract class CaptureSet extends Showable: * as frozen. */ def accountsFor(x: CaptureRef)(using Context): Boolean = - reporting.trace(i"$this accountsFor $x, ${x.captureSetOfInfo}?", show = true): + def debugInfo(using Context) = i"$this accountsFor $x, which has capture set ${x.captureSetOfInfo}" + def test(using Context) = reporting.trace(debugInfo): elems.exists(_.subsumes(x)) || !x.isMaxCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK + comparer match + case comparer: ExplainingTypeComparer => comparer.traceIndented(debugInfo)(test) + case _ => test /** A more optimistic version of accountsFor, which does not take variable supersets * of the `x` reference into account. A set might account for `x` if it accounts