From 8a39d35e726ef0da70dd71d5a73be9ffb42fbad5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 11 Apr 2021 12:21:07 +0200 Subject: [PATCH 1/3] Avoid wildcards when typing default parameters of context function type --- .../src/dotty/tools/dotc/typer/Namer.scala | 26 ++++++++++++++++++- tests/neg/i12019.scala | 20 ++++++++++++++ tests/pos/i12019.scala | 21 +++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i12019.scala create mode 100644 tests/pos/i12019.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index aa0b1eccfc4c..d60a1200bd80 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1483,6 +1483,30 @@ class Namer { typer: Typer => case _ => NoType + /** The expected type for a default argument. This is normally the `defaultParamType` + * with references to internal parameters replaced by wildcards. This replacement + * makes it possible that the default argument can be a more specific type than the + * parameter. For instance, we allow + * + * class C[A](a: A) { def copy[B](x: B = a): C[B] = C(x) } + * + * However, if the default parameter type is a context function type, we + * have to make sure that wildcard types do not leak into the implicitly + * generated closure's result type. Test case is pos/i12019.scala. If there + * would be a leakage with the wildcard approximation, we pick the original + * default parameter type as expected type. + */ + def expectedDefaultArgType = + val originalTp = defaultParamType + val approxTp = wildApprox(originalTp) + approxTp.stripPoly match + case atp @ defn.ContextFunctionType(_, resType, _) + if !defn.isNonRefinedFunction(approxTp) // in this case `resType` is lying, gives us only the non-dependent upper bound + || resType.existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true, forceLazy = false) => + originalTp + case _ => + approxTp + // println(s"final inherited for $sym: ${inherited.toString}") !!! // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") // TODO Scala 3.1: only check for inline vals (no final ones) @@ -1509,7 +1533,7 @@ class Namer { typer: Typer => // expected type but we run it through `wildApprox` to allow default // parameters like in `def mkList[T](value: T = 1): List[T]`. val defaultTp = defaultParamType - val pt = inherited.orElse(wildApprox(defaultTp)).orElse(WildcardType).widenExpr + val pt = inherited.orElse(expectedDefaultArgType).orElse(WildcardType).widenExpr val tp = typedAheadRhs(pt).tpe if (defaultTp eq pt) && (tp frozen_<:< defaultTp) then // When possible, widen to the default getter parameter type to permit a diff --git a/tests/neg/i12019.scala b/tests/neg/i12019.scala new file mode 100644 index 000000000000..5a811940061a --- /dev/null +++ b/tests/neg/i12019.scala @@ -0,0 +1,20 @@ +object test1: + trait A + type B <: AnyRef + val test1: A ?=> B = null // error + val test2: A ?=> B = A ?=> null // error + +import scala.quoted.* + +object Eg1 { + + // no default arg: ok + def ok (f: (q: Quotes) ?=> q.reflect.Term) = () + + // default the function *reference* to null: compilation error + def ko_1(f: (q: Quotes) ?=> q.reflect.Term = null) = () // error + + // default the function *result* to null: compilation error + def ko_2(f: (q: Quotes) ?=> q.reflect.Term = (_: Quotes) ?=> null) = () // error +} + diff --git a/tests/pos/i12019.scala b/tests/pos/i12019.scala new file mode 100644 index 000000000000..6b671be54a29 --- /dev/null +++ b/tests/pos/i12019.scala @@ -0,0 +1,21 @@ +trait A: + type X >: Null + +def ko1(f: (q: A) ?=> Int => q.X = null) = () +def ko2(f: (q: A) ?=> Int => q.X = (_: A) ?=> null) = () +def ko3(f: (q: A) => q.X = (q => null)) = () + + +import scala.quoted.* + +object Eg2 { + + // no default arg: ok + def ok (f: (q: Quotes) ?=> q.reflect.ValDef => q.reflect.Term) = () + + // default the function *reference* to null: crash! + def ko_1(f: (q: Quotes) ?=> q.reflect.ValDef => q.reflect.Term = null) = () + + // default the function *result* to null: crash! + def ko_2(f: (q: Quotes) ?=> q.reflect.ValDef => q.reflect.Term = (_: Quotes) ?=> null) = () +} \ No newline at end of file From 5ea9a741315d36366ac9bd2460c13ea54e76e62d Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 11 Apr 2021 12:23:02 +0200 Subject: [PATCH 2/3] Update compiler/src/dotty/tools/dotc/typer/Namer.scala --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index d60a1200bd80..877a624e3d40 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1485,7 +1485,7 @@ class Namer { typer: Typer => /** The expected type for a default argument. This is normally the `defaultParamType` * with references to internal parameters replaced by wildcards. This replacement - * makes it possible that the default argument can be a more specific type than the + * makes it possible that the default argument can have a more specific type than the * parameter. For instance, we allow * * class C[A](a: A) { def copy[B](x: B = a): C[B] = C(x) } From 8986cb75c7e1a128dd8028f3be2696021434eeec Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 11 Apr 2021 12:31:14 +0200 Subject: [PATCH 3/3] Update compiler/src/dotty/tools/dotc/typer/Namer.scala --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 877a624e3d40..3be5fae4df98 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1501,7 +1501,7 @@ class Namer { typer: Typer => val approxTp = wildApprox(originalTp) approxTp.stripPoly match case atp @ defn.ContextFunctionType(_, resType, _) - if !defn.isNonRefinedFunction(approxTp) // in this case `resType` is lying, gives us only the non-dependent upper bound + if !defn.isNonRefinedFunction(atp) // in this case `resType` is lying, gives us only the non-dependent upper bound || resType.existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true, forceLazy = false) => originalTp case _ =>